From: Stephen Seo Date: Thu, 9 Nov 2017 12:10:01 +0000 (+0900) Subject: Implement forMatchingSignatures for efficiency X-Git-Tag: 1.0~64 X-Git-Url: https://git.seodisparate.com/stephenseo/static/search/searchdata.js?a=commitdiff_plain;h=0dba6874ad73794c599ab85320bc7763f87ad0d8;p=EntityComponentMetaSystem Implement forMatchingSignatures for efficiency EC::Manager::forMatchingSignatures is different from EC::Manager::forMatchingSignature in that it takes multiple signatures and functions and iterates through all entities once. The trade-off is that a vector of vectors is created to store matching entities. This function can be called to use multiple threads as well, similar to the "non-plural" version of this function. See the doxygen-style documentation in src/EC/Manager.hpp for forMatchingSignatures (or the generated doxygen html) for how to use. Usage example is in the last unit test function in src/test/ECTest.hpp . --- diff --git a/src/EC/Manager.hpp b/src/EC/Manager.hpp index a29ec58..378ca7a 100644 --- a/src/EC/Manager.hpp +++ b/src/EC/Manager.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "Meta/Combine.hpp" #include "Meta/Matching.hpp" @@ -965,6 +966,186 @@ namespace EC return forMatchingFunctions.erase(index) == 1; } + public: + + /*! + \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. + + 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(FuncTuple funcTuple, + std::size_t threadCount = 1) + { + std::vector > multiMatchingEntities( + SigList::size); + BitsetType signatureBitsets[SigList::size]; + + // generate bitsets for each signature + EC::Meta::forEach( + [this, &signatureBitsets] (auto signature) { + signatureBitsets[ + EC::Meta::IndexOf{} ] = + BitsetType::template generateBitset(); + }); + + // find and store entities matching signatures + if(threadCount <= 1) + { + 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::vector threads(threadCount); + std::size_t s = currentSize / threadCount; + std::mutex sigsMutexes[SigList::size]; + for(std::size_t i = 0; i < threadCount; ++i) + { + std::size_t begin = s * i; + std::size_t end; + if(i == threadCount - 1) + { + end = currentSize; + } + else + { + end = s * (i + 1); + } + threads[i] = std::thread( + [this, &signatureBitsets, &multiMatchingEntities, + &sigsMutexes] + (std::size_t begin, std::size_t end) { + for(std::size_t eid = begin; eid < end; ++eid) + { + if(!isAlive(eid)) + { + continue; + } + for(std::size_t i = 0; i < SigList::size; ++i) + { + if((signatureBitsets[i] + & std::get(entities[eid])) + == signatureBitsets[i]) + { + std::lock_guard{sigsMutexes[i]}; + multiMatchingEntities[i].push_back(eid); + } + } + } + }, + begin, end); + } + for(std::size_t i = 0; i < threadCount; ++i) + { + threads[i].join(); + } + } + + // call functions on matching entities + EC::Meta::forEach( + [this, &multiMatchingEntities, &funcTuple, &threadCount] + (auto signature) { + using SignatureComponents = + typename EC::Meta::Matching< + decltype(signature), ComponentsList>::type; + using Helper = + EC::Meta::Morph< + SignatureComponents, + ForMatchingSignatureHelper<> >; + using Index = EC::Meta::IndexOf; + if(threadCount <= 1) + { + for(auto iter = multiMatchingEntities[Index{}].begin(); + iter != multiMatchingEntities[Index{}].end(); ++iter) + { + Helper::call(*iter, *this, + std::get(funcTuple)); + } + } + else + { + std::vector threads(threadCount); + std::size_t s = multiMatchingEntities[Index{}].size() + / threadCount; + for(std::size_t i = 0; i < threadCount; ++i) + { + std::size_t begin = s * i; + std::size_t end; + if(i == threadCount - 1) + { + end = multiMatchingEntities[Index{}].size(); + } + else + { + end = s * (i + 1); + } + threads[i] = std::thread( + [this, &multiMatchingEntities, &funcTuple] + (std::size_t begin, std::size_t end) + { + for(std::size_t i = begin; i < end; ++i) + { + Helper::call(multiMatchingEntities[Index{}][i], + *this, std::get(funcTuple)); + } + }, begin, end); + } + for(std::size_t i = 0; i < threadCount; ++i) + { + threads[i].join(); + } + } + }); + } + /*! \brief Resets the Manager, removing all entities. diff --git a/src/test/ECTest.cpp b/src/test/ECTest.cpp index 155e45e..8100264 100644 --- a/src/test/ECTest.cpp +++ b/src/test/ECTest.cpp @@ -582,3 +582,92 @@ TEST(EC, MultiThreaded) } } +TEST(EC, ForMatchingSignatures) +{ + EC::Manager manager; + + auto e = { + manager.addEntity(), + manager.addEntity(), + manager.addEntity(), + manager.addEntity() + }; + + for(auto id : e) + { + manager.addComponent(id); + manager.addComponent(id); + manager.addTag(id); + + auto& c1 = manager.getEntityData(id); + c1.vx = 0; + c1.vy = 0; + } + + using namespace EC::Meta; + + manager.forMatchingSignatures< + TypeList, TypeList > + >( + std::make_tuple( + [] (std::size_t eid, C0& c) { + EXPECT_EQ(c.x, 0); + EXPECT_EQ(c.y, 0); + c.x = 1; + c.y = 1; + }, + [] (std::size_t eid, C0& c0, C1& c1) { + EXPECT_EQ(c0.x, 1); + EXPECT_EQ(c0.y, 1); + EXPECT_EQ(c1.vx, 0); + EXPECT_EQ(c1.vy, 0); + c1.vx = c0.x; + c1.vy = c0.y; + c0.x = 2; + c0.y = 2; + }) + ); + + for(auto id : e) + { + EXPECT_EQ(2, manager.getEntityData(id).x); + EXPECT_EQ(2, manager.getEntityData(id).y); + EXPECT_EQ(1, manager.getEntityData(id).vx); + EXPECT_EQ(1, manager.getEntityData(id).vy); + } + + manager.forMatchingSignatures< + TypeList, TypeList > + > + ( + std::make_tuple( + [] (std::size_t eid, C0& c) { + EXPECT_EQ(2, c.x); + EXPECT_EQ(2, c.y); + c.x = 5; + c.y = 7; + }, + [] (std::size_t eid, C0& c0, C1& c1) { + EXPECT_EQ(5, c0.x); + EXPECT_EQ(7, c0.y); + EXPECT_EQ(1, c1.vx); + EXPECT_EQ(1, c1.vy); + + c1.vx += c0.x; + c1.vy += c0.y; + + c0.x = 1; + c0.y = 2; + }), + 3 + ); + + for(auto eid : e) + { + EXPECT_EQ(1, manager.getEntityData(eid).x); + EXPECT_EQ(2, manager.getEntityData(eid).y); + EXPECT_EQ(6, manager.getEntityData(eid).vx); + EXPECT_EQ(8, manager.getEntityData(eid).vy); + } +} +