diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5a992df --- /dev/null +++ b/.clang-format @@ -0,0 +1,212 @@ +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveMacros: None +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: true +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 3 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '([-_](test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseLabels: true +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 4 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Left +PPIndentWidth: -1 +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + - ParseTestProto + - ParsePartialTestProto + CanonicalDelimiter: pb + BasedOnStyle: google +ReferenceAlignment: Pointer +ReflowComments: true +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: Auto +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/src/EC/Manager.hpp b/src/EC/Manager.hpp index 4c1c8e1..5215e14 100644 --- a/src/EC/Manager.hpp +++ b/src/EC/Manager.hpp @@ -3,1073 +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 #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. - 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 }}} + Example: + \code{.cpp} + struct C0 + { + // constructor is compatible as a default constructor + C0(int a = 0, char b = 'b') : + a(a), b(b) + {} - /*! - \brief Initializes the manager with a default capacity. - - The default capacity is set with macro EC_INIT_ENTITIES_SIZE, - and will grow by amounts of EC_GROW_SIZE_AMOUNT when needed. - */ - Manager() - { - resize(EC_INIT_ENTITIES_SIZE); - 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; } - private: - void resize(std::size_t newCapacity) - { - if(currentCapacity >= newCapacity) - { - return; - } + Component component(std::forward(args)...); - EC::Meta::forEach([this, newCapacity] (auto t) { - std::get >( - this->componentsStorage).resize(newCapacity); - }); + std::get(entities[entityID]) + .template getComponentBit() = true; - entities.resize(newCapacity); - for(std::size_t i = currentCapacity; i < newCapacity; ++i) - { - entities[i] = std::make_tuple(false, BitsetType{}); - } + constexpr auto index = EC::Meta::IndexOf::value; - currentCapacity = newCapacity; + // 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; } - public: - /*! - \brief Adds an entity to the system, returning the ID of the entity. + std::get(entities[entityID]) + .template getComponentBit() = false; + } - Note: The ID of an entity is guaranteed to not change. - */ - std::size_t addEntity() - { - if(deletedSet.empty()) + /*! + \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) { - 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); - } - 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< - 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; - } - - 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) - { - 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); - } + if ((signatureBitset & std::get(entities[i])) == + signatureBitset) { + Helper::call(i, *this, std::forward(function), + userData); } } - else - { - std::array fnDataAr; + } 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); - } + 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->easyWakeAndWait(); + }, + &fnDataAr[i]); } - - 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) - { - 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) + // pop from idStack "call stack" + do { { - 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); - } + std::lock_guard lock(idStackMutex); + 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->easyWakeAndWait(); + }, + &fnDataAr[i]); } - - 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}; @@ -1077,694 +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->easyWakeAndWait(); + 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->easyWakeAndWait(); + }, + &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) - { - 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 - (); + Example: + \code{.cpp} + manager.addForMatchingFunction>([] + (std::size_t ID, + void* context, + C0* component0, C1* component1) + { + // 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->easyWakeAndWait(); - } - - // 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->easyWakeAndWait(); - } - } - ); - - 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; { - 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) @@ -1772,268 +1458,538 @@ namespace EC } } } - }, &fnDataAr[i]); - } - threadPool->easyWakeAndWait(); + }, + &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->easyWakeAndWait(); + }, + &fnDataAr[i]); } + threadPool->easyStartAndWait(); } - ); + }); - handleDeferredDeletions(); + // 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; + { + // 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; - 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) { - 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::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->easyWakeAndWait(); + }, + &fnDataAr[i]); } - - handleDeferredDeletions(); + threadPool->easyStartAndWait(); } - /*! - \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) { - 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); + // pop from idStack "call stack" + do { + { + std::lock_guard lock(idStackMutex); + 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 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(begin == end) { - continue; + } + if (!isValid) { + continue; + } + + 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); } - 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->easyWakeAndWait(); + }, + &fnDataAr[i]); } - - 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 - diff --git a/src/EC/ThreadPool.hpp b/src/EC/ThreadPool.hpp index e0dd21f..b62daab 100644 --- a/src/EC/ThreadPool.hpp +++ b/src/EC/ThreadPool.hpp @@ -1,150 +1,161 @@ #ifndef EC_META_SYSTEM_THREADPOOL_HPP #define EC_META_SYSTEM_THREADPOOL_HPP -#include -#include -#include #include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef NDEBUG +#include +#endif namespace EC { namespace Internal { - using TPFnType = std::function; - using TPTupleType = std::tuple; - using TPQueueType = std::queue; -} // namespace Internal +using TPFnType = std::function; +using TPTupleType = std::tuple; +using TPQueueType = std::queue; +using ThreadPtr = std::unique_ptr; +using ThreadStackType = std::vector>; +using ThreadStacksType = std::deque; +using ThreadStacksMutexesT = std::deque; +using ThreadCountersT = std::deque; +using PtrsHoldT = std::deque; +using PointersT = std::tuple; +} // namespace Internal /*! \brief Implementation of a Thread Pool. - Note that if SIZE is less than 2, then ThreadPool will not create threads and - run queued functions on the calling thread. + Note that MAXSIZE template parameter determines how many threads are created + each time that startThreads() (or easyStartAndWait()) is called. */ -template +template class ThreadPool { -public: - ThreadPool() : waitCount(0) { - isAlive.store(true); - if(SIZE >= 2) { - for(unsigned int i = 0; i < SIZE; ++i) { - threads.emplace_back([] (std::atomic_bool *isAlive, - std::condition_variable *cv, - std::mutex *cvMutex, - Internal::TPQueueType *fnQueue, - std::mutex *queueMutex, - int *waitCount, - std::mutex *waitCountMutex) { - bool hasFn = false; - Internal::TPTupleType fnTuple; - while(isAlive->load()) { - hasFn = false; - { - std::lock_guard lock(*queueMutex); - if(!fnQueue->empty()) { - fnTuple = fnQueue->front(); - fnQueue->pop(); - hasFn = true; - } - } - if(hasFn) { - std::get<0>(fnTuple)(std::get<1>(fnTuple)); - continue; - } - - { - std::lock_guard lock(*waitCountMutex); - *waitCount += 1; - } - { - std::unique_lock lock(*cvMutex); - cv->wait(lock); - } - { - std::lock_guard lock(*waitCountMutex); - *waitCount -= 1; - } - } - }, &isAlive, &cv, &cvMutex, &fnQueue, &queueMutex, &waitCount, - &waitCountMutex); - } - } - } + public: + ThreadPool() + : threadStacks{}, threadStackMutexes{}, fnQueue{}, queueMutex{} {} ~ThreadPool() { - if(SIZE >= 2) { - isAlive.store(false); - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - cv.notify_all(); - for(auto &thread : threads) { - thread.join(); - } + while (!isNotRunning()) { + std::this_thread::sleep_for(std::chrono::microseconds(30)); } } /*! \brief Queues a function to be called (doesn't start calling yet). - To run the queued functions, wakeThreads() must be called to wake the + To run the queued functions, startThreads() must be called to wake the waiting threads which will start pulling functions from the queue to be called. + + Note that the easyStartAndWait() calls startThreads() and waits until + the threads have finished execution. */ - void queueFn(std::function&& fn, void *ud = nullptr) { + void queueFn(std::function &&fn, void *ud = nullptr) { std::lock_guard lock(queueMutex); fnQueue.emplace(std::make_tuple(fn, ud)); } /*! - \brief Wakes waiting threads to start running queued functions. + \brief Creates MAXSIZE threads that will process queueFn() functions. - If SIZE is less than 2, then this function call will block until all the - queued functions have been executed on the calling thread. + Note that if MAXSIZE < 2, then this function will synchronously execute + the queued functions and block until the functions have been executed. + Otherwise, this function may return before the queued functions have + been executed. + */ + Internal::PointersT startThreads() { + if (MAXSIZE >= 2) { + checkStacks(); + auto pointers = newStackEntry(); + Internal::ThreadStackType *threadStack = std::get<0>(pointers); + std::mutex *threadStackMutex = std::get<1>(pointers); + std::atomic_uint *aCounter = std::get<2>(pointers); + for (unsigned int i = 0; i < MAXSIZE; ++i) { + std::thread *newThread = new std::thread( + [](Internal::ThreadStackType *threadStack, + std::mutex *threadStackMutex, + Internal::TPQueueType *fnQueue, std::mutex *queueMutex, + std::atomic_uint *initCount) { + // add id to idStack "call stack" + { + std::lock_guard lock(*threadStackMutex); + threadStack->push_back( + {Internal::ThreadPtr(nullptr), + std::this_thread::get_id()}); + } - If SIZE is 2 or greater, then this function will return immediately after - waking one or all threads, depending on the given boolean parameter. - */ - void wakeThreads(bool wakeAll = true) { - if(SIZE >= 2) { - // wake threads to pull functions from queue and run them - if(wakeAll) { - cv.notify_all(); - } else { - cv.notify_one(); + ++(*initCount); + + // fetch queued fns and execute them + // fnTuples must live until end of function + std::list fnTuples; + do { + bool fnFound = false; + { + std::lock_guard lock(*queueMutex); + if (!fnQueue->empty()) { + fnTuples.emplace_back( + std::move(fnQueue->front())); + fnQueue->pop(); + fnFound = true; + } + } + if (fnFound) { + std::get<0>(fnTuples.back())( + std::get<1>(fnTuples.back())); + } else { + break; + } + } while (true); + + // pop id from idStack "call stack" + do { + std::this_thread::sleep_for( + std::chrono::microseconds(15)); + if (initCount->load() != MAXSIZE) { + continue; + } + { + std::lock_guard lock( + *threadStackMutex); + if (std::get<1>(threadStack->back()) == + std::this_thread::get_id()) { + if (!std::get<0>(threadStack->back())) { + continue; + } + std::get<0>(threadStack->back())->detach(); + threadStack->pop_back(); + break; + } + } + } while (true); + }, + threadStack, threadStackMutex, &fnQueue, &queueMutex, + aCounter); + // Wait until thread has pushed to threadStack before setting + // the handle to it + while (aCounter->load() != i + 1) { + std::this_thread::sleep_for(std::chrono::microseconds(15)); + } + std::lock_guard stackLock(*threadStackMutex); + std::get<0>(threadStack->at(i)).reset(newThread); } + return pointers; } else { sequentiallyRunTasks(); } - } - - /*! - \brief Gets the number of waiting threads. - - If all threads are waiting, this should equal ThreadCount. - - If SIZE is less than 2, then this will always return 0. - */ - int getWaitCount() { - std::lock_guard lock(waitCountMutex); - return waitCount; - } - - /*! - \brief Returns true if all threads are waiting. - - If SIZE is less than 2, then this will always return true. - */ - bool isAllThreadsWaiting() { - if(SIZE >= 2) { - std::lock_guard lock(waitCountMutex); - return waitCount == SIZE; - } else { - return true; - } + return {nullptr, nullptr, nullptr, nullptr}; } /*! @@ -156,41 +167,80 @@ public: } /*! - \brief Returns the ThreadCount that this class was created with. + \brief Returns the MAXSIZE count that this class was created with. */ - constexpr unsigned int getThreadCount() { - return SIZE; - } + constexpr unsigned int getMaxThreadCount() { return MAXSIZE; } /*! - \brief Wakes all threads and blocks until all queued tasks are finished. + \brief Calls startThreads() and waits until all threads have finished. - If SIZE is less than 2, then this function call will block until all the - queued functions have been executed on the calling thread. - - If SIZE is 2 or greater, then this function will block until all the - queued functions have been executed by the threads in the thread pool. + Regardless of the value set to MAXSIZE, this function will block until + all previously queued functions have been executed. */ - void easyWakeAndWait() { - if(SIZE >= 2) { - wakeThreads(); + void easyStartAndWait() { + if (MAXSIZE >= 2) { + Internal::PointersT pointers = startThreads(); do { - std::this_thread::sleep_for(std::chrono::microseconds(150)); - } while(!isQueueEmpty() || !isAllThreadsWaiting()); + std::this_thread::sleep_for(std::chrono::microseconds(30)); + + bool isQueueEmpty = false; + { + std::lock_guard lock(queueMutex); + isQueueEmpty = fnQueue.empty(); + } + + if (isQueueEmpty) { + break; + } + } while (true); + if (std::get<0>(pointers)) { + do { + { + std::lock_guard lock( + *std::get<1>(pointers)); + if (std::get<0>(pointers)->empty()) { + std::get<3>(pointers)->store(false); + break; + } + } + std::this_thread::sleep_for(std::chrono::microseconds(15)); + } while (true); + } } else { sequentiallyRunTasks(); } } -private: - std::vector threads; - std::atomic_bool isAlive; - std::condition_variable cv; - std::mutex cvMutex; + /*! + \brief Checks if any threads are currently running, returning true if + there are no threads running. + */ + bool isNotRunning() { + std::lock_guard lock(dequesMutex); + auto tIter = threadStacks.begin(); + auto mIter = threadStackMutexes.begin(); + while (tIter != threadStacks.end() && + mIter != threadStackMutexes.end()) { + { + std::lock_guard lock(*mIter); + if (!tIter->empty()) { + return false; + } + } + ++tIter; + ++mIter; + } + return true; + } + + private: + Internal::ThreadStacksType threadStacks; + Internal::ThreadStacksMutexesT threadStackMutexes; Internal::TPQueueType fnQueue; std::mutex queueMutex; - int waitCount; - std::mutex waitCountMutex; + Internal::ThreadCountersT threadCounters; + Internal::PtrsHoldT ptrsHoldBools; + std::mutex dequesMutex; void sequentiallyRunTasks() { // pull functions from queue and run them on current thread @@ -199,7 +249,7 @@ private: do { { std::lock_guard lock(queueMutex); - if(!fnQueue.empty()) { + if (!fnQueue.empty()) { hasFn = true; fnTuple = fnQueue.front(); fnQueue.pop(); @@ -207,14 +257,55 @@ private: hasFn = false; } } - if(hasFn) { + if (hasFn) { std::get<0>(fnTuple)(std::get<1>(fnTuple)); } - } while(hasFn); + } while (hasFn); } + void checkStacks() { + std::lock_guard lock(dequesMutex); + if (threadStacks.empty()) { + return; + } + + bool erased = false; + do { + erased = false; + { + std::lock_guard lock(threadStackMutexes.front()); + if (ptrsHoldBools.front().load()) { + break; + } else if (threadStacks.front().empty()) { + threadStacks.pop_front(); + threadCounters.pop_front(); + ptrsHoldBools.pop_front(); + erased = true; + } + } + if (erased) { + threadStackMutexes.pop_front(); + } else { + break; + } + } while (!threadStacks.empty() && !threadStackMutexes.empty() && + !threadCounters.empty() && !ptrsHoldBools.empty()); + } + + Internal::PointersT newStackEntry() { + std::lock_guard lock(dequesMutex); + threadStacks.emplace_back(); + threadStackMutexes.emplace_back(); + threadCounters.emplace_back(); + threadCounters.back().store(0); + ptrsHoldBools.emplace_back(); + ptrsHoldBools.back().store(true); + + return {&threadStacks.back(), &threadStackMutexes.back(), + &threadCounters.back(), &ptrsHoldBools.back()}; + } }; -} // namespace EC +} // namespace EC #endif diff --git a/src/test/ECTest.cpp b/src/test/ECTest.cpp index e0ff4bb..381f24f 100644 --- a/src/test/ECTest.cpp +++ b/src/test/ECTest.cpp @@ -1,7 +1,9 @@ #include +#include #include +#include #include #include #include @@ -1431,3 +1433,33 @@ TEST(EC, ManagerDeferredDeletions) { } } } + +TEST(EC, NestedThreadPoolTasks) { + using ManagerType = EC::Manager; + ManagerType manager; + + std::array entities; + for (auto &entity : entities) { + entity = manager.addEntity(); + manager.addComponent(entity, entity, entity); + } + + manager.forMatchingSignature>([] (std::size_t id, void *data, C0 *c) { + ManagerType *manager = (ManagerType*)data; + + manager->forMatchingSignature>([id] (std::size_t inner_id, void* data, C0 *inner_c) { + const C0 *const outer_c = (C0*)data; + EXPECT_EQ(id, outer_c->x); + EXPECT_EQ(inner_id, inner_c->x); + if (id == inner_id) { + EXPECT_EQ(outer_c->x, inner_c->x); + EXPECT_EQ(outer_c->y, inner_c->y); + } else { + EXPECT_NE(outer_c->x, inner_c->x); + EXPECT_NE(outer_c->y, inner_c->y); + } + }, c, true); + }, &manager, true); + + //std::this_thread::sleep_for(std::chrono::milliseconds(100)); +} diff --git a/src/test/ThreadPoolTest.cpp b/src/test/ThreadPoolTest.cpp index 9ebcbe1..ea2f161 100644 --- a/src/test/ThreadPoolTest.cpp +++ b/src/test/ThreadPoolTest.cpp @@ -16,22 +16,22 @@ TEST(ECThreadPool, OneThread) { p.queueFn(fn, &data); - p.wakeThreads(); + p.startThreads(); do { std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } while(!p.isQueueEmpty() || !p.isAllThreadsWaiting()); + } while(!p.isQueueEmpty() || !p.isNotRunning()); ASSERT_EQ(data.load(), 1); for(unsigned int i = 0; i < 10; ++i) { p.queueFn(fn, &data); } - p.wakeThreads(); + p.startThreads(); do { std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } while(!p.isQueueEmpty() || !p.isAllThreadsWaiting()); + } while(!p.isQueueEmpty() || !p.isNotRunning()); ASSERT_EQ(data.load(), 11); } @@ -47,22 +47,22 @@ TEST(ECThreadPool, Simple) { p.queueFn(fn, &data); - p.wakeThreads(); + p.startThreads(); do { std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } while(!p.isQueueEmpty() || !p.isAllThreadsWaiting()); + } while(!p.isQueueEmpty() || !p.isNotRunning()); ASSERT_EQ(data.load(), 1); for(unsigned int i = 0; i < 10; ++i) { p.queueFn(fn, &data); } - p.wakeThreads(); + p.startThreads(); do { std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } while(!p.isQueueEmpty() || !p.isAllThreadsWaiting()); + } while(!p.isQueueEmpty() || !p.isNotRunning()); ASSERT_EQ(data.load(), 11); } @@ -70,15 +70,15 @@ TEST(ECThreadPool, Simple) { TEST(ECThreadPool, QueryCount) { { OneThreadPool oneP; - ASSERT_EQ(1, oneP.getThreadCount()); + ASSERT_EQ(1, oneP.getMaxThreadCount()); } { ThreeThreadPool threeP; - ASSERT_EQ(3, threeP.getThreadCount()); + ASSERT_EQ(3, threeP.getMaxThreadCount()); } } -TEST(ECThreadPool, easyWakeAndWait) { +TEST(ECThreadPool, easyStartAndWait) { std::atomic_int data; data.store(0); { @@ -89,7 +89,7 @@ TEST(ECThreadPool, easyWakeAndWait) { atomicInt->fetch_add(1); }, &data); } - oneP.easyWakeAndWait(); + oneP.easyStartAndWait(); EXPECT_EQ(20, data.load()); } { @@ -100,7 +100,7 @@ TEST(ECThreadPool, easyWakeAndWait) { atomicInt->fetch_add(1); }, &data); } - threeP.easyWakeAndWait(); + threeP.easyStartAndWait(); EXPECT_EQ(40, data.load()); } }