diff --git a/src/EC/Manager.hpp b/src/EC/Manager.hpp index 6c93f90..5215e14 100644 --- a/src/EC/Manager.hpp +++ b/src/EC/Manager.hpp @@ -3,1126 +3,1001 @@ // under the Academic Free License. // His code is available here: https://github.com/SuperV1234/cppcon2015 - #ifndef EC_MANAGER_HPP #define EC_MANAGER_HPP -#include #define EC_INIT_ENTITIES_SIZE 256 #define EC_GROW_SIZE_AMOUNT 256 +#include #include #include +#include #include -#include #include -#include -#include #include #include -#include -#include -#include -#include -#include #include +#include +#include +#include #include +#include +#include +#include +#include #ifndef NDEBUG - #include +#include #endif -#include "Meta/Combine.hpp" -#include "Meta/Matching.hpp" -#include "Meta/ForEachWithIndex.hpp" -#include "Meta/ForEachDoubleTuple.hpp" -#include "Meta/IndexOf.hpp" #include "Bitset.hpp" - +#include "Meta/Combine.hpp" +#include "Meta/ForEachDoubleTuple.hpp" +#include "Meta/ForEachWithIndex.hpp" +#include "Meta/IndexOf.hpp" +#include "Meta/Matching.hpp" #include "ThreadPool.hpp" -namespace EC -{ +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. + + An optional third template parameter may be given, which is the size of + the number of threads in the internal ThreadPool, and should be at + least 2. If ThreadCount is 1 or less, then the ThreadPool will not be + created and it will never be used, even if the "true" parameter is given + for functions that enable its usage. + + Note that when calling one of the "forMatching" functions that make use + of the internal ThreadPool, it is allowed to call addEntity() or + deleteEntity() as the functions cache which entities are alive before + running (allowing for addEntity()), and the functions defer deletions + during concurrent execution (allowing for deleteEntity()). + + Example: + \code{.cpp} + EC::Manager, TypeList> manager; + \endcode +*/ +template +struct Manager { + public: + using Components = ComponentsList; + using Tags = TagsList; + using Combined = EC::Meta::Combine; + using BitsetType = EC::Bitset; + + private: + using ComponentsTuple = EC::Meta::Morph >; + static_assert(std::is_default_constructible::value, + "All components must be default constructible"); + + template + struct Storage { + using type = std::tuple..., std::deque >; + }; + using ComponentsStorage = + typename EC::Meta::Morph >::type; + + // Entity: isAlive, ComponentsTags Info + using EntitiesTupleType = std::tuple; + using EntitiesType = std::deque; + + EntitiesType entities; + ComponentsStorage componentsStorage; + std::size_t currentCapacity = 0; + std::size_t currentSize = 0; + std::unordered_set deletedSet; + + std::unique_ptr > threadPool; + + std::atomic_uint deferringDeletions; + std::vector deferredDeletions; + std::mutex deferredDeletionsMutex; + + std::vector idStack; + std::size_t idStackCounter; + std::mutex idStackMutex; + + public: + // section for "temporary" structures {{{ + /// Temporary struct used internally by ThreadPool + struct TPFnDataStructZero { + std::array range; + Manager* manager; + EntitiesType* entities; + const BitsetType* signature; + void* userData; + std::unordered_set dead; + }; + /// Temporary struct used internally by ThreadPool + template + struct TPFnDataStructOne { + std::array range; + Manager* manager; + EntitiesType* entities; + BitsetType* signature; + void* userData; + Function* fn; + std::unordered_set dead; + }; + /// Temporary struct used internally by ThreadPool + struct TPFnDataStructTwo { + std::array range; + Manager* manager; + EntitiesType* entities; + void* userData; + const std::vector* matching; + std::unordered_set dead; + }; + /// Temporary struct used internally by ThreadPool + struct TPFnDataStructThree { + std::array range; + Manager* manager; + std::vector >* matchingV; + const std::vector* bitsets; + EntitiesType* entities; + std::mutex* mutex; + std::unordered_set dead; + }; + /// Temporary struct used internally by ThreadPool + struct TPFnDataStructFour { + std::array range; + Manager* manager; + std::vector >* multiMatchingEntities; + BitsetType* signatures; + std::mutex* mutex; + std::unordered_set dead; + }; + /// Temporary struct used internally by ThreadPool + struct TPFnDataStructFive { + std::array range; + std::size_t index; + Manager* manager; + void* userData; + std::vector >* multiMatchingEntities; + std::unordered_set dead; + }; + /// Temporary struct used internally by ThreadPool + struct TPFnDataStructSix { + std::array range; + Manager* manager; + std::vector >* multiMatchingEntities; + BitsetType* bitsets; + std::mutex* mutex; + std::unordered_set dead; + }; + /// Temporary struct used internally by ThreadPool + template + struct TPFnDataStructSeven { + std::array range; + Manager* manager; + EntitiesType* entities; + Iterable* iterable; + void* userData; + std::unordered_set dead; + }; + // end section for "temporary" structures }}} + /*! - \brief Manages an EntityComponent system. + \brief Initializes the manager with a default capacity. - EC::Manager must be created with a list of all used Components and all - used tags. + The default capacity is set with macro EC_INIT_ENTITIES_SIZE, + and will grow by amounts of EC_GROW_SIZE_AMOUNT when needed. + */ + Manager() : threadPool{}, idStackCounter(0) { + resize(EC_INIT_ENTITIES_SIZE); + if (ThreadCount >= 2) { + threadPool = std::make_unique >(); + } - Note that all components must have a default constructor. + deferringDeletions.store(0); + } - An optional third template parameter may be given, which is the size of - the number of threads in the internal ThreadPool, and should be at - least 2. If ThreadCount is 1 or less, then the ThreadPool will not be - created and it will never be used, even if the "true" parameter is given - for functions that enable its usage. + ~Manager() { + if (threadPool) { + while (!threadPool->isNotRunning()) { + std::this_thread::sleep_for(std::chrono::microseconds(30)); + } + } + } - Note that when calling one of the "forMatching" functions that make use - of the internal ThreadPool, it is allowed to call addEntity() or - deleteEntity() as the functions cache which entities are alive before - running (allowing for addEntity()), and the functions defer deletions - during concurrent execution (allowing for deleteEntity()). + 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, BitsetType{}); + } + + currentCapacity = newCapacity; + } + + public: + /*! + \brief Adds an entity to the system, returning the ID of the entity. + + Note: The ID of an entity is guaranteed to not change. + */ + std::size_t addEntity() { + if (deletedSet.empty()) { + if (currentSize == currentCapacity) { + resize(currentCapacity + EC_GROW_SIZE_AMOUNT); + } + + std::get(entities[currentSize]) = true; + + return currentSize++; + } else { + std::size_t id; + { + auto iter = deletedSet.begin(); + id = *iter; + deletedSet.erase(iter); + } + std::get(entities[id]) = true; + return id; + } + } + + private: + void deleteEntityImpl(std::size_t id) { + if (hasEntity(id)) { + std::get(entities.at(id)) = false; + std::get(entities.at(id)).reset(); + deletedSet.insert(id); + } + } + + public: + /*! + \brief Marks an entity for deletion. + + A deleted Entity's id is stored to be reclaimed later when + addEntity is called. Thus calling addEntity may return an id of + a previously deleted Entity. + */ + void deleteEntity(std::size_t index) { + if (deferringDeletions.load() != 0) { + std::lock_guard lock(deferredDeletionsMutex); + deferredDeletions.push_back(index); + } else { + deleteEntityImpl(index); + } + } + + private: + void handleDeferredDeletions() { + if (deferringDeletions.fetch_sub(1) == 1) { + std::lock_guard lock(deferredDeletionsMutex); + for (std::size_t id : deferredDeletions) { + deleteEntityImpl(id); + } + deferredDeletions.clear(); + } + } + + public: + /*! + \brief Checks if the Entity with the given ID is in the system. + + Note that deleted Entities are still considered in the system. + Consider using isAlive(). + */ + 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 the current size or number of entities in the system. + + Note this function will only count entities where isAlive() returns + true. + */ + std::size_t getCurrentSize() const { + return currentSize - deletedSet.size(); + } + + /* + \brief Returns the current capacity or number of entities the system + can hold. + + Note that when capacity is exceeded, the capacity is increased by + EC_GROW_SIZE_AMOUNT. + */ + std::size_t getCurrentCapacity() const { return currentCapacity; } + + /*! + \brief Returns a const reference to an Entity's info. + + An Entity's info is a std::tuple with a bool, and a + bitset. + + \n The bool determines if the Entity is alive. + \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 pointer to a component belonging to the given + Entity. + + This function will return a pointer to a Component regardless of + whether or not the Entity actually owns the Component. 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. + + If the given Component is unknown to the Manager, then this function + will return a nullptr. + */ + template + Component* getEntityData(const std::size_t& index) { + constexpr auto componentIndex = + EC::Meta::IndexOf::value; + if (componentIndex < Components::size) { + // Cast required due to compiler thinking that an invalid + // Component is needed even though the enclosing if statement + // prevents this from ever happening. + return (Component*)&std::get(componentsStorage) + .at(index); + } else { + return nullptr; + } + } + + /*! + \brief Returns a pointer to a component belonging to the given + Entity. + + Note that this function is the same as getEntityData(). + + This function will return a pointer to a Component regardless of + whether or not the Entity actually owns the Component. 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. + + If the given Component is unknown to the Manager, then this function + will return a nullptr. + */ + template + Component* getEntityComponent(const std::size_t& index) { + return getEntityData(index); + } + + /*! + \brief Returns a const pointer to a component belonging to the + given Entity. + + This function will return a const pointer to a Component + regardless of whether or not the Entity actually owns the Component. + 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. + + If the given Component is unknown to the Manager, then this function + will return a nullptr. + */ + template + const Component* getEntityData(const std::size_t& index) const { + constexpr auto componentIndex = + EC::Meta::IndexOf::value; + if (componentIndex < Components::size) { + // Cast required due to compiler thinking that an invalid + // Component is needed even though the enclosing if statement + // prevents this from ever happening. + return (Component*)&std::get(componentsStorage) + .at(index); + } else { + return nullptr; + } + } + + /*! + \brief Returns a const pointer to a component belonging to the + given Entity. + + Note that this function is the same as getEntityData() (const). + + This function will return a const pointer to a Component + regardless of whether or not the Entity actually owns the Component. + 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. + + If the given Component is unknown to the Manager, then this function + will return a nullptr. + */ + template + const Component* getEntityComponent(const std::size_t& index) const { + return getEntityData(index); + } + + /*! + \brief Checks whether or not the given Entity has the given + Component. Example: \code{.cpp} - EC::Manager, TypeList> manager; + manager.hasComponent(entityID); \endcode */ - template - struct Manager - { - public: - using Components = ComponentsList; - using Tags = TagsList; - using Combined = EC::Meta::Combine; - using BitsetType = EC::Bitset; + template + bool hasComponent(const std::size_t& index) const { + return std::get(entities.at(index)) + .template getComponentBit(); + } - private: - using ComponentsTuple = EC::Meta::Morph >; - static_assert(std::is_default_constructible::value, - "All components must be default constructible"); + /*! + \brief Checks whether or not the given Entity has the given Tag. - template - struct Storage - { - using type = std::tuple..., std::deque >; - }; - using ComponentsStorage = - typename EC::Meta::Morph >::type; + Example: + \code{.cpp} + manager.hasTag(entityID); + \endcode + */ + template + bool hasTag(const std::size_t& index) const { + return std::get(entities.at(index)) + .template getTagBit(); + } - // Entity: isAlive, ComponentsTags Info - using EntitiesTupleType = std::tuple; - using EntitiesType = std::deque; + /*! + \brief Adds a component to the given Entity. - EntitiesType entities; - ComponentsStorage componentsStorage; - std::size_t currentCapacity = 0; - std::size_t currentSize = 0; - std::unordered_set deletedSet; + Additional parameters given to this function will construct the + Component with those parameters. - std::unique_ptr > threadPool; + Note that if the Entity already has the same component, then it + will be overwritten by the newly created Component with the given + arguments. - std::atomic_uint deferringDeletions; - std::vector deferredDeletions; - std::mutex deferredDeletionsMutex; + If the Entity is not alive or the given Component is not known to + the Manager, then nothing will change. - std::vector idStack; - std::size_t idStackCounter; - std::mutex idStackMutex; + Example: + \code{.cpp} + struct C0 + { + // constructor is compatible as a default constructor + C0(int a = 0, char b = 'b') : + a(a), b(b) + {} - public: - // section for "temporary" structures {{{ - /// Temporary struct used internally by ThreadPool - struct TPFnDataStructZero { - std::array range; - Manager *manager; - EntitiesType *entities; - const BitsetType *signature; - void *userData; - std::unordered_set dead; - }; - /// Temporary struct used internally by ThreadPool - template - struct TPFnDataStructOne { - std::array range; - Manager *manager; - EntitiesType *entities; - BitsetType *signature; - void *userData; - Function *fn; - std::unordered_set dead; - }; - /// Temporary struct used internally by ThreadPool - struct TPFnDataStructTwo { - std::array range; - Manager *manager; - EntitiesType *entities; - void *userData; - const std::vector *matching; - std::unordered_set dead; - }; - /// Temporary struct used internally by ThreadPool - struct TPFnDataStructThree { - std::array range; - Manager *manager; - std::vector > *matchingV; - const std::vector *bitsets; - EntitiesType *entities; - std::mutex *mutex; - std::unordered_set dead; - }; - /// Temporary struct used internally by ThreadPool - struct TPFnDataStructFour { - std::array range; - Manager *manager; - std::vector >* - multiMatchingEntities; - BitsetType *signatures; - std::mutex *mutex; - std::unordered_set dead; - }; - /// Temporary struct used internally by ThreadPool - struct TPFnDataStructFive { - std::array range; - std::size_t index; - Manager *manager; - void *userData; - std::vector >* - multiMatchingEntities; - std::unordered_set dead; - }; - /// Temporary struct used internally by ThreadPool - struct TPFnDataStructSix { - std::array range; - Manager *manager; - std::vector > * - multiMatchingEntities; - BitsetType *bitsets; - std::mutex *mutex; - std::unordered_set dead; - }; - /// Temporary struct used internally by ThreadPool - template - struct TPFnDataStructSeven { - std::array range; - Manager *manager; - EntitiesType *entities; - Iterable *iterable; - void *userData; - std::unordered_set dead; - }; - // end section for "temporary" structures }}} - - /*! - \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() : - threadPool{}, - idStackCounter(0) - { - resize(EC_INIT_ENTITIES_SIZE); - if(ThreadCount >= 2) { - threadPool = std::make_unique >(); + int a; + char b; } - deferringDeletions.store(0); + manager.addComponent(entityID, 10, 'd'); + \endcode + */ + template + void addComponent(const std::size_t& entityID, Args&&... args) { + if (!EC::Meta::Contains::value || + !isAlive(entityID)) { + return; } - ~Manager() { - if (threadPool) { - while(!threadPool->isNotRunning()) { - std::this_thread::sleep_for(std::chrono::microseconds(30)); - } - } + Component component(std::forward(args)...); + + std::get(entities[entityID]) + .template getComponentBit() = true; + + constexpr auto index = EC::Meta::IndexOf::value; + + // Cast required due to compiler thinking that deque at + // index = Components::size is being used, even if the previous + // if statement will prevent this from ever happening. + (*((std::deque*)(&std::get( + componentsStorage))))[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 (!EC::Meta::Contains::value || + !isAlive(entityID)) { + return; } - private: - void resize(std::size_t newCapacity) - { - if(currentCapacity >= newCapacity) - { - return; - } + std::get(entities[entityID]) + .template getComponentBit() = false; + } - EC::Meta::forEach([this, newCapacity] (auto t) { - std::get >( - this->componentsStorage).resize(newCapacity); - }); + /*! + \brief Adds the given Tag to the given Entity. - entities.resize(newCapacity); - for(std::size_t i = currentCapacity; i < newCapacity; ++i) - { - entities[i] = std::make_tuple(false, BitsetType{}); - } - - currentCapacity = newCapacity; + Example: + \code{.cpp} + manager.addTag(entityID); + \endcode + */ + template + void addTag(const std::size_t& entityID) { + if (!EC::Meta::Contains::value || !isAlive(entityID)) { + return; } - public: - /*! - \brief Adds an entity to the system, returning the ID of the entity. + std::get(entities[entityID]).template getTagBit() = + true; + } - Note: The ID of an entity is guaranteed to not change. - */ - std::size_t addEntity() - { - if(deletedSet.empty()) + /*! + \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 (!EC::Meta::Contains::value || !isAlive(entityID)) { + return; + } + + std::get(entities[entityID]).template getTagBit() = + false; + } + + /*! + \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; + deletedSet.clear(); + resize(EC_INIT_ENTITIES_SIZE); + + std::lock_guard lock(deferredDeletionsMutex); + deferringDeletions.store(0); + deferredDeletions.clear(); + } + + private: + template + struct ForMatchingSignatureHelper { + template + static void call(const std::size_t& entityID, CType& ctype, + Function&& function, void* userData = nullptr) { + function(entityID, userData, + ctype.template getEntityData(entityID)...); + } + + template + static void callPtr(const std::size_t& entityID, CType& ctype, + Function* function, void* userData = nullptr) { + (*function)(entityID, userData, + ctype.template getEntityData(entityID)...); + } + + template + void callInstance(const std::size_t& entityID, CType& ctype, + Function&& function, void* userData = nullptr) const { + ForMatchingSignatureHelper::call( + entityID, ctype, std::forward(function), userData); + } + + template + void callInstancePtr(const std::size_t& entityID, CType& ctype, + Function* function, + void* userData = nullptr) const { + ForMatchingSignatureHelper::callPtr(entityID, ctype, + function, userData); + } + }; + + 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, void* as its second parameter, and Component + pointers 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. + + The second parameter is default nullptr and will be passed to the + function call as the second parameter as a means of providing + context (useful when the function is not a lambda function). + + The third parameter is default false (not multi-threaded). + Otherwise, if true, then the thread pool will be used to call the + given function in parallel across all entities. Note that + multi-threading is based on splitting the task of calling the + function across sections of entities. Thus if there are only a small + amount of entities in the manager, then using multiple threads may + not have as great of a speed-up. + + Example: + \code{.cpp} + Context c; // some class/struct with data + manager.forMatchingSignature>([] + (std::size_t ID, + void* context, + C0* component0, C1* component1) { - if(currentSize == currentCapacity) - { - resize(currentCapacity + EC_GROW_SIZE_AMOUNT); + // Lambda function contents here + }, + &c, // "Context" object passed to the function + true // enable use of internal ThreadPool + ); + \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, void* userData = nullptr, + const bool useThreadPool = false) { + std::size_t current_id; + { + // push to idStack "call stack" + std::lock_guard lock(idStackMutex); + current_id = idStackCounter++; + idStack.push_back(current_id); + } + deferringDeletions.fetch_add(1); + using SignatureComponents = + typename EC::Meta::Matching::type; + using Helper = + EC::Meta::Morph >; + + BitsetType signatureBitset = + BitsetType::template generateBitset(); + if (!useThreadPool || !threadPool) { + for (std::size_t i = 0; i < currentSize; ++i) { + if (!std::get(entities[i])) { + continue; } - std::get(entities[currentSize]) = true; - - return currentSize++; - } - else - { - std::size_t id; - { - auto iter = deletedSet.begin(); - id = *iter; - deletedSet.erase(iter); + if ((signatureBitset & std::get(entities[i])) == + signatureBitset) { + Helper::call(i, *this, std::forward(function), + userData); } - std::get(entities[id]) = true; - return id; } - } + } else { + std::array fnDataAr; - private: - void deleteEntityImpl(std::size_t id) { - if(hasEntity(id)) { - std::get(entities.at(id)) = false; - std::get(entities.at(id)).reset(); - deletedSet.insert(id); - } - } - - public: - /*! - \brief Marks an entity for deletion. - - A deleted Entity's id is stored to be reclaimed later when - addEntity is called. Thus calling addEntity may return an id of - a previously deleted Entity. - */ - void deleteEntity(std::size_t index) - { - if(deferringDeletions.load() != 0) { - std::lock_guard lock(deferredDeletionsMutex); - deferredDeletions.push_back(index); - } else { - deleteEntityImpl(index); - } - } - - private: - void handleDeferredDeletions() { - if(deferringDeletions.fetch_sub(1) == 1) { - std::lock_guard lock(deferredDeletionsMutex); - for(std::size_t id : deferredDeletions) { - deleteEntityImpl(id); + std::size_t s = currentSize / (ThreadCount * 2); + for (std::size_t i = 0; i < ThreadCount * 2; ++i) { + std::size_t begin = s * i; + std::size_t end; + if (i == ThreadCount * 2 - 1) { + end = currentSize; + } else { + end = s * (i + 1); } - deferredDeletions.clear(); - } - } - - public: - - /*! - \brief Checks if the Entity with the given ID is in the system. - - Note that deleted Entities are still considered in the system. - Consider using isAlive(). - */ - 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 the current size or number of entities in the system. - - Note this function will only count entities where isAlive() returns - true. - */ - std::size_t getCurrentSize() const - { - return currentSize - deletedSet.size(); - } - - /* - \brief Returns the current capacity or number of entities the system - can hold. - - Note that when capacity is exceeded, the capacity is increased by - EC_GROW_SIZE_AMOUNT. - */ - std::size_t getCurrentCapacity() const - { - return currentCapacity; - } - - /*! - \brief Returns a const reference to an Entity's info. - - An Entity's info is a std::tuple with a bool, and a - bitset. - - \n The bool determines if the Entity is alive. - \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 pointer to a component belonging to the given - Entity. - - This function will return a pointer to a Component regardless of - whether or not the Entity actually owns the Component. 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. - - If the given Component is unknown to the Manager, then this function - will return a nullptr. - */ - template - Component* getEntityData(const std::size_t& index) - { - constexpr auto componentIndex = EC::Meta::IndexOf< - Component, Components>::value; - if(componentIndex < Components::size) - { - // Cast required due to compiler thinking that an invalid - // Component is needed even though the enclosing if statement - // prevents this from ever happening. - return (Component*) &std::get( - componentsStorage).at(index); - } - else - { - return nullptr; - } - } - - /*! - \brief Returns a pointer to a component belonging to the given - Entity. - - Note that this function is the same as getEntityData(). - - This function will return a pointer to a Component regardless of - whether or not the Entity actually owns the Component. 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. - - If the given Component is unknown to the Manager, then this function - will return a nullptr. - */ - template - Component* getEntityComponent(const std::size_t& index) - { - return getEntityData(index); - } - - /*! - \brief Returns a const pointer to a component belonging to the - given Entity. - - This function will return a const pointer to a Component - regardless of whether or not the Entity actually owns the Component. - 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. - - If the given Component is unknown to the Manager, then this function - will return a nullptr. - */ - template - const Component* getEntityData(const std::size_t& index) const - { - constexpr auto componentIndex = EC::Meta::IndexOf< - Component, Components>::value; - if(componentIndex < Components::size) - { - // Cast required due to compiler thinking that an invalid - // Component is needed even though the enclosing if statement - // prevents this from ever happening. - return (Component*) &std::get( - componentsStorage).at(index); - } - else - { - return nullptr; - } - } - - /*! - \brief Returns a const pointer to a component belonging to the - given Entity. - - Note that this function is the same as getEntityData() (const). - - This function will return a const pointer to a Component - regardless of whether or not the Entity actually owns the Component. - 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. - - If the given Component is unknown to the Manager, then this function - will return a nullptr. - */ - template - const Component* getEntityComponent(const std::size_t& index) const - { - 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 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. - - If the Entity is not alive or the given Component is not known to - the Manager, then nothing will change. - - 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; + if (begin == end) { + continue; } - - manager.addComponent(entityID, 10, 'd'); - \endcode - */ - template - void addComponent(const std::size_t& entityID, Args&&... args) - { - if(!EC::Meta::Contains::value - || !isAlive(entityID)) - { - return; - } - - Component component(std::forward(args)...); - - std::get( - entities[entityID] - ).template getComponentBit() = true; - - constexpr auto index = - EC::Meta::IndexOf::value; - - // Cast required due to compiler thinking that deque at - // index = Components::size is being used, even if the previous - // if statement will prevent this from ever happening. - (*((std::deque*)(&std::get( - componentsStorage - ))))[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(!EC::Meta::Contains::value - || !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(!EC::Meta::Contains::value - || !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(!EC::Meta::Contains::value - || !isAlive(entityID)) - { - return; - } - - std::get( - entities[entityID] - ).template getTagBit() = false; - } - - /*! - \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; - deletedSet.clear(); - resize(EC_INIT_ENTITIES_SIZE); - - std::lock_guard lock(deferredDeletionsMutex); - deferringDeletions.store(0); - deferredDeletions.clear(); - } - - private: - template - struct ForMatchingSignatureHelper - { - template - static void call( - const std::size_t& entityID, - CType& ctype, - Function&& function, - void* userData = nullptr) - { - function( - entityID, - userData, - ctype.template getEntityData(entityID)... - ); - } - - template - static void callPtr( - const std::size_t& entityID, - CType& ctype, - Function* function, - void* userData = nullptr) - { - (*function)( - entityID, - userData, - ctype.template getEntityData(entityID)... - ); - } - - template - void callInstance( - const std::size_t& entityID, - CType& ctype, - Function&& function, - void* userData = nullptr) const - { - ForMatchingSignatureHelper::call( - entityID, - ctype, - std::forward(function), - userData); - } - - template - void callInstancePtr( - const std::size_t& entityID, - CType& ctype, - Function* function, - void* userData = nullptr) const - { - ForMatchingSignatureHelper::callPtr( - entityID, - ctype, - function, - userData); - } - }; - - 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, void* as its second parameter, and Component - pointers 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. - - The second parameter is default nullptr and will be passed to the - function call as the second parameter as a means of providing - context (useful when the function is not a lambda function). - - The third parameter is default false (not multi-threaded). - Otherwise, if true, then the thread pool will be used to call the - given function in parallel across all entities. Note that - multi-threading is based on splitting the task of calling the - function across sections of entities. Thus if there are only a small - amount of entities in the manager, then using multiple threads may - not have as great of a speed-up. - - Example: - \code{.cpp} - Context c; // some class/struct with data - manager.forMatchingSignature>([] - (std::size_t ID, - void* context, - C0* component0, C1* component1) - { - // Lambda function contents here - }, - &c, // "Context" object passed to the function - true // enable use of internal ThreadPool - ); - \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, - void* userData = nullptr, - const bool useThreadPool = false) - { - std::size_t current_id; - { - // push to idStack "call stack" - std::lock_guard lock(idStackMutex); - current_id = idStackCounter++; - idStack.push_back(current_id); - } - deferringDeletions.fetch_add(1); - using SignatureComponents = - typename EC::Meta::Matching::type; - using Helper = - EC::Meta::Morph< - SignatureComponents, - ForMatchingSignatureHelper<> >; - - BitsetType signatureBitset = - BitsetType::template generateBitset(); - if(!useThreadPool || !threadPool) - { - 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), userData); + fnDataAr[i].range = {begin, end}; + fnDataAr[i].manager = this; + fnDataAr[i].entities = &entities; + fnDataAr[i].signature = &signatureBitset; + fnDataAr[i].userData = userData; + for (std::size_t j = begin; j < end; ++j) { + if (!isAlive(j)) { + fnDataAr[i].dead.insert(j); } } - } - else - { - std::array fnDataAr; - std::size_t s = currentSize / (ThreadCount * 2); - for(std::size_t i = 0; i < ThreadCount * 2; ++i) { - std::size_t begin = s * i; - std::size_t end; - if(i == ThreadCount * 2 - 1) { - end = currentSize; - } else { - end = s * (i + 1); - } - if(begin == end) { - continue; - } - fnDataAr[i].range = {begin, end}; - fnDataAr[i].manager = this; - fnDataAr[i].entities = &entities; - fnDataAr[i].signature = &signatureBitset; - fnDataAr[i].userData = userData; - for(std::size_t j = begin; j < end; ++j) { - if(!isAlive(j)) { - fnDataAr[i].dead.insert(j); - } - } - - threadPool->queueFn([&function] (void *ud) { - auto *data = static_cast(ud); - for(std::size_t i = data->range[0]; i < data->range[1]; - ++i) { - if(data->dead.find(i) != data->dead.end()) { + threadPool->queueFn( + [&function](void* ud) { + auto* data = static_cast(ud); + for (std::size_t i = data->range[0]; i < data->range[1]; + ++i) { + if (data->dead.find(i) != data->dead.end()) { continue; } - if(((*data->signature) - & std::get( - data->entities->at(i))) - == *data->signature) { - Helper::call(i, - *data->manager, + if (((*data->signature) & + std::get(data->entities->at(i))) == + *data->signature) { + Helper::call(i, *data->manager, std::forward(function), data->userData); } } - }, &fnDataAr[i]); - } - threadPool->easyStartAndWait(); + }, + &fnDataAr[i]); } - - // pop from idStack "call stack" - do { - { - std::lock_guard lock(idStackMutex); - if (idStack.back() == current_id) { - idStack.pop_back(); - break; - } - } - std::this_thread::sleep_for(std::chrono::microseconds(15)); - } while (true); - - handleDeferredDeletions(); + threadPool->easyStartAndWait(); } - /*! - \brief Calls the given function on all Entities matching the given - Signature. - - The function pointer given to this function must accept std::size_t - as its first parameter, void* as its second parameter, and - Component pointers 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. - - The second parameter is default nullptr and will be passed to the - function call as the second parameter as a means of providing - context (useful when the function is not a lambda function). - - The third parameter is default false (not multi-threaded). - Otherwise, if true, then the thread pool will be used to call the - given function in parallel across all entities. Note that - multi-threading is based on splitting the task of calling the - function across sections of entities. Thus if there are only a small - amount of entities in the manager, then using multiple threads may - not have as great of a speed-up. - - Example: - \code{.cpp} - Context c; // some class/struct with data - auto function = [] - (std::size_t ID, - void* context, - C0* component0, C1* component1) - { - // Lambda function contents here - }; - manager.forMatchingSignaturePtr>( - &function, // ptr - &c, // "Context" object passed to the function - true // enable use of ThreadPool - ); - \endcode - Note, the ID given to the function is not permanent. An entity's ID - may change when cleanup() is called. - */ - template - void forMatchingSignaturePtr(Function* function, - void* userData = nullptr, - const bool useThreadPool = false) - { - std::size_t current_id; + // pop from idStack "call stack" + do { { - // push to idStack "call stack" std::lock_guard lock(idStackMutex); - current_id = idStackCounter++; - idStack.push_back(current_id); - } - deferringDeletions.fetch_add(1); - using SignatureComponents = - typename EC::Meta::Matching::type; - using Helper = - EC::Meta::Morph< - SignatureComponents, - ForMatchingSignatureHelper<> >; - - BitsetType signatureBitset = - BitsetType::template generateBitset(); - if(!useThreadPool || !threadPool) - { - for(std::size_t i = 0; i < currentSize; ++i) - { - if(!std::get(entities[i])) - { - continue; - } - - if((signatureBitset & std::get(entities[i])) - == signatureBitset) - { - Helper::callPtr(i, *this, function, userData); - } + if (idStack.back() == current_id) { + idStack.pop_back(); + break; } } - else - { - std::array, ThreadCount * 2> fnDataAr; + std::this_thread::sleep_for(std::chrono::microseconds(15)); + } while (true); - std::size_t s = currentSize / (ThreadCount * 2); - for(std::size_t i = 0; i < ThreadCount * 2; ++i) { - std::size_t begin = s * i; - std::size_t end; - if(i == ThreadCount * 2 - 1) { - end = currentSize; - } else { - end = s * (i + 1); + handleDeferredDeletions(); + } + + /*! + \brief Calls the given function on all Entities matching the given + Signature. + + The function pointer given to this function must accept std::size_t + as its first parameter, void* as its second parameter, and + Component pointers 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. + + The second parameter is default nullptr and will be passed to the + function call as the second parameter as a means of providing + context (useful when the function is not a lambda function). + + The third parameter is default false (not multi-threaded). + Otherwise, if true, then the thread pool will be used to call the + given function in parallel across all entities. Note that + multi-threading is based on splitting the task of calling the + function across sections of entities. Thus if there are only a small + amount of entities in the manager, then using multiple threads may + not have as great of a speed-up. + + Example: + \code{.cpp} + Context c; // some class/struct with data + auto function = [] + (std::size_t ID, + void* context, + C0* component0, C1* component1) + { + // Lambda function contents here + }; + manager.forMatchingSignaturePtr>( + &function, // ptr + &c, // "Context" object passed to the function + true // enable use of ThreadPool + ); + \endcode + Note, the ID given to the function is not permanent. An entity's ID + may change when cleanup() is called. + */ + template + void forMatchingSignaturePtr(Function* function, void* userData = nullptr, + const bool useThreadPool = false) { + std::size_t current_id; + { + // push to idStack "call stack" + std::lock_guard lock(idStackMutex); + current_id = idStackCounter++; + idStack.push_back(current_id); + } + deferringDeletions.fetch_add(1); + using SignatureComponents = + typename EC::Meta::Matching::type; + using Helper = + EC::Meta::Morph >; + + BitsetType signatureBitset = + BitsetType::template generateBitset(); + if (!useThreadPool || !threadPool) { + for (std::size_t i = 0; i < currentSize; ++i) { + if (!std::get(entities[i])) { + continue; + } + + if ((signatureBitset & std::get(entities[i])) == + signatureBitset) { + Helper::callPtr(i, *this, function, userData); + } + } + } else { + std::array, ThreadCount * 2> fnDataAr; + + std::size_t s = currentSize / (ThreadCount * 2); + for (std::size_t i = 0; i < ThreadCount * 2; ++i) { + std::size_t begin = s * i; + std::size_t end; + if (i == ThreadCount * 2 - 1) { + end = currentSize; + } else { + end = s * (i + 1); + } + if (begin == end) { + continue; + } + fnDataAr[i].range = {begin, end}; + fnDataAr[i].manager = this; + fnDataAr[i].entities = &entities; + fnDataAr[i].signature = &signatureBitset; + fnDataAr[i].userData = userData; + fnDataAr[i].fn = function; + for (std::size_t j = begin; j < end; ++j) { + if (!isAlive(j)) { + fnDataAr[i].dead.insert(j); } - if(begin == end) { - continue; - } - fnDataAr[i].range = {begin, end}; - fnDataAr[i].manager = this; - fnDataAr[i].entities = &entities; - fnDataAr[i].signature = &signatureBitset; - fnDataAr[i].userData = userData; - fnDataAr[i].fn = function; - for(std::size_t j = begin; j < end; ++j) { - if(!isAlive(j)) { - fnDataAr[i].dead.insert(j); - } - } - threadPool->queueFn([] (void *ud) { - auto *data = static_cast*>(ud); - for(std::size_t i = data->range[0]; i < data->range[1]; - ++i) { - if(data->dead.find(i) != data->dead.end()) { + } + threadPool->queueFn( + [](void* ud) { + auto* data = + static_cast*>(ud); + for (std::size_t i = data->range[0]; i < data->range[1]; + ++i) { + if (data->dead.find(i) != data->dead.end()) { continue; } - if(((*data->signature) - & std::get( - data->entities->at(i))) - == *data->signature) { - Helper::callPtr(i, - *data->manager, - data->fn, + if (((*data->signature) & + std::get(data->entities->at(i))) == + *data->signature) { + Helper::callPtr(i, *data->manager, data->fn, data->userData); } } - }, &fnDataAr[i]); - } - threadPool->easyStartAndWait(); + }, + &fnDataAr[i]); } - - // pop from idStack "call stack" - do { - { - std::lock_guard lock(idStackMutex); - if (idStack.back() == current_id) { - idStack.pop_back(); - break; - } - } - std::this_thread::sleep_for(std::chrono::microseconds(15)); - } while (true); - - handleDeferredDeletions(); + threadPool->easyStartAndWait(); } - - - private: - std::map, - void*)> > > - forMatchingFunctions; - std::size_t 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 if called by callForMatchingFunctions() unless the - internal functionIndex counter has wrapped around (is a - std::size_t). Calling clearForMatchingFunctions() will reset this - counter to zero. - - Note that the context pointer provided here (default nullptr) will - be provided to the stored function when called. - - Example: - \code{.cpp} - manager.addForMatchingFunction>([] - (std::size_t ID, - void* context, - C0* component0, C1* component1) - { - // Lambda function contents here - }); - - // call all stored functions - manager.callForMatchingFunctions(); - - // remove all stored functions - manager.clearForMatchingFunctions(); - \endcode - - \return The index of the function, used for deletion with - removeForMatchingFunction() or filtering with - keepSomeMatchingFunctions() or removeSomeMatchingFunctions(), - or calling with callForMatchingFunction(). - */ - template - std::size_t addForMatchingFunction( - Function&& function, - void* userData = nullptr) - { - deferringDeletions.fetch_add(1); - while(forMatchingFunctions.find(functionIndex) - != forMatchingFunctions.end()) + // pop from idStack "call stack" + do { { - ++functionIndex; + std::lock_guard lock(idStackMutex); + if (idStack.back() == current_id) { + idStack.pop_back(); + break; + } } + std::this_thread::sleep_for(std::chrono::microseconds(15)); + } while (true); - using SignatureComponents = - typename EC::Meta::Matching::type; - using Helper = - EC::Meta::Morph< - SignatureComponents, - ForMatchingSignatureHelper<> >; + handleDeferredDeletions(); + } - Helper helper; - BitsetType signatureBitset = - BitsetType::template generateBitset(); + private: + std::map, void*)> > > + forMatchingFunctions; + std::size_t functionIndex = 0; - forMatchingFunctions.emplace(std::make_pair( - functionIndex, - std::make_tuple( - signatureBitset, - userData, - [function, helper, this] - (const bool useThreadPool, - std::vector matching, - void* userData) - { - if(!useThreadPool || !threadPool) - { - for(auto eid : matching) - { - if(isAlive(eid)) - { - helper.callInstancePtr( - eid, *this, &function, userData); + 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 if called by callForMatchingFunctions() unless the + internal functionIndex counter has wrapped around (is a + std::size_t). Calling clearForMatchingFunctions() will reset this + counter to zero. + + Note that the context pointer provided here (default nullptr) will + be provided to the stored function when called. + + Example: + \code{.cpp} + manager.addForMatchingFunction>([] + (std::size_t ID, + void* context, + C0* component0, C1* component1) + { + // Lambda function contents here + }); + + // call all stored functions + manager.callForMatchingFunctions(); + + // remove all stored functions + manager.clearForMatchingFunctions(); + \endcode + + \return The index of the function, used for deletion with + removeForMatchingFunction() or filtering with + keepSomeMatchingFunctions() or removeSomeMatchingFunctions(), + or calling with callForMatchingFunction(). + */ + template + std::size_t addForMatchingFunction(Function&& function, + void* userData = nullptr) { + deferringDeletions.fetch_add(1); + while (forMatchingFunctions.find(functionIndex) != + forMatchingFunctions.end()) { + ++functionIndex; + } + + 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, + std::make_tuple( + signatureBitset, userData, + [function, helper, this](const bool useThreadPool, + std::vector matching, + void* userData) { + if (!useThreadPool || !threadPool) { + for (auto eid : matching) { + if (isAlive(eid)) { + helper.callInstancePtr(eid, *this, &function, + userData); } } - } - else - { + } else { std::array fnDataAr; std::size_t s = matching.size() / (ThreadCount * 2); - for(std::size_t i = 0; i < ThreadCount * 2; ++i) { + for (std::size_t i = 0; i < ThreadCount * 2; ++i) { std::size_t begin = s * i; std::size_t end; - if(i == ThreadCount * 2 - 1) { + if (i == ThreadCount * 2 - 1) { end = matching.size(); } else { end = s * (i + 1); } - if(begin == end) { + if (begin == end) { continue; } fnDataAr[i].range = {begin, end}; @@ -1130,720 +1005,452 @@ namespace EC fnDataAr[i].entities = &entities; fnDataAr[i].userData = userData; fnDataAr[i].matching = &matching; - for(std::size_t j = begin; j < end; ++j) { - if(!isAlive(matching.at(j))) { + for (std::size_t j = begin; j < end; ++j) { + if (!isAlive(matching.at(j))) { fnDataAr[i].dead.insert(j); } } - threadPool->queueFn([&function, helper] (void* ud) { - auto *data = static_cast(ud); - for(std::size_t i = data->range[0]; - i < data->range[1]; - ++i) { - if(data->dead.find(i) == data->dead.end()) { - helper.callInstancePtr( - data->matching->at(i), - *data->manager, - &function, - data->userData); + threadPool->queueFn( + [&function, helper](void* ud) { + auto* data = + static_cast(ud); + for (std::size_t i = data->range[0]; + i < data->range[1]; ++i) { + if (data->dead.find(i) == + data->dead.end()) { + helper.callInstancePtr( + data->matching->at(i), + *data->manager, &function, + data->userData); + } } - } - }, &fnDataAr[i]); + }, + &fnDataAr[i]); } threadPool->easyStartAndWait(); } }))); - handleDeferredDeletions(); - return functionIndex++; - } + handleDeferredDeletions(); + return functionIndex++; + } - private: - std::vector > getMatchingEntities( - std::vector bitsets, const bool useThreadPool = false) - { - std::vector > matchingV(bitsets.size()); + private: + std::vector > getMatchingEntities( + std::vector bitsets, const bool useThreadPool = false) { + std::vector > matchingV(bitsets.size()); - if(!useThreadPool || !threadPool) - { - for(std::size_t i = 0; i < currentSize; ++i) - { - if(!isAlive(i)) - { - continue; - } - for(std::size_t j = 0; j < bitsets.size(); ++j) - { - if(((*bitsets[j]) & std::get(entities[i])) - == (*bitsets[j])) - { - matchingV[j].push_back(i); - } + if (!useThreadPool || !threadPool) { + for (std::size_t i = 0; i < currentSize; ++i) { + if (!isAlive(i)) { + continue; + } + for (std::size_t j = 0; j < bitsets.size(); ++j) { + if (((*bitsets[j]) & std::get(entities[i])) == + (*bitsets[j])) { + matchingV[j].push_back(i); } } } - else - { - std::array fnDataAr; + } else { + std::array fnDataAr; - std::size_t s = currentSize / (ThreadCount * 2); - std::mutex mutex; - for(std::size_t i = 0; i < ThreadCount * 2; ++i) { - std::size_t begin = s * i; - std::size_t end; - if(i == ThreadCount * 2 - 1) { - end = currentSize; - } else { - end = s * (i + 1); + std::size_t s = currentSize / (ThreadCount * 2); + std::mutex mutex; + for (std::size_t i = 0; i < ThreadCount * 2; ++i) { + std::size_t begin = s * i; + std::size_t end; + if (i == ThreadCount * 2 - 1) { + end = currentSize; + } else { + end = s * (i + 1); + } + if (begin == end) { + continue; + } + fnDataAr[i].range = {begin, end}; + fnDataAr[i].manager = this; + fnDataAr[i].matchingV = &matchingV; + fnDataAr[i].bitsets = &bitsets; + fnDataAr[i].entities = &entities; + fnDataAr[i].mutex = &mutex; + for (std::size_t j = begin; j < end; ++j) { + if (!isAlive(j)) { + fnDataAr[i].dead.insert(j); } - if(begin == end) { - continue; - } - fnDataAr[i].range = {begin, end}; - fnDataAr[i].manager = this; - fnDataAr[i].matchingV = &matchingV; - fnDataAr[i].bitsets = &bitsets; - fnDataAr[i].entities = &entities; - fnDataAr[i].mutex = &mutex; - for(std::size_t j = begin; j < end; ++j) { - if(!isAlive(j)) { - fnDataAr[i].dead.insert(j); - } - } - threadPool->queueFn([] (void *ud) { - auto *data = static_cast(ud); - for(std::size_t i = data->range[0]; i < data->range[1]; - ++i) { - if(data->dead.find(i) != data->dead.end()) { + } + threadPool->queueFn( + [](void* ud) { + auto* data = static_cast(ud); + for (std::size_t i = data->range[0]; i < data->range[1]; + ++i) { + if (data->dead.find(i) != data->dead.end()) { continue; } - for(std::size_t j = 0; j < data->bitsets->size(); - ++j) { - if((*data->bitsets->at(j) - & std::get( - data->entities->at(i))) - == *data->bitsets->at(j)) { + for (std::size_t j = 0; j < data->bitsets->size(); + ++j) { + if ((*data->bitsets->at(j) & + std::get(data->entities->at( + i))) == *data->bitsets->at(j)) { std::lock_guard lock( - *data->mutex); + *data->mutex); data->matchingV->at(j).push_back(i); } } } - }, &fnDataAr[i]); - } - threadPool->easyStartAndWait(); + }, + &fnDataAr[i]); } - - return matchingV; + threadPool->easyStartAndWait(); } - public: + return matchingV; + } - /*! - \brief Call all stored functions. + public: + /*! + \brief Call all stored functions. - The first (and only) parameter can be optionally used to enable the - use of the internal ThreadPool to call all stored functions in - parallel. Using the value false (which is the default) will not use - the ThreadPool and run all stored functions sequentially on the main - thread. Note that multi-threading is based on splitting the task of - calling the functions across sections of entities. Thus if there are - only a small amount of entities in the manager, then using multiple - threads may not have as great of a speed-up. + The first (and only) parameter can be optionally used to enable the + use of the internal ThreadPool to call all stored functions in + parallel. Using the value false (which is the default) will not use + the ThreadPool and run all stored functions sequentially on the main + thread. Note that multi-threading is based on splitting the task of + calling the functions across sections of entities. Thus if there are + only a small amount of entities in the manager, then using multiple + threads may not have as great of a speed-up. - Example: - \code{.cpp} - manager.addForMatchingFunction>([] - (std::size_t ID, - void* context, - C0* component0, C1* component1) { - // Lambda function contents here - }); + Example: + \code{.cpp} + manager.addForMatchingFunction>([] + (std::size_t ID, + void* context, + C0* component0, C1* component1) { + // Lambda function contents here + }); - // call all stored functions - manager.callForMatchingFunctions(); + // call all stored functions + manager.callForMatchingFunctions(); - // call all stored functions with ThreadPool enabled - manager.callForMatchingFunctions(true); + // call all stored functions with ThreadPool enabled + manager.callForMatchingFunctions(true); - // remove all stored functions - manager.clearForMatchingFunctions(); - \endcode - */ - void callForMatchingFunctions(const bool useThreadPool = false) - { - deferringDeletions.fetch_add(1); - std::vector bitsets; - for(auto iter = forMatchingFunctions.begin(); - iter != forMatchingFunctions.end(); - ++iter) - { - bitsets.push_back(&std::get(iter->second)); - } - - std::vector > matching = - getMatchingEntities(bitsets, useThreadPool); - - std::size_t i = 0; - for(auto iter = forMatchingFunctions.begin(); - iter != forMatchingFunctions.end(); - ++iter) - { - std::get<2>(iter->second)( - useThreadPool, matching[i++], std::get<1>(iter->second)); - } - - handleDeferredDeletions(); + // remove all stored functions + manager.clearForMatchingFunctions(); + \endcode + */ + void callForMatchingFunctions(const bool useThreadPool = false) { + deferringDeletions.fetch_add(1); + std::vector bitsets; + for (auto iter = forMatchingFunctions.begin(); + iter != forMatchingFunctions.end(); ++iter) { + bitsets.push_back(&std::get(iter->second)); } - /*! - \brief Call a specific stored function. + std::vector > matching = + getMatchingEntities(bitsets, useThreadPool); - The second parameter can be optionally used to enable the use of the - internal ThreadPool to call the stored function in parallel. Using - the value false (which is the default) will not use the ThreadPool - and run the stored function sequentially on the main thread. Note - that multi-threading is based on splitting the task of calling the - functions across sections of entities. Thus if there are only a - small amount of entities in the manager, then using multiple threads - may not have as great of a speed-up. - - Example: - \code{.cpp} - std::size_t id = - manager.addForMatchingFunction>( - [] (std::size_t ID, void* context, C0* c0, C1* c1) { - // Lambda function contents here - }); - - // call the previously added function - manager.callForMatchingFunction(id); - - // call the previously added function with ThreadPool enabled - manager.callForMatchingFunction(id, true); - \endcode - - \return False if a function with the given id does not exist. - */ - bool callForMatchingFunction(std::size_t id, - const bool useThreadPool = false) - { - auto iter = forMatchingFunctions.find(id); - if(iter == forMatchingFunctions.end()) - { - return false; - } - deferringDeletions.fetch_add(1); - std::vector > matching = - getMatchingEntities(std::vector{ - &std::get(iter->second)}, useThreadPool); - std::get<2>(iter->second)( - useThreadPool, matching[0], std::get<1>(iter->second)); - - handleDeferredDeletions(); - return true; + std::size_t i = 0; + for (auto iter = forMatchingFunctions.begin(); + iter != forMatchingFunctions.end(); ++iter) { + std::get<2>(iter->second)(useThreadPool, matching[i++], + std::get<1>(iter->second)); } - /*! - \brief Remove all stored functions. + handleDeferredDeletions(); + } - Also resets the index counter of stored functions to 0. + /*! + \brief Call a specific stored function. - Example: - \code{.cpp} - manager.addForMatchingFunction>([] - (std::size_t ID, - void* context, - C0* component0, C1* component1) - { - // Lambda function contents here - }); + The second parameter can be optionally used to enable the use of the + internal ThreadPool to call the stored function in parallel. Using + the value false (which is the default) will not use the ThreadPool + and run the stored function sequentially on the main thread. Note + that multi-threading is based on splitting the task of calling the + functions across sections of entities. Thus if there are only a + small amount of entities in the manager, then using multiple threads + may not have as great of a speed-up. - // call all stored functions - manager.callForMatchingFunctions(); + Example: + \code{.cpp} + std::size_t id = + manager.addForMatchingFunction>( + [] (std::size_t ID, void* context, C0* c0, C1* c1) { + // Lambda function contents here + }); - // remove all stored functions - manager.clearForMatchingFunctions(); - \endcode - */ - void clearForMatchingFunctions() - { - forMatchingFunctions.clear(); - functionIndex = 0; - } + // call the previously added function + manager.callForMatchingFunction(id); - /*! - \brief Removes a function that has the given id. + // call the previously added function with ThreadPool enabled + manager.callForMatchingFunction(id, true); + \endcode - \return True if a function was erased. - */ - bool removeForMatchingFunction(std::size_t id) - { - return forMatchingFunctions.erase(id) == 1; - } - - /*! - \brief Removes all functions that do not have the index specified - in argument "list". - - The given List must be iterable. - This is the only requirement, so a set could also be given. - - \return The number of functions deleted. - */ - template - std::size_t keepSomeMatchingFunctions(List list) - { - std::size_t deletedCount = 0; - for(auto iter = forMatchingFunctions.begin(); - iter != forMatchingFunctions.end();) - { - if(std::find(list.begin(), list.end(), iter->first) - == list.end()) - { - iter = forMatchingFunctions.erase(iter); - ++deletedCount; - } - else - { - ++iter; - } - } - - return deletedCount; - } - - /*! - \brief Removes all functions that do not have the index specified - in argument "list". - - This function allows for passing an initializer list. - - \return The number of functions deleted. - */ - std::size_t keepSomeMatchingFunctions( - std::initializer_list list) - { - return keepSomeMatchingFunctions(list); - } - - /*! - \brief Removes all functions that do have the index specified - in argument "list". - - The given List must be iterable. - This is the only requirement, so a set could also be given. - - \return The number of functions deleted. - */ - template - std::size_t removeSomeMatchingFunctions(List list) - { - std::size_t deletedCount = 0; - for(auto listIter = list.begin(); - listIter != list.end(); - ++listIter) - { - deletedCount += forMatchingFunctions.erase(*listIter); - } - - return deletedCount; - } - - /*! - \brief Removes all functions that do have the index specified - in argument "list". - - This function allows for passing an initializer list. - - \return The number of functions deleted. - */ - std::size_t removeSomeMatchingFunctions( - std::initializer_list list) - { - return removeSomeMatchingFunctions(list); - } - - /*! - \brief Sets the context pointer of a stored function - - \return True if id is valid and context was updated - */ - bool changeForMatchingFunctionContext(std::size_t id, void* userData) - { - auto f = forMatchingFunctions.find(id); - if(f != forMatchingFunctions.end()) - { - std::get<1>(f->second) = userData; - return true; - } + \return False if a function with the given id does not exist. + */ + bool callForMatchingFunction(std::size_t id, + const bool useThreadPool = false) { + auto iter = forMatchingFunctions.find(id); + if (iter == forMatchingFunctions.end()) { return false; } + deferringDeletions.fetch_add(1); + std::vector > matching = getMatchingEntities( + std::vector{&std::get(iter->second)}, + useThreadPool); + std::get<2>(iter->second)(useThreadPool, matching[0], + std::get<1>(iter->second)); - /*! - \brief Call multiple functions with mulitple signatures on all - living entities. + handleDeferredDeletions(); + return true; + } - (Living entities as in entities that have not been marked for - deletion.) + /*! + \brief Remove all stored functions. - This function requires the first template parameter to be a - EC::Meta::TypeList of signatures. Note that a signature is a - EC::Meta::TypeList of components and tags, meaning that SigList - is a TypeList of TypeLists. + Also resets the index counter of stored functions to 0. - The second template parameter can be inferred from the function - parameter which should be a tuple of functions. The function - at any index in the tuple should match with a signature of the - same index in the SigList. Behavior is undefined if there are - less functions than signatures. - - See the Unit Test of this function in src/test/ECTest.cpp for - usage examples. - - The second parameter (default nullptr) will be provided to every - function call as a void* (context). - - The third parameter is default false (not multi-threaded). - Otherwise, if true, then the thread pool will be used to call the - given function in parallel across all entities. Note that - multi-threading is based on splitting the task of calling the - function across sections of entities. Thus if there are only a small - amount of entities in the manager, then using multiple threads may - not have as great of a speed-up. - - This function was created for the use case where there are many - entities in the system which can cause multiple calls to - forMatchingSignature to be slow due to the overhead of iterating - through the entire list of entities on each invocation. - This function instead iterates through all entities once, - storing matching entities in a vector of vectors (for each - signature and function pair) and then calling functions with - the matching list of entities. - - Note that multi-threaded or not, functions will be called in order - of signatures. The first function signature pair will be called - first, then the second, third, and so on. - If this function is called with more than 1 thread specified, then - the order of entities called is not guaranteed. Otherwise entities - will be called in consecutive order by their ID. - */ - template - void forMatchingSignatures( - FTuple fTuple, - void* userData = nullptr, - const bool useThreadPool = false) - { - std::size_t current_id; + Example: + \code{.cpp} + manager.addForMatchingFunction>([] + (std::size_t ID, + void* context, + C0* component0, C1* component1) { - // push to idStack "call stack" - std::lock_guard lock(idStackMutex); - current_id = idStackCounter++; - idStack.push_back(current_id); - } - deferringDeletions.fetch_add(1); - std::vector > - multiMatchingEntities(SigList::size); - BitsetType signatureBitsets[SigList::size]; - - // generate bitsets for each signature - EC::Meta::forEachWithIndex( - [&signatureBitsets] (auto signature, const auto index) { - signatureBitsets[index] = - BitsetType::template generateBitset - (); + // Lambda function contents here }); - // find and store entities matching signatures - if(!useThreadPool || !threadPool) - { - for(std::size_t eid = 0; eid < currentSize; ++eid) - { - if(!isAlive(eid)) - { - continue; - } - for(std::size_t i = 0; i < SigList::size; ++i) - { - if((signatureBitsets[i] - & std::get(entities[eid])) - == signatureBitsets[i]) - { - multiMatchingEntities[i].push_back(eid); - } - } - } + // call all stored functions + manager.callForMatchingFunctions(); + + // remove all stored functions + manager.clearForMatchingFunctions(); + \endcode + */ + void clearForMatchingFunctions() { + forMatchingFunctions.clear(); + functionIndex = 0; + } + + /*! + \brief Removes a function that has the given id. + + \return True if a function was erased. + */ + bool removeForMatchingFunction(std::size_t id) { + return forMatchingFunctions.erase(id) == 1; + } + + /*! + \brief Removes all functions that do not have the index specified + in argument "list". + + The given List must be iterable. + This is the only requirement, so a set could also be given. + + \return The number of functions deleted. + */ + template + std::size_t keepSomeMatchingFunctions(List list) { + std::size_t deletedCount = 0; + for (auto iter = forMatchingFunctions.begin(); + iter != forMatchingFunctions.end();) { + if (std::find(list.begin(), list.end(), iter->first) == + list.end()) { + iter = forMatchingFunctions.erase(iter); + ++deletedCount; + } else { + ++iter; } - else - { - std::array fnDataAr; - - std::mutex mutex; - std::size_t s = currentSize / (ThreadCount * 2); - for(std::size_t i = 0; i < ThreadCount * 2; ++i) { - std::size_t begin = s * i; - std::size_t end; - if(i == ThreadCount * 2 - 1) { - end = currentSize; - } else { - end = s * (i + 1); - } - if(begin == end) { - continue; - } - fnDataAr[i].range = {begin, end}; - fnDataAr[i].manager = this; - fnDataAr[i].multiMatchingEntities = &multiMatchingEntities; - fnDataAr[i].signatures = signatureBitsets; - fnDataAr[i].mutex = &mutex; - for(std::size_t j = begin; j < end; ++j) { - if(!isAlive(j)) { - fnDataAr[i].dead.insert(j); - } - } - - threadPool->queueFn([] (void *ud) { - auto *data = static_cast(ud); - for(std::size_t i = data->range[0]; i < data->range[1]; - ++i) { - if(data->dead.find(i) != data->dead.end()) { - continue; - } - for(std::size_t j = 0; j < SigList::size; ++j) { - if((data->signatures[j] - & std::get( - data->manager->entities[i])) - == data->signatures[j]) { - std::lock_guard lock( - *data->mutex); - data->multiMatchingEntities->at(j) - .push_back(i); - } - } - } - }, &fnDataAr[i]); - } - threadPool->easyStartAndWait(); - } - - // call functions on matching entities - EC::Meta::forEachDoubleTuple( - EC::Meta::Morph >{}, - fTuple, - [this, &multiMatchingEntities, useThreadPool, &userData] - (auto sig, auto func, auto index) - { - using SignatureComponents = - typename EC::Meta::Matching< - decltype(sig), ComponentsList>::type; - using Helper = - EC::Meta::Morph< - SignatureComponents, - ForMatchingSignatureHelper<> >; - if(!useThreadPool || !threadPool) { - for(const auto& id : multiMatchingEntities[index]) { - if(isAlive(id)) { - Helper::call(id, *this, func, userData); - } - } - } else { - std::array - fnDataAr; - std::size_t s = multiMatchingEntities[index].size() - / (ThreadCount * 2); - for(unsigned int i = 0; i < ThreadCount * 2; ++i) { - std::size_t begin = s * i; - std::size_t end; - if(i == ThreadCount * 2 - 1) { - end = multiMatchingEntities[index].size(); - } else { - end = s * (i + 1); - } - if(begin == end) { - continue; - } - fnDataAr[i].range = {begin, end}; - fnDataAr[i].index = index; - fnDataAr[i].manager = this; - fnDataAr[i].userData = userData; - fnDataAr[i].multiMatchingEntities = - &multiMatchingEntities; - for(std::size_t j = begin; j < end; ++j) { - if(!isAlive(multiMatchingEntities.at(index).at(j))) { - fnDataAr[i].dead.insert(j); - } - } - threadPool->queueFn([&func] (void *ud) { - auto *data = static_cast(ud); - for(std::size_t i = data->range[0]; - i < data->range[1]; ++i) { - if(data->dead.find(i) == data->dead.end()) { - Helper::call( - data->multiMatchingEntities - ->at(data->index).at(i), - *data->manager, - func, - data->userData); - } - } - }, &fnDataAr[i]); - } - threadPool->easyStartAndWait(); - } - } - ); - - // pop from idStack "call stack" - do { - { - std::lock_guard lock(idStackMutex); - if (idStack.back() == current_id) { - idStack.pop_back(); - break; - } - } - std::this_thread::sleep_for(std::chrono::microseconds(15)); - } while (true); - - handleDeferredDeletions(); } - /*! - \brief Call multiple functions with mulitple signatures on all - living entities. + return deletedCount; + } - (Living entities as in entities that have not been marked for - deletion.) + /*! + \brief Removes all functions that do not have the index specified + in argument "list". - Note that this function requires the tuple of functions to hold - pointers to functions, not just functions. + This function allows for passing an initializer list. - This function requires the first template parameter to be a - EC::Meta::TypeList of signatures. Note that a signature is a - EC::Meta::TypeList of components and tags, meaning that SigList - is a TypeList of TypeLists. + \return The number of functions deleted. + */ + std::size_t keepSomeMatchingFunctions( + std::initializer_list list) { + return keepSomeMatchingFunctions(list); + } - The second template parameter can be inferred from the function - parameter which should be a tuple of functions. The function - at any index in the tuple should match with a signature of the - same index in the SigList. Behavior is undefined if there are - less functions than signatures. + /*! + \brief Removes all functions that do have the index specified + in argument "list". - See the Unit Test of this function in src/test/ECTest.cpp for - usage examples. + The given List must be iterable. + This is the only requirement, so a set could also be given. - The second parameter (default nullptr) will be provided to every - function call as a void* (context). + \return The number of functions deleted. + */ + template + std::size_t removeSomeMatchingFunctions(List list) { + std::size_t deletedCount = 0; + for (auto listIter = list.begin(); listIter != list.end(); ++listIter) { + deletedCount += forMatchingFunctions.erase(*listIter); + } - The third parameter is default false (not multi-threaded). - Otherwise, if true, then the thread pool will be used to call the - given function in parallel across all entities. Note that - multi-threading is based on splitting the task of calling the - function across sections of entities. Thus if there are only a small - amount of entities in the manager, then using multiple threads may - not have as great of a speed-up. + return deletedCount; + } - This function was created for the use case where there are many - entities in the system which can cause multiple calls to - forMatchingSignature to be slow due to the overhead of iterating - through the entire list of entities on each invocation. - This function instead iterates through all entities once, - storing matching entities in a vector of vectors (for each - signature and function pair) and then calling functions with - the matching list of entities. + /*! + \brief Removes all functions that do have the index specified + in argument "list". - Note that multi-threaded or not, functions will be called in order - of signatures. The first function signature pair will be called - first, then the second, third, and so on. - If this function is called with more than 1 thread specified, then - the order of entities called is not guaranteed. Otherwise entities - will be called in consecutive order by their ID. - */ - template - void forMatchingSignaturesPtr(FTuple fTuple, - void* userData = nullptr, - const bool useThreadPool = false) + This function allows for passing an initializer list. + + \return The number of functions deleted. + */ + std::size_t removeSomeMatchingFunctions( + std::initializer_list list) { + return removeSomeMatchingFunctions(list); + } + + /*! + \brief Sets the context pointer of a stored function + + \return True if id is valid and context was updated + */ + bool changeForMatchingFunctionContext(std::size_t id, void* userData) { + auto f = forMatchingFunctions.find(id); + if (f != forMatchingFunctions.end()) { + std::get<1>(f->second) = userData; + return true; + } + return false; + } + + /*! + \brief Call multiple functions with mulitple signatures on all + living entities. + + (Living entities as in entities that have not been marked for + deletion.) + + This function requires the first template parameter to be a + EC::Meta::TypeList of signatures. Note that a signature is a + EC::Meta::TypeList of components and tags, meaning that SigList + is a TypeList of TypeLists. + + The second template parameter can be inferred from the function + parameter which should be a tuple of functions. The function + at any index in the tuple should match with a signature of the + same index in the SigList. Behavior is undefined if there are + less functions than signatures. + + See the Unit Test of this function in src/test/ECTest.cpp for + usage examples. + + The second parameter (default nullptr) will be provided to every + function call as a void* (context). + + The third parameter is default false (not multi-threaded). + Otherwise, if true, then the thread pool will be used to call the + given function in parallel across all entities. Note that + multi-threading is based on splitting the task of calling the + function across sections of entities. Thus if there are only a small + amount of entities in the manager, then using multiple threads may + not have as great of a speed-up. + + This function was created for the use case where there are many + entities in the system which can cause multiple calls to + forMatchingSignature to be slow due to the overhead of iterating + through the entire list of entities on each invocation. + This function instead iterates through all entities once, + storing matching entities in a vector of vectors (for each + signature and function pair) and then calling functions with + the matching list of entities. + + Note that multi-threaded or not, functions will be called in order + of signatures. The first function signature pair will be called + first, then the second, third, and so on. + If this function is called with more than 1 thread specified, then + the order of entities called is not guaranteed. Otherwise entities + will be called in consecutive order by their ID. + */ + template + void forMatchingSignatures(FTuple fTuple, void* userData = nullptr, + const bool useThreadPool = false) { + std::size_t current_id; { - std::size_t current_id; - { - // push to idStack "call stack" - std::lock_guard lock(idStackMutex); - current_id = idStackCounter++; - idStack.push_back(current_id); - } - deferringDeletions.fetch_add(1); - std::vector > multiMatchingEntities( - SigList::size); - BitsetType signatureBitsets[SigList::size]; + // push to idStack "call stack" + std::lock_guard lock(idStackMutex); + current_id = idStackCounter++; + idStack.push_back(current_id); + } + deferringDeletions.fetch_add(1); + std::vector > multiMatchingEntities( + SigList::size); + BitsetType signatureBitsets[SigList::size]; - // generate bitsets for each signature - EC::Meta::forEachWithIndex( - [&signatureBitsets] (auto signature, const auto index) { + // generate bitsets for each signature + EC::Meta::forEachWithIndex( + [&signatureBitsets](auto signature, const auto index) { signatureBitsets[index] = - BitsetType::template generateBitset - (); + BitsetType::template generateBitset(); }); - // find and store entities matching signatures - if(!useThreadPool || !threadPool) - { - for(std::size_t eid = 0; eid < currentSize; ++eid) - { - if(!isAlive(eid)) - { - continue; - } - for(std::size_t i = 0; i < SigList::size; ++i) - { - if((signatureBitsets[i] - & std::get(entities[eid])) - == signatureBitsets[i]) - { - multiMatchingEntities[i].push_back(eid); - } + // find and store entities matching signatures + if (!useThreadPool || !threadPool) { + for (std::size_t eid = 0; eid < currentSize; ++eid) { + if (!isAlive(eid)) { + continue; + } + for (std::size_t i = 0; i < SigList::size; ++i) { + if ((signatureBitsets[i] & + std::get(entities[eid])) == + signatureBitsets[i]) { + multiMatchingEntities[i].push_back(eid); } } } - else - { - std::array fnDataAr; + } else { + std::array fnDataAr; - std::mutex mutex; - std::size_t s = currentSize / (ThreadCount * 2); - for(std::size_t i = 0; i < ThreadCount * 2; ++i) { - std::size_t begin = s * i; - std::size_t end; - if(i == ThreadCount * 2 - 1) { - end = currentSize; - } else { - end = s * (i + 1); - } - if(begin == end) { - continue; - } - fnDataAr[i].range = {begin, end}; - fnDataAr[i].manager = this; - fnDataAr[i].multiMatchingEntities = &multiMatchingEntities; - fnDataAr[i].bitsets = signatureBitsets; - fnDataAr[i].mutex = &mutex; - for(std::size_t j = begin; j < end; ++j) { - if(!isAlive(j)) { - fnDataAr[i].dead.insert(j); - } + std::mutex mutex; + std::size_t s = currentSize / (ThreadCount * 2); + for (std::size_t i = 0; i < ThreadCount * 2; ++i) { + std::size_t begin = s * i; + std::size_t end; + if (i == ThreadCount * 2 - 1) { + end = currentSize; + } else { + end = s * (i + 1); + } + if (begin == end) { + continue; + } + fnDataAr[i].range = {begin, end}; + fnDataAr[i].manager = this; + fnDataAr[i].multiMatchingEntities = &multiMatchingEntities; + fnDataAr[i].signatures = signatureBitsets; + fnDataAr[i].mutex = &mutex; + for (std::size_t j = begin; j < end; ++j) { + if (!isAlive(j)) { + fnDataAr[i].dead.insert(j); } + } - threadPool->queueFn([] (void *ud) { - auto *data = static_cast(ud); - for(std::size_t i = data->range[0]; i < data->range[1]; - ++i) { - if(data->dead.find(i) != data->dead.end()) { + threadPool->queueFn( + [](void* ud) { + auto* data = static_cast(ud); + for (std::size_t i = data->range[0]; i < data->range[1]; + ++i) { + if (data->dead.find(i) != data->dead.end()) { continue; } - for(std::size_t j = 0; j < SigList::size; ++j) { - if((data->bitsets[j] - & std::get( - data->manager->entities[i])) - == data->bitsets[j]) { + for (std::size_t j = 0; j < SigList::size; ++j) { + if ((data->signatures[j] & + std::get( + data->manager->entities[i])) == + data->signatures[j]) { std::lock_guard lock( *data->mutex); data->multiMatchingEntities->at(j) @@ -1851,319 +1458,538 @@ namespace EC } } } - }, &fnDataAr[i]); - } - threadPool->easyStartAndWait(); + }, + &fnDataAr[i]); } + threadPool->easyStartAndWait(); + } - // call functions on matching entities - EC::Meta::forEachDoubleTuple( - EC::Meta::Morph >{}, - fTuple, - [this, &multiMatchingEntities, useThreadPool, &userData] - (auto sig, auto func, auto index) - { - using SignatureComponents = - typename EC::Meta::Matching< - decltype(sig), ComponentsList>::type; - using Helper = - EC::Meta::Morph< - SignatureComponents, - ForMatchingSignatureHelper<> >; - if(!useThreadPool || !threadPool) - { - for(const auto& id : multiMatchingEntities[index]) - { - if(isAlive(id)) - { - Helper::callPtr(id, *this, func, userData); - } + // call functions on matching entities + EC::Meta::forEachDoubleTuple( + EC::Meta::Morph >{}, fTuple, + [this, &multiMatchingEntities, useThreadPool, &userData]( + auto sig, auto func, auto index) { + using SignatureComponents = + typename EC::Meta::Matching::type; + using Helper = EC::Meta::Morph >; + if (!useThreadPool || !threadPool) { + for (const auto& id : multiMatchingEntities[index]) { + if (isAlive(id)) { + Helper::call(id, *this, func, userData); } } - else - { - std::array - fnDataAr; - std::size_t s = multiMatchingEntities[index].size() - / (ThreadCount * 2); - for(unsigned int i = 0; i < ThreadCount * 2; ++i) { - std::size_t begin = s * i; - std::size_t end; - if(i == ThreadCount * 2 - 1) { - end = multiMatchingEntities[index].size(); - } else { - end = s * (i + 1); + } else { + std::array fnDataAr; + std::size_t s = + multiMatchingEntities[index].size() / (ThreadCount * 2); + for (unsigned int i = 0; i < ThreadCount * 2; ++i) { + std::size_t begin = s * i; + std::size_t end; + if (i == ThreadCount * 2 - 1) { + end = multiMatchingEntities[index].size(); + } else { + end = s * (i + 1); + } + if (begin == end) { + continue; + } + fnDataAr[i].range = {begin, end}; + fnDataAr[i].index = index; + fnDataAr[i].manager = this; + fnDataAr[i].userData = userData; + fnDataAr[i].multiMatchingEntities = + &multiMatchingEntities; + for (std::size_t j = begin; j < end; ++j) { + if (!isAlive( + multiMatchingEntities.at(index).at(j))) { + fnDataAr[i].dead.insert(j); } - if(begin == end) { + } + threadPool->queueFn( + [&func](void* ud) { + auto* data = + static_cast(ud); + for (std::size_t i = data->range[0]; + i < data->range[1]; ++i) { + if (data->dead.find(i) == + data->dead.end()) { + Helper::call(data->multiMatchingEntities + ->at(data->index) + .at(i), + *data->manager, func, + data->userData); + } + } + }, + &fnDataAr[i]); + } + threadPool->easyStartAndWait(); + } + }); + + // pop from idStack "call stack" + do { + { + std::lock_guard lock(idStackMutex); + if (idStack.back() == current_id) { + idStack.pop_back(); + break; + } + } + std::this_thread::sleep_for(std::chrono::microseconds(15)); + } while (true); + + handleDeferredDeletions(); + } + + /*! + \brief Call multiple functions with mulitple signatures on all + living entities. + + (Living entities as in entities that have not been marked for + deletion.) + + Note that this function requires the tuple of functions to hold + pointers to functions, not just functions. + + This function requires the first template parameter to be a + EC::Meta::TypeList of signatures. Note that a signature is a + EC::Meta::TypeList of components and tags, meaning that SigList + is a TypeList of TypeLists. + + The second template parameter can be inferred from the function + parameter which should be a tuple of functions. The function + at any index in the tuple should match with a signature of the + same index in the SigList. Behavior is undefined if there are + less functions than signatures. + + See the Unit Test of this function in src/test/ECTest.cpp for + usage examples. + + The second parameter (default nullptr) will be provided to every + function call as a void* (context). + + The third parameter is default false (not multi-threaded). + Otherwise, if true, then the thread pool will be used to call the + given function in parallel across all entities. Note that + multi-threading is based on splitting the task of calling the + function across sections of entities. Thus if there are only a small + amount of entities in the manager, then using multiple threads may + not have as great of a speed-up. + + This function was created for the use case where there are many + entities in the system which can cause multiple calls to + forMatchingSignature to be slow due to the overhead of iterating + through the entire list of entities on each invocation. + This function instead iterates through all entities once, + storing matching entities in a vector of vectors (for each + signature and function pair) and then calling functions with + the matching list of entities. + + Note that multi-threaded or not, functions will be called in order + of signatures. The first function signature pair will be called + first, then the second, third, and so on. + If this function is called with more than 1 thread specified, then + the order of entities called is not guaranteed. Otherwise entities + will be called in consecutive order by their ID. + */ + template + void forMatchingSignaturesPtr(FTuple fTuple, void* userData = nullptr, + const bool useThreadPool = false) { + std::size_t current_id; + { + // push to idStack "call stack" + std::lock_guard lock(idStackMutex); + current_id = idStackCounter++; + idStack.push_back(current_id); + } + deferringDeletions.fetch_add(1); + std::vector > multiMatchingEntities( + SigList::size); + BitsetType signatureBitsets[SigList::size]; + + // generate bitsets for each signature + EC::Meta::forEachWithIndex( + [&signatureBitsets](auto signature, const auto index) { + signatureBitsets[index] = + BitsetType::template generateBitset(); + }); + + // find and store entities matching signatures + if (!useThreadPool || !threadPool) { + for (std::size_t eid = 0; eid < currentSize; ++eid) { + if (!isAlive(eid)) { + continue; + } + for (std::size_t i = 0; i < SigList::size; ++i) { + if ((signatureBitsets[i] & + std::get(entities[eid])) == + signatureBitsets[i]) { + multiMatchingEntities[i].push_back(eid); + } + } + } + } else { + std::array fnDataAr; + + std::mutex mutex; + std::size_t s = currentSize / (ThreadCount * 2); + for (std::size_t i = 0; i < ThreadCount * 2; ++i) { + std::size_t begin = s * i; + std::size_t end; + if (i == ThreadCount * 2 - 1) { + end = currentSize; + } else { + end = s * (i + 1); + } + if (begin == end) { + continue; + } + fnDataAr[i].range = {begin, end}; + fnDataAr[i].manager = this; + fnDataAr[i].multiMatchingEntities = &multiMatchingEntities; + fnDataAr[i].bitsets = signatureBitsets; + fnDataAr[i].mutex = &mutex; + for (std::size_t j = begin; j < end; ++j) { + if (!isAlive(j)) { + fnDataAr[i].dead.insert(j); + } + } + + threadPool->queueFn( + [](void* ud) { + auto* data = static_cast(ud); + for (std::size_t i = data->range[0]; i < data->range[1]; + ++i) { + if (data->dead.find(i) != data->dead.end()) { continue; } - fnDataAr[i].range = {begin, end}; - fnDataAr[i].index = index; - fnDataAr[i].manager = this; - fnDataAr[i].userData = userData; - fnDataAr[i].multiMatchingEntities = - &multiMatchingEntities; - for(std::size_t j = begin; j < end; ++j) { - if(!isAlive(multiMatchingEntities.at(index).at(j))) { - fnDataAr[i].dead.insert(j); + for (std::size_t j = 0; j < SigList::size; ++j) { + if ((data->bitsets[j] & + std::get( + data->manager->entities[i])) == + data->bitsets[j]) { + std::lock_guard lock( + *data->mutex); + data->multiMatchingEntities->at(j) + .push_back(i); } } - threadPool->queueFn([&func] (void *ud) { - auto *data = static_cast(ud); - for(std::size_t i = data->range[0]; - i < data->range[1]; ++i) { - if(data->dead.find(i) == data->dead.end()) { + } + }, + &fnDataAr[i]); + } + threadPool->easyStartAndWait(); + } + + // call functions on matching entities + EC::Meta::forEachDoubleTuple( + EC::Meta::Morph >{}, fTuple, + [this, &multiMatchingEntities, useThreadPool, &userData]( + auto sig, auto func, auto index) { + using SignatureComponents = + typename EC::Meta::Matching::type; + using Helper = EC::Meta::Morph >; + if (!useThreadPool || !threadPool) { + for (const auto& id : multiMatchingEntities[index]) { + if (isAlive(id)) { + Helper::callPtr(id, *this, func, userData); + } + } + } else { + std::array fnDataAr; + std::size_t s = + multiMatchingEntities[index].size() / (ThreadCount * 2); + for (unsigned int i = 0; i < ThreadCount * 2; ++i) { + std::size_t begin = s * i; + std::size_t end; + if (i == ThreadCount * 2 - 1) { + end = multiMatchingEntities[index].size(); + } else { + end = s * (i + 1); + } + if (begin == end) { + continue; + } + fnDataAr[i].range = {begin, end}; + fnDataAr[i].index = index; + fnDataAr[i].manager = this; + fnDataAr[i].userData = userData; + fnDataAr[i].multiMatchingEntities = + &multiMatchingEntities; + for (std::size_t j = begin; j < end; ++j) { + if (!isAlive( + multiMatchingEntities.at(index).at(j))) { + fnDataAr[i].dead.insert(j); + } + } + threadPool->queueFn( + [&func](void* ud) { + auto* data = + static_cast(ud); + for (std::size_t i = data->range[0]; + i < data->range[1]; ++i) { + if (data->dead.find(i) == + data->dead.end()) { Helper::callPtr( data->multiMatchingEntities - ->at(data->index).at(i), - *data->manager, - func, + ->at(data->index) + .at(i), + *data->manager, func, data->userData); } } - }, &fnDataAr[i]); - } - threadPool->easyStartAndWait(); + }, + &fnDataAr[i]); } + threadPool->easyStartAndWait(); } - ); + }); - // pop from idStack "call stack" - do { - { - std::lock_guard lock(idStackMutex); - if (idStack.back() == current_id) { - idStack.pop_back(); - break; - } - } - std::this_thread::sleep_for(std::chrono::microseconds(15)); - } while (true); - - handleDeferredDeletions(); - } - - typedef void ForMatchingFn(std::size_t, - Manager*, - void*); - - /*! - \brief A simple version of forMatchingSignature() - - This function behaves like forMatchingSignature(), but instead of - providing a function with each requested component as a parameter, - the function receives a pointer to the manager itself, with which to - query component/tag data. - - The third parameter can be optionally used to enable the use of the - internal ThreadPool to call the function in parallel. Using the - value false (which is the default) will not use the ThreadPool and - run the function sequentially on all entities on the main thread. - Note that multi-threading is based on splitting the task of calling - the functions across sections of entities. Thus if there are only a - small amount of entities in the manager, then using multiple threads - may not have as great of a speed-up. - */ - template - void forMatchingSimple(ForMatchingFn fn, - void *userData = nullptr, - const bool useThreadPool = false) { - std::size_t current_id; + // pop from idStack "call stack" + do { { - // push to idStack "call stack" std::lock_guard lock(idStackMutex); - current_id = idStackCounter++; - idStack.push_back(current_id); + if (idStack.back() == current_id) { + idStack.pop_back(); + break; + } } - deferringDeletions.fetch_add(1); - const BitsetType signatureBitset = - BitsetType::template generateBitset(); - if(!useThreadPool || !threadPool) { - for(std::size_t i = 0; i < currentSize; ++i) { - if(!std::get(entities[i])) { - continue; - } else if((signatureBitset - & std::get(entities[i])) - == signatureBitset) { - fn(i, this, userData); + std::this_thread::sleep_for(std::chrono::microseconds(15)); + } while (true); + + handleDeferredDeletions(); + } + + typedef void ForMatchingFn(std::size_t, Manager*, + void*); + + /*! + \brief A simple version of forMatchingSignature() + + This function behaves like forMatchingSignature(), but instead of + providing a function with each requested component as a parameter, + the function receives a pointer to the manager itself, with which to + query component/tag data. + + The third parameter can be optionally used to enable the use of the + internal ThreadPool to call the function in parallel. Using the + value false (which is the default) will not use the ThreadPool and + run the function sequentially on all entities on the main thread. + Note that multi-threading is based on splitting the task of calling + the functions across sections of entities. Thus if there are only a + small amount of entities in the manager, then using multiple threads + may not have as great of a speed-up. + */ + template + void forMatchingSimple(ForMatchingFn fn, void* userData = nullptr, + const bool useThreadPool = false) { + std::size_t current_id; + { + // push to idStack "call stack" + std::lock_guard lock(idStackMutex); + current_id = idStackCounter++; + idStack.push_back(current_id); + } + deferringDeletions.fetch_add(1); + const BitsetType signatureBitset = + BitsetType::template generateBitset(); + if (!useThreadPool || !threadPool) { + for (std::size_t i = 0; i < currentSize; ++i) { + if (!std::get(entities[i])) { + continue; + } else if ((signatureBitset & + std::get(entities[i])) == + signatureBitset) { + fn(i, this, userData); + } + } + } else { + std::array fnDataAr; + + std::size_t s = currentSize / (ThreadCount * 2); + for (std::size_t i = 0; i < ThreadCount * 2; ++i) { + std::size_t begin = s * i; + std::size_t end; + if (i == ThreadCount * 2 - 1) { + end = currentSize; + } else { + end = s * (i + 1); + } + if (begin == end) { + continue; + } + fnDataAr[i].range = {begin, end}; + fnDataAr[i].manager = this; + fnDataAr[i].entities = &entities; + fnDataAr[i].signature = &signatureBitset; + fnDataAr[i].userData = userData; + for (std::size_t j = begin; j < end; ++j) { + if (!isAlive(j)) { + fnDataAr[i].dead.insert(j); } } - } else { - std::array fnDataAr; - - std::size_t s = currentSize / (ThreadCount * 2); - for(std::size_t i = 0; i < ThreadCount * 2; ++i) { - std::size_t begin = s * i; - std::size_t end; - if(i == ThreadCount * 2 - 1) { - end = currentSize; - } else { - end = s * (i + 1); - } - if(begin == end) { - continue; - } - fnDataAr[i].range = {begin, end}; - fnDataAr[i].manager = this; - fnDataAr[i].entities = &entities; - fnDataAr[i].signature = &signatureBitset; - fnDataAr[i].userData = userData; - for(std::size_t j = begin; j < end; ++j) { - if(!isAlive(j)) { - fnDataAr[i].dead.insert(j); - } - } - threadPool->queueFn([&fn] (void *ud) { - auto *data = static_cast(ud); - for(std::size_t i = data->range[0]; i < data->range[1]; - ++i) { - if(data->dead.find(i) != data->dead.end()) { + threadPool->queueFn( + [&fn](void* ud) { + auto* data = static_cast(ud); + for (std::size_t i = data->range[0]; i < data->range[1]; + ++i) { + if (data->dead.find(i) != data->dead.end()) { continue; - } else if((*data->signature - & std::get( - data->entities->at(i))) - == *data->signature) { + } else if ((*data->signature & + std::get(data->entities->at( + i))) == *data->signature) { fn(i, data->manager, data->userData); } } - }, &fnDataAr[i]); - } - threadPool->easyStartAndWait(); + }, + &fnDataAr[i]); } + threadPool->easyStartAndWait(); + } - // pop from idStack "call stack" - do { - { - std::lock_guard lock(idStackMutex); - if (idStack.back() == current_id) { - idStack.pop_back(); + // pop from idStack "call stack" + do { + { + std::lock_guard lock(idStackMutex); + if (idStack.back() == current_id) { + idStack.pop_back(); + break; + } + } + std::this_thread::sleep_for(std::chrono::microseconds(15)); + } while (true); + + handleDeferredDeletions(); + } + + /*! + \brief Similar to forMatchingSimple(), but with a collection of + Component/Tag indices + + This function works like forMatchingSimple(), but instead of + providing template types that filter out non-matching entities, an + iterable of indices must be provided which correlate to matching + Component/Tag indices. The function given must match the previously + defined typedef of type ForMatchingFn. + + The fourth parameter can be optionally used to enable the use of the + internal ThreadPool to call the function in parallel. Using the + value false (which is the default) will not use the ThreadPool and + run the function sequentially on all entities on the main thread. + Note that multi-threading is based on splitting the task of calling + the functions across sections of entities. Thus if there are only a + small amount of entities in the manager, then using multiple threads + may not have as great of a speed-up. + */ + template + void forMatchingIterable(Iterable iterable, ForMatchingFn fn, + void* userData = nullptr, + const bool useThreadPool = false) { + std::size_t current_id; + { + // push to idStack "call stack" + std::lock_guard lock(idStackMutex); + current_id = idStackCounter++; + idStack.push_back(current_id); + } + + deferringDeletions.fetch_add(1); + if (!useThreadPool || !threadPool) { + bool isValid; + for (std::size_t i = 0; i < currentSize; ++i) { + if (!std::get(entities[i])) { + continue; + } + + isValid = true; + for (const auto& integralValue : iterable) { + if (!std::get(entities[i]) + .getCombinedBit(integralValue)) { + isValid = false; break; } } - std::this_thread::sleep_for(std::chrono::microseconds(15)); - } while (true); - - handleDeferredDeletions(); - } - - /*! - \brief Similar to forMatchingSimple(), but with a collection of Component/Tag indices - - This function works like forMatchingSimple(), but instead of - providing template types that filter out non-matching entities, an - iterable of indices must be provided which correlate to matching - Component/Tag indices. The function given must match the previously - defined typedef of type ForMatchingFn. - - The fourth parameter can be optionally used to enable the use of the - internal ThreadPool to call the function in parallel. Using the - value false (which is the default) will not use the ThreadPool and - run the function sequentially on all entities on the main thread. - Note that multi-threading is based on splitting the task of calling - the functions across sections of entities. Thus if there are only a - small amount of entities in the manager, then using multiple threads - may not have as great of a speed-up. - */ - template - void forMatchingIterable(Iterable iterable, - ForMatchingFn fn, - void* userData = nullptr, - const bool useThreadPool = false) { - std::size_t current_id; - { - // push to idStack "call stack" - std::lock_guard lock(idStackMutex); - current_id = idStackCounter++; - idStack.push_back(current_id); - } - - deferringDeletions.fetch_add(1); - if(!useThreadPool || !threadPool) { - bool isValid; - for(std::size_t i = 0; i < currentSize; ++i) { - if(!std::get(entities[i])) { - continue; - } - - isValid = true; - for(const auto& integralValue : iterable) { - if(!std::get(entities[i]).getCombinedBit( - integralValue)) { - isValid = false; - break; - } - } - if(!isValid) { continue; } - - fn(i, this, userData); + if (!isValid) { + continue; } - } else { - std::array, ThreadCount * 2> - fnDataAr; - std::size_t s = currentSize / (ThreadCount * 2); - for(std::size_t i = 0; i < ThreadCount * 2; ++i) { - std::size_t begin = s * i; - std::size_t end; - if(i == ThreadCount * 2 - 1) { - end = currentSize; - } else { - end = s * (i + 1); + fn(i, this, userData); + } + } else { + std::array, ThreadCount * 2> fnDataAr; + + std::size_t s = currentSize / (ThreadCount * 2); + for (std::size_t i = 0; i < ThreadCount * 2; ++i) { + std::size_t begin = s * i; + std::size_t end; + if (i == ThreadCount * 2 - 1) { + end = currentSize; + } else { + end = s * (i + 1); + } + if (begin == end) { + continue; + } + fnDataAr[i].range = {begin, end}; + fnDataAr[i].manager = this; + fnDataAr[i].entities = &entities; + fnDataAr[i].iterable = &iterable; + fnDataAr[i].userData = userData; + for (std::size_t j = begin; j < end; ++j) { + if (!isAlive(j)) { + fnDataAr[i].dead.insert(j); } - if(begin == end) { - continue; - } - fnDataAr[i].range = {begin, end}; - fnDataAr[i].manager = this; - fnDataAr[i].entities = &entities; - fnDataAr[i].iterable = &iterable; - fnDataAr[i].userData = userData; - for(std::size_t j = begin; j < end; ++j) { - if(!isAlive(j)) { - fnDataAr[i].dead.insert(j); - } - } - threadPool->queueFn([&fn] (void *ud) { - auto *data = static_cast*>(ud); + } + threadPool->queueFn( + [&fn](void* ud) { + auto* data = + static_cast*>(ud); bool isValid; - for(std::size_t i = data->range[0]; i < data->range[1]; - ++i) { - if(data->dead.find(i) != data->dead.end()) { + for (std::size_t i = data->range[0]; i < data->range[1]; + ++i) { + if (data->dead.find(i) != data->dead.end()) { continue; } isValid = true; - for(const auto& integralValue : *data->iterable) { - if(!std::get(data->entities->at(i)) - .getCombinedBit(integralValue)) { + for (const auto& integralValue : *data->iterable) { + if (!std::get(data->entities->at(i)) + .getCombinedBit(integralValue)) { isValid = false; break; } } - if(!isValid) { continue; } + if (!isValid) { + continue; + } fn(i, data->manager, data->userData); } - }, &fnDataAr[i]); - } - threadPool->easyStartAndWait(); + }, + &fnDataAr[i]); } - - // pop from idStack "call stack" - do { - { - std::lock_guard lock(idStackMutex); - if (idStack.back() == current_id) { - idStack.pop_back(); - break; - } - } - std::this_thread::sleep_for(std::chrono::microseconds(15)); - } while (true); - - handleDeferredDeletions(); + threadPool->easyStartAndWait(); } - }; -} + + // pop from idStack "call stack" + do { + { + std::lock_guard lock(idStackMutex); + if (idStack.back() == current_id) { + idStack.pop_back(); + break; + } + } + std::this_thread::sleep_for(std::chrono::microseconds(15)); + } while (true); + + handleDeferredDeletions(); + } +}; +} // namespace EC #endif -