Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
adlarkin committed May 11, 2021
1 parent 0e61ee4 commit 1d71bcb
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 40 deletions.
5 changes: 5 additions & 0 deletions include/simpleECM/Components.hh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ struct BaseComponent
/// \return The typeId of the derived component
public: virtual ComponentTypeId DerivedTypeId() const = 0;

/// \brief Whether this component should be ignored or not. This is used
/// internally by the ECM to handle component addition and removal, and should
/// not be used outside of the ECM
public: bool ignore{false};

public: constexpr const static ComponentTypeId typeId{kInvalidComponent};
};

Expand Down
125 changes: 92 additions & 33 deletions include/simpleECM/Ecm.hh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ class ECM
/// \return The Entity that was created
public: Entity CreateEntity();

// TODO handle adding/removing a component before Each is called. In this
// case, the entity with this component may try to be added to a view when it
// shouldn't be

/// \brief Add a component to an entity (the entity must already exist)
/// \param[in] _entity The entity
/// \param[in] _component The component
Expand Down Expand Up @@ -46,25 +50,41 @@ class ECM
private: template<typename ...ComponentTypeTs>
View<ComponentTypeTs...> *FindView();

/// \brief Check if an entity has a component of a particular type
/// \brief Check if an entity has a reference to a component of a particular
/// type. This does not indicate whether the component being referred to is
/// valid/being used by the entity or not (the component may have been removed
/// after it was originally added to the entity). The Component method should
/// be used to learn more about the state of the referenced component, if a
/// reference exists
/// \param[in] _entity The entity
/// \param[in] _typeId The component type
/// \return true if _entity has a component of _typeId, false otherwise
/// (false is returned if _entity does not exist)
/// \return true if _entity has a reference to a component of _typeId, false
/// otherwise (false is returned if _entity does not exist)
/// \sa Component
private: bool HasComponent(const Entity &_entity,
const ComponentTypeId &_typeId) const;

private: bool HasValidComponent(const Entity &_entity,
const ComponentTypeId &_typeId) const;

/// \brief Get the pointer to an entity's component of a particular type
/// \param[in] _entity The entity
/// \return The pointer to the component, if it exists. Otherwise, nullptr
/// \return The pointer to the component (nullptr if the component doesn't
/// exist). If the pointer isn't nullptr, the ignore flag of the component
/// should be checked to see if this component is being used by _entity or
/// not. Ignore is true if _entity isn't using this component (i.e., the
/// component was added to _entity but later removed from _entity) Ignore is
/// false if _entity is using this component (i.e., the component was added to
/// _entity and has not been removed)
/// \sa HasComponent
private: template<typename ComponentTypeT>
ComponentTypeT *Component(const Entity &_entity) const;

/// \brief See if an entity has a list of component types
/// \brief See if an entity has a list of valid component types
/// \param[in] _entity The entity
/// \param[in] _compTypes The types of components
/// \return true if _entity has each type of component in _compTypes, false
/// otherwise
/// \return true if _entity has each type of component in _compTypes as valid
/// references, false otherwise
private: bool HasAllComponents(const Entity &_entity,
const std::vector<ComponentTypeId> &_compTypes) const;

