// This work derives from Vittorio Romeo's code used for cppcon 2015 licensed under the Academic Free License. // His code is available here: https://github.com/SuperV1234/cppcon2015 #ifndef EC_MANAGER_HPP #define EC_MANAGER_HPP #define EC_INIT_ENTITIES_SIZE 256 #define EC_GROW_SIZE_AMOUNT 256 #include #include #include #include #include #include #include #include #include "Meta/Combine.hpp" #include "Meta/Matching.hpp" #include "Bitset.hpp" namespace EC { /*! \brief Manages an EntityComponent system. EC::Manager must be created with a list of all used Components and all used tags. Note that all components must have a default constructor. Example: \code{.cpp} EC::Manager, TypeList> manager; \endcode */ template struct Manager { public: using Combined = EC::Meta::Combine; using BitsetType = EC::Bitset; private: template struct Storage { using type = std::tuple... >; }; using ComponentsStorage = typename EC::Meta::Morph >::type; // Entity: isAlive, dataIndex, ComponentsTags Info using EntitiesTupleType = std::tuple; using EntitiesType = std::vector; EntitiesType entities; ComponentsStorage componentsStorage; std::size_t currentCapacity = 0; std::size_t currentSize = 0; public: /*! \brief Initializes the manager with a default capacity. The default capacity is set with macro EC_INIT_ENTITIES_SIZE, and will grow by amounts of EC_GROW_SIZE_AMOUNT when needed. */ Manager() { resize(EC_INIT_ENTITIES_SIZE); } private: void resize(std::size_t newCapacity) { if(currentCapacity >= newCapacity) { return; } EC::Meta::forEach([this, newCapacity] (auto t) { std::get >(this->componentsStorage).resize(newCapacity); }); entities.resize(newCapacity); for(std::size_t i = currentCapacity; i < newCapacity; ++i) { entities[i] = std::make_tuple(false, i, BitsetType{}); } currentCapacity = newCapacity; } public: /*! \brief Adds an entity to the system, returning the ID of the entity. WARNING: The ID of an entity may change after calls to cleanup(). Usage of entity IDs should be safe during initialization. Otherwise, only use the ID given during usage of forMatchingSignature(). */ std::size_t addEntity() { if(currentSize == currentCapacity) { resize(currentCapacity + EC_GROW_SIZE_AMOUNT); } std::get(entities[currentSize]) = true; return currentSize++; } /*! \brief Marks an entity for deletion. A deleted Entity is not actually deleted until cleanup() is called. While an Entity is "deleted" but still in the system, calls to forMatchingSignature() will ignore the Entity. */ void deleteEntity(const std::size_t& index) { std::get(entities.at(index)) = false; } /*! \brief Checks if the Entity with the given ID is in the system. Note that deleted Entities that haven't yet been cleaned up (via cleanup()) are considered still in the system. */ bool hasEntity(const std::size_t& index) const { return index < currentSize; } /*! \brief Checks if the Entity is not marked as deleted. Note that invalid Entities (Entities where calls to hasEntity() returns false) will return false. */ bool isAlive(const std::size_t& index) const { return hasEntity(index) && std::get(entities.at(index)); } /*! \brief Returns a const reference to an Entity's info. An Entity's info is a std::tuple with a bool, std::size_t, and a bitset. \n The bool determines if the Entity is alive. \n The std::size_t is the ID to this Entity's data in the system. \n The bitset shows what Components and Tags belong to the Entity. */ const EntitiesTupleType& getEntityInfo(const std::size_t& index) const { return entities.at(index); } /*! \brief Returns a reference to a component belonging to the given Entity. This function will return a reference to a Component regardless of whether or not the Entity actually owns the reference. If the Entity doesn't own the Component, changes to the Component will not affect any Entity. It is recommended to use hasComponent() to determine if the Entity actually owns that Component. */ template Component& getEntityData(const std::size_t& index) { return std::get >(componentsStorage).at(std::get(entities.at(index))); } /*! \brief Returns a reference to a component belonging to the given Entity. Note that this function is the same as getEntityData(). This function will return a reference to a Component regardless of whether or not the Entity actually owns the reference. If the Entity doesn't own the Component, changes to the Component will not affect any Entity. It is recommended to use hasComponent() to determine if the Entity actually owns that Component. */ template Component& getEntityComponent(const std::size_t& index) { return getEntityData(index); } /*! \brief Checks whether or not the given Entity has the given Component. Example: \code{.cpp} manager.hasComponent(entityID); \endcode */ template bool hasComponent(const std::size_t& index) const { return std::get(entities.at(index)).template getComponentBit(); } /*! \brief Checks whether or not the given Entity has the given Tag. Example: \code{.cpp} manager.hasTag(entityID); \endcode */ template bool hasTag(const std::size_t& index) const { return std::get(entities.at(index)).template getTagBit(); } /*! \brief Does garbage collection on Entities. Does housekeeping on the vector containing Entities that will result in entity IDs changing if some Entities were marked for deletion. This function should be called periodically to correctly handle deletion of entities. */ void cleanup() { if(currentSize == 0) { return; } std::size_t rhs = currentSize - 1; std::size_t lhs = 0; while(lhs < rhs) { while(!std::get(entities[rhs])) { if(rhs == 0) { currentSize = 0; return; } std::get(entities[rhs]).reset(); --rhs; } if(lhs >= rhs) { break; } else if(!std::get(entities[lhs])) { // lhs is marked for deletion // swap lhs entity with rhs entity std::swap(entities[lhs], entities.at(rhs)); // clear deleted bitset std::get(entities[rhs]).reset(); // inc/dec pointers ++lhs; --rhs; } else { ++lhs; } } currentSize = rhs + 1; } /*! \brief Adds a component to the given Entity. Additional parameters given to this function will construct the Component with those parameters. Note that if the Entity already has the same component, then it will be overwritten by the newly created Component with the given arguments. Example: \code{.cpp} struct C0 { // constructor is compatible as a default constructor C0(int a = 0, char b = 'b') : a(a), b(b) {} int a; char b; } manager.addComponent(entityID, 10, 'd'); \endcode */ template void addComponent(const std::size_t& entityID, Args&&... args) { if(!hasEntity(entityID) || !isAlive(entityID)) { return; } Component component(std::forward(args)...); std::get(entities[entityID]).template getComponentBit() = true; std::get >(componentsStorage)[std::get(entities[entityID])] = std::move(component); } /*! \brief Removes the given Component from the given Entity. If the Entity does not have the Component given, nothing will change. Example: \code{.cpp} manager.removeComponent(entityID); \endcode */ template void removeComponent(const std::size_t& entityID) { if(!hasEntity(entityID) || !isAlive(entityID)) { return; } std::get(entities[entityID]).template getComponentBit() = false; } /*! \brief Adds the given Tag to the given Entity. Example: \code{.cpp} manager.addTag(entityID); \endcode */ template void addTag(const std::size_t& entityID) { if(!hasEntity(entityID) || !isAlive(entityID)) { return; } std::get(entities[entityID]).template getTagBit() = true; } /*! \brief Removes the given Tag from the given Entity. If the Entity does not have the Tag given, nothing will change. Example: \code{.cpp} manager.removeTag(entityID); \endcode */ template void removeTag(const std::size_t& entityID) { if(!hasEntity(entityID) || !isAlive(entityID)) { return; } std::get(entities[entityID]).template getTagBit() = false; } private: template struct ForMatchingSignatureHelper { template static void call(const std::size_t& entityID, CType& ctype, Function&& function) { function( entityID, ctype.template getEntityData(entityID)... ); } template void callInstance(const std::size_t& entityID, CType& ctype, Function&& function) const { ForMatchingSignatureHelper::call(entityID, ctype, std::forward(function)); } }; public: /*! \brief Calls the given function on all Entities matching the given Signature. The function object given to this function must accept std::size_t as its first parameter and Component references for the rest of the parameters. Tags specified in the Signature are only used as filters and will not be given as a parameter to the function. Example: \code{.cpp} manager.forMatchingSignature>([] (std::size_t ID, C0& component0, C1& component1) { // Lambda function contents here }); \endcode Note, the ID given to the function is not permanent. An entity's ID may change when cleanup() is called. */ template void forMatchingSignature(Function&& function) { using SignatureComponents = typename EC::Meta::Matching::type; using Helper = EC::Meta::Morph >; BitsetType signatureBitset = BitsetType::template generateBitset(); for(std::size_t i = 0; i < currentSize; ++i) { if(!std::get(entities[i])) { continue; } if((signatureBitset & std::get(entities[i])) == signatureBitset) { Helper::call(i, *this, std::forward(function)); } } } private: std::map > forMatchingFunctions; unsigned long long functionIndex = 0; public: /*! \brief Stores a function in the manager to be called later. As an alternative to calling functions directly with forMatchingSignature(), functions can be stored in the manager to be called later with callForMatchingFunctions() and callForMatchingFunction, and removed with clearForMatchingFunctions() and removeForMatchingFunction(). The syntax for the Function is the same as with forMatchingSignature(). Note that functions will be called in the same order they are inserted. Old functions may be overwritten if there are more functions than sizeof(unsigned long long) as they are stored in a map with unsigned long long as the key (index). Example: \code{.cpp} manager.addForMatchingFunction>([] (std::size_t ID, C0& component0, C1& component1) { // Lambda function contents here }); manager.callForMatchingFunctions(); // call all stored functions manager.clearForMatchingFunctions(); // remove all stored functions \endcode \return The index of the function, used for deletion with deleteForMatchingFunction() or filtering with clearSomeMatchingFunctions(). */ template unsigned long long addForMatchingFunction(Function&& function) { using SignatureComponents = typename EC::Meta::Matching::type; using Helper = EC::Meta::Morph >; Helper helper; BitsetType signatureBitset = BitsetType::template generateBitset(); forMatchingFunctions.emplace(std::make_pair(functionIndex, [function, signatureBitset, helper, this] () { for(std::size_t i = 0; i < this->currentSize; ++i) { if(!std::get(this->entities[i])) { continue; } if((signatureBitset & std::get(this->entities[i])) == signatureBitset) { helper.callInstance(i, *this, function); } } })); return functionIndex++; } /*! \brief Call all stored functions. Example: \code{.cpp} manager.addForMatchingFunction>([] (std::size_t ID, C0& component0, C1& component1) { // Lambda function contents here }); manager.callForMatchingFunctions(); // call all stored functions manager.clearForMatchingFunctions(); // remove all stored functions \endcode */ void callForMatchingFunctions() { for(auto functionIter = forMatchingFunctions.begin(); functionIter != forMatchingFunctions.end(); ++functionIter) { functionIter->second(); } } /*! \brief Call a specific stored function. Example: \code{.cpp} unsigned long long id = manager.addForMatchingFunction>( [] (std::size_t ID, C0& c0, C1& c1) { // Lambda function contents here }); manager.callForMatchingFunction(id); // call the previously added function \endcode \return False if a function with the given id does not exist. */ bool callForMatchingFunction(unsigned long long id) { auto iter = forMatchingFunctions.find(id); if(iter == forMatchingFunctions.end()) { return false; } iter->second(); return true; } /*! \brief Remove all stored functions. Also resets the index counter of stored functions to 0. Example: \code{.cpp} manager.addForMatchingFunction>([] (std::size_t ID, C0& component0, C1& component1) { // Lambda function contents here }); manager.callForMatchingFunctions(); // call all stored functions manager.clearForMatchingFunctions(); // remove all stored functions \endcode */ void clearForMatchingFunctions() { forMatchingFunctions.clear(); functionIndex = 0; } /*! \brief Removes all functions that do not have the index specified in argument "list". Note this function is slower than the variant that uses a set argument as all items in the List are traversed during traversal through all entities to check if they are in the list. Thus the complexity of this function is n^2. */ template void clearSomeMatchingFunctions(List list) { bool willErase; for(auto functionIter = forMatchingFunctions.begin(); functionIter != forMatchingFunctions.end(); ++functionIter) { willErase = true; for(auto listIter = list.begin(); listIter != list.end(); ++listIter) { if(functionIter->first == *listIter) { willErase = false; break; } } if(willErase) { functionIter = --(forMatchingFunctions.erase(functionIter)); } } } /*! \brief Removes all functions that do not have the index specified in argument "list". Note this function is slower than the variant that uses a set argument as all items in the List are traversed during traversal through all entities to check if they are in the list. Thus the complexity of this function is n^2. */ void clearSomeMatchingFunctions(std::initializer_list list) { clearSomeMatchingFunctions(list); } /*! \brief Removes a function that has the given id. \return True if a function was erased. */ bool removeForMatchingFunction(unsigned long long id) { return forMatchingFunctions.erase(id) == 1; } private: template void clearSomeMatchingFunctionsWithSet(Set set) { for(auto functionIter = forMatchingFunctions.begin(); functionIter != forMatchingFunctions.end(); ++functionIter) { if(set.find(functionIter->first) == set.end()) { functionIter = --(forMatchingFunctions.erase(functionIter)); } } } public: /*! \brief Removes all functions that do not have the index specified in argument "set". Note this function is faster than the variant that uses a list argument as the set's implementation as a tree allows for log(n) time checking of an index to the set. Thus the complexity of this function is n*log(n). */ void clearSomeMatchingFunctions(std::set set) { clearSomeMatchingFunctionsWithSet(set); } /*! \brief Removes all functions that do not have the index specified in argument "set". Note this function is faster than the variant that uses a list argument as the unordered_set's implementation as a hash table allows for constant time checking of an index to the set. Thus the complexity of this function is n. */ void clearSomeMatchingFunctions(std::unordered_set set) { clearSomeMatchingFunctionsWithSet(set); } /*! \brief Deletes the specified function. The index of a function is returned from addForMatchingFunction() so there is no other way to get the index of a function. */ void deleteForMatchingFunction(unsigned long long index) { forMatchingFunctions.erase(index); } /*! \brief Resets the Manager, removing all entities. Some data may persist but will be overwritten when new entities are added. Thus, do not depend on data to persist after a call to reset(). */ void reset() { clearForMatchingFunctions(); currentSize = 0; currentCapacity = 0; resize(EC_INIT_ENTITIES_SIZE); } }; } #endif