diff --git a/include/simpleECM/Components.hh b/include/simpleECM/Components.hh index d32150f..4406fd6 100644 --- a/include/simpleECM/Components.hh +++ b/include/simpleECM/Components.hh @@ -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}; }; diff --git a/include/simpleECM/Ecm.hh b/include/simpleECM/Ecm.hh index 1118eb4..86e6ab3 100644 --- a/include/simpleECM/Ecm.hh +++ b/include/simpleECM/Ecm.hh @@ -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 @@ -46,25 +50,41 @@ class ECM private: template View *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 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 &_compTypes) const; @@ -119,53 +139,81 @@ Entity ECM::CreateEntity() template 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(_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(_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 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(_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); + } + } } } @@ -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 ComponentTypeT *ECM::Component(const Entity &_entity) const { @@ -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; } diff --git a/include/simpleECM/View.hh b/include/simpleECM/View.hh index 4019926..aa5c9f4 100644 --- a/include/simpleECM/View.hh +++ b/include/simpleECM/View.hh @@ -1,6 +1,7 @@ #ifndef VIEW_HH_ #define VIEW_HH_ +#include #include #include #include @@ -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 Entities() const + public: const std::unordered_set &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 @@ -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 NewEntities() const + public: const std::unordered_set &NewEntities() const { return this->newEntities; } @@ -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() { @@ -83,6 +102,14 @@ class BaseView /// \brief The component types in the view protected: std::unordered_set 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> + missingCompTracker; }; template @@ -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 @@ -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); } @@ -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 data; + private: std::unordered_map 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 invalidData; }; #endif