Expand Down Expand Up @@ -119,53 +139,81 @@ Entity ECM::CreateEntity()
template<typename ComponentTypeT>
void ECM::AddComponent(const Entity &_entity, const ComponentTypeT &_component)
{
// check to see if a component of type ComponentTypeT::typeId already exists
// for _entity. There are a few cases to consider:
// 1. A component of this type was added to _entity previously
// a. If this component wasn't removed, this component's ignore flag is
// false, so the request to add a new component should be ignored
// b. If this component was removed, this component's ignore flag is true,
// so this component should be modified directly to contain the data
// contained in _component
// 2. A component of this type was never added to _entity previously. In this
// case, a new component needs to be created and added to _entity, and
// _entity may need to be added to views that have this component type

if (this->HasComponent(_entity, ComponentTypeT::typeId))
{
auto existingCompPtr = this->Component<ComponentTypeT>(_entity);
// case 1b
if (existingCompPtr->ignore)
{
// update the component data and notify relevant views that _entity had a
// component update in case _entity once again meets the component
// requirements of the view
*existingCompPtr = _component;
existingCompPtr->ignore = false;
for (auto &[compTypes, view] : this->views)
{
if (view->HasComponent(ComponentTypeT::typeId))
{
if (view->HasComponentData(_entity))
view->NotifyComponentAddition(_entity, ComponentTypeT::typeId);
else if (this->HasAllComponents(_entity, compTypes))
view->AddNewEntity(_entity);
}
}
}
// (if the check above for case 1b failed, conditions for case 1a are met,
// which means do nothing/ignore the add component request)
return;
}

// case 2
auto entityCompIter = this->entityComponents.find(_entity);
auto vectorIdx = entityCompIter->second.size();
entityCompIter->second.push_back(
std::make_shared<ComponentTypeT>(_component));
this->componentTypeIndex[_entity][ComponentTypeT::typeId] = vectorIdx;

for (auto &[compTypes, view] : this->views)
{
if (!view->HasEntity(_entity) && !view->HasNewEntity(_entity) &&
//if (!view->HasEntity(_entity) && !view->HasNewEntity(_entity) &&
if (!view->HasComponentData(_entity) && !view->HasNewEntity(_entity) &&
this->HasAllComponents(_entity, compTypes))
view->AddNewEntity(_entity);
}
}


template<typename ComponentTypeT>
void ECM::RemoveComponent(const Entity &_entity)
{
if (!this->HasComponent(_entity, ComponentTypeT::typeId))
return;

// remove the component
// - if the component to remove is the last component in the entity's vector
// of components, simply pop off the last component from the vector
// - if the component to remove is not the last component in the entity's
// vector of components, swap it with the last component in the vector and
// then pop off the last component in the vector
const auto removalCompIdx =
this->componentTypeIndex[_entity][ComponentTypeT::typeId];
if (removalCompIdx != (this->entityComponents[_entity].size() - 1))
{
const auto lastBaseComp = this->entityComponents[_entity].back();
this->componentTypeIndex[_entity][lastBaseComp->DerivedTypeId()] =
removalCompIdx;
this->entityComponents[_entity][removalCompIdx] = lastBaseComp;
}
this->entityComponents[_entity].pop_back();
this->componentTypeIndex[_entity].erase(ComponentTypeT::typeId);

// remove the entity from the views that have this component
for (auto &[compTypes, view] : this->views)
auto compPtr = this->Component<ComponentTypeT>(_entity);
if (!compPtr->ignore)
{
if (view->HasComponent(ComponentTypeT::typeId))
view->RemoveEntity(_entity);
compPtr->ignore = true;
for (auto &[compTypes, view] : this->views)
{
// notify relevant views that a component has been removed from _entity
// (_entity no longer meets the component requirements for views that
// contain component data of type ComponentTypeT::typeId)
if (view->HasComponent(ComponentTypeT::typeId) &&
view->HasEntity(_entity))
{
view->NotifyComponentRemoval(_entity, ComponentTypeT::typeId);
}
}
}
}

Expand Down Expand Up @@ -233,6 +281,17 @@ bool ECM::HasComponent(const Entity &_entity,
typeMapIter->second.find(_typeId) != typeMapIter->second.end();
}

// TODO clean this up and turn it into something more useful
bool ECM::HasValidComponent(const Entity &_entity,
const ComponentTypeId &_typeId) const
{
auto typeMapIter = this->componentTypeIndex.find(_entity);
return typeMapIter != this->componentTypeIndex.end() &&
typeMapIter->second.find(_typeId) != typeMapIter->second.end() &&
!this->entityComponents.at(_entity)
.at(typeMapIter->second.find(_typeId)->second)->ignore;
}

template<typename ComponentTypeT>
ComponentTypeT *ECM::Component(const Entity &_entity) const
{
Expand All @@ -251,7 +310,7 @@ bool ECM::HasAllComponents(const Entity &_entity,
{
for (const auto &type : _compTypes)
{
if (!this->HasComponent(_entity, type))
if (!this->HasValidComponent(_entity, type))
return false;
}

Expand Down
96 changes: 89 additions & 7 deletions include/simpleECM/View.hh
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef VIEW_HH_
#define VIEW_HH_

#include <cstddef>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
Expand All @@ -11,11 +12,13 @@ class BaseView
{
/// \brief Get the entities that are stored in the view
/// \return The entities in the view
public: std::unordered_set<Entity> Entities() const
public: const std::unordered_set<Entity> &Entities() const
{
return this->entities;
}

public: virtual bool HasComponentData(const Entity &_entity) = 0;

/// \brief Check if an entity is a part of the view
/// \param[in] _entity The entity
/// \return true if _entity is a part of the view, false otherwise
Expand All @@ -39,7 +42,7 @@ class BaseView

/// \brief Get all of the new entities that should be added to the view
/// \return The entities
public: std::unordered_set<Entity> NewEntities() const
public: const std::unordered_set<Entity> &NewEntities() const
{
return this->newEntities;
}
Expand All @@ -65,11 +68,27 @@ class BaseView
/// \param[in] _typeId The component type
/// \return true if the view has component data of type _typeId, false
/// otherwise
public: bool virtual HasComponent(const ComponentTypeId &_typeId) const
public: bool HasComponent(const ComponentTypeId &_typeId) const
{
return this->compTypes.find(_typeId) != this->compTypes.end();
}

/// \brief Update the internal data in the view because a component has been
/// added to an entity. It is assumed that the entity already is a part of the
/// view.
/// \param[in] _entity The entity
/// \param[in] _typeId The type of component that was added to _entity
public: virtual void NotifyComponentAddition(const Entity &_entity,
const ComponentTypeId &_typeId) = 0;

/// \brief Update the internal data in the view because a component has been
/// removed to an entity. It is assumed that the entity already is a part of
/// the view.
/// \param[in] _entity The entity
/// \param[in] _typeId The type of component that was removed from _entity
public: virtual void NotifyComponentRemoval(const Entity &_entity,
const ComponentTypeId &_typeId) = 0;

/// \brief Destructor
public: virtual ~BaseView()
{
Expand All @@ -83,6 +102,14 @@ class BaseView

/// \brief The component types in the view
protected: std::unordered_set<ComponentTypeId> compTypes;

/// \brief A map that keeps track of which components for entities in
/// invalidData need to be added back to the entity in order to move the
/// entity back to validData.
///
/// \sa invalidData
protected: std::unordered_map<Entity, std::unordered_set<ComponentTypeId>>
missingCompTracker;
};

template<typename ...ComponentTypeTs>
Expand All @@ -96,13 +123,20 @@ class View : public BaseView
this->compTypes = {ComponentTypeTs::typeId...};
}

public: bool HasComponentData(const Entity &_entity)
{
return this->validData.find(_entity) != this->validData.end() ||
this->invalidData.find(_entity) != this->invalidData.end();
}

/// \brief Get an entity and its component data. It is assumed that the
/// entity being requested exists in the view
/// \param[in] _entity The entity
/// \return The entity and its component data
// TODO return a reference here instead of a copy?
public: ComponentData EntityComponentData(const Entity &_entity) const
{
return this->data.at(_entity);
return this->validData.at(_entity);
}

/// \brief Add an entity with its component data to the view. It is assunmed
Expand All @@ -111,7 +145,7 @@ class View : public BaseView
/// \param[in] _compPtrs Pointers to the entity's components
public: void AddEntity(const Entity &_entity, ComponentTypeTs*... _compPtrs)
{
this->data[_entity] = std::make_tuple(_entity, _compPtrs...);
this->validData[_entity] = std::make_tuple(_entity, _compPtrs...);
this->entities.insert(_entity);
}

Expand All @@ -120,11 +154,59 @@ class View : public BaseView
{
this->newEntities.erase(_entity);
this->entities.erase(_entity);
this->data.erase(_entity);
this->validData.erase(_entity);
}

/// \brief Documentation inherited
public: void NotifyComponentAddition(const Entity &_entity,
const ComponentTypeId &_typeId)
{
auto missingCompsIter = this->missingCompTracker.find(_entity);
missingCompsIter->second.erase(_typeId);
if (missingCompsIter->second.empty())
{
this->validData[_entity] = this->invalidData[_entity];
this->entities.insert(_entity);
this->invalidData.erase(_entity);
this->missingCompTracker.erase(_entity);
}
};

/// \brief Documentation inherited
public: void NotifyComponentRemoval(const Entity &_entity,
const ComponentTypeId &_typeId)
{
// if the component being removed is the first component that causes _entity
// to be invalid for this view, move _entity from validData to invalidData
// and make sure _entity isn't considered a part of the view
if (this->validData.find(_entity) != this->validData.end())
{
this->invalidData[_entity] = this->validData[_entity];
this->validData.erase(_entity);
this->entities.erase(_entity);
this->missingCompTracker[_entity] = {_typeId};
}
else
this->missingCompTracker[_entity].insert(_typeId);
};

/// \brief A map of entities to their component data
private: std::unordered_map<Entity, ComponentData> data;
private: std::unordered_map<Entity, ComponentData> validData;

/// \brief A map of invalid entities to their component data. The difference
/// between invalidData and validData is that the entities in invalidData were
/// once in validData, but they had a component removed, so the entity no
/// longer meets the component requirements of the view. If the missing
/// component data is ever added back to an entitiy in invalidData, then this
/// entity will be moved back to validData
///
/// The reason for moving entities with missing components to invalidData
/// instead of completely deleting them is because tuple creation can be
/// costly, so this approach is used instead to maintain performance (the
/// tradeoff of mainting performance is increased complexity and memory usage)
///
/// \sa missingCompTracker
private: std::unordered_map<Entity, ComponentData> invalidData;
};

#endif

0 comments on commit 1d71bcb

Please sign in to comment.