diff --git a/src/EC/Manager.hpp b/src/EC/Manager.hpp index b5ffe13..17f35a0 100644 --- a/src/EC/Manager.hpp +++ b/src/EC/Manager.hpp @@ -11,6 +11,7 @@ #define EC_GROW_SIZE_AMOUNT 256 #include +#include #include #include #include @@ -54,6 +55,12 @@ namespace EC 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; @@ -95,6 +102,10 @@ namespace EC std::unique_ptr > threadPool; + std::atomic_uint deferringDeletions; + std::vector deferredDeletions; + std::mutex deferredDeletionsMutex; + public: // section for "temporary" structures {{{ /// Temporary struct used internally by ThreadPool @@ -190,6 +201,8 @@ namespace EC if constexpr(ThreadCount >= 2) { threadPool = std::make_unique >(); } + + deferringDeletions.store(0); } private: @@ -246,6 +259,16 @@ namespace EC } } + 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. @@ -253,16 +276,28 @@ namespace EC addEntity is called. Thus calling addEntity may return an id of a previously deleted Entity. */ - void deleteEntity(const std::size_t& index) + void deleteEntity(std::size_t index) { - if(hasEntity(index)) - { - std::get(entities.at(index)) = false; - std::get(entities.at(index)).reset(); - deletedSet.insert(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. @@ -605,6 +640,10 @@ namespace EC currentCapacity = 0; deletedSet.clear(); resize(EC_INIT_ENTITIES_SIZE); + + std::lock_guard lock(deferredDeletionsMutex); + deferringDeletions.store(0); + deferredDeletions.clear(); } private: @@ -713,6 +752,7 @@ namespace EC void* userData = nullptr, const bool useThreadPool = false) { + deferringDeletions.fetch_add(1); using SignatureComponents = typename EC::Meta::Matching::type; using Helper = @@ -788,6 +828,8 @@ namespace EC } threadPool->easyWakeAndWait(); } + + handleDeferredDeletions(); } /*! @@ -836,6 +878,7 @@ namespace EC void* userData = nullptr, const bool useThreadPool = false) { + deferringDeletions.fetch_add(1); using SignatureComponents = typename EC::Meta::Matching::type; using Helper = @@ -910,6 +953,8 @@ namespace EC } threadPool->easyWakeAndWait(); } + + handleDeferredDeletions(); } @@ -974,6 +1019,7 @@ namespace EC Function&& function, void* userData = nullptr) { + deferringDeletions.fetch_add(1); while(forMatchingFunctions.find(functionIndex) != forMatchingFunctions.end()) { @@ -1057,6 +1103,7 @@ namespace EC } }))); + handleDeferredDeletions(); return functionIndex++; } @@ -1174,6 +1221,7 @@ namespace EC */ void callForMatchingFunctions(const bool useThreadPool = false) { + deferringDeletions.fetch_add(1); std::vector bitsets; for(auto iter = forMatchingFunctions.begin(); iter != forMatchingFunctions.end(); @@ -1193,6 +1241,8 @@ namespace EC std::get<2>(iter->second)( useThreadPool, matching[i++], std::get<1>(iter->second)); } + + handleDeferredDeletions(); } /*! @@ -1232,11 +1282,14 @@ namespace EC { 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; } @@ -1430,6 +1483,7 @@ namespace EC void* userData = nullptr, const bool useThreadPool = false) { + deferringDeletions.fetch_add(1); std::vector > multiMatchingEntities(SigList::size); BitsetType signatureBitsets[SigList::size]; @@ -1580,6 +1634,8 @@ namespace EC } } ); + + handleDeferredDeletions(); } /*! @@ -1638,6 +1694,7 @@ namespace EC void* userData = nullptr, const bool useThreadPool = false) { + deferringDeletions.fetch_add(1); std::vector > multiMatchingEntities( SigList::size); BitsetType signatureBitsets[SigList::size]; @@ -1793,6 +1850,8 @@ namespace EC } } ); + + handleDeferredDeletions(); } typedef void ForMatchingFn(std::size_t, @@ -1820,6 +1879,7 @@ namespace EC void forMatchingSimple(ForMatchingFn fn, void *userData = nullptr, const bool useThreadPool = false) { + deferringDeletions.fetch_add(1); const BitsetType signatureBitset = BitsetType::template generateBitset(); if(!useThreadPool || !threadPool) { @@ -1874,6 +1934,8 @@ namespace EC } threadPool->easyWakeAndWait(); } + + handleDeferredDeletions(); } /*! @@ -1899,6 +1961,7 @@ namespace EC 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) { @@ -1968,6 +2031,8 @@ namespace EC } threadPool->easyWakeAndWait(); } + + handleDeferredDeletions(); } }; } diff --git a/src/test/ECTest.cpp b/src/test/ECTest.cpp index d83dd93..e0ff4bb 100644 --- a/src/test/ECTest.cpp +++ b/src/test/ECTest.cpp @@ -1399,3 +1399,35 @@ TEST(EC, ManagerWithLowThreadCount) { EXPECT_EQ(component->y, 1); } } + +TEST(EC, ManagerDeferredDeletions) { + using ManagerType = EC::Manager; + ManagerType manager; + + std::array entities; + for(std::size_t i = 0; i < entities.size(); ++i) { + entities.at(i) = manager.addEntity(); + manager.addTag(entities.at(i)); + if(i < entities.size() / 2) { + manager.addTag(entities.at(i)); + } + } + + auto dataTuple = std::tuple + {&manager, entities.size()}; + + manager.forMatchingSignature>([] (std::size_t id, void *data) { + auto *tuple = (std::tuple*)data; + std::size_t size = std::get<1>(*tuple); + std::get<0>(*tuple)->deleteEntity(id + size / 4); + }, &dataTuple, true); + + for(std::size_t i = 0; i < entities.size(); ++i) { + if (entities.at(i) >= entities.size() / 4 + && entities.at(i) < entities.size() * 3 / 4) { + EXPECT_FALSE(manager.isAlive(entities.at(i))); + } else { + EXPECT_TRUE(manager.isAlive(entities.at(i))); + } + } +}