From 50a84f5c8c6f8f22db744c7101adaa038485fdf1 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Thu, 11 Jul 2019 19:47:39 +0900 Subject: [PATCH 1/4] Minor whitespace fixes, minor .gitignore change --- .gitignore | 1 + src/EC/Manager.hpp | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index ae041a3..a087dc2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ build*/ doxygen_html/ compile_commands.json tags +.clangd/ diff --git a/src/EC/Manager.hpp b/src/EC/Manager.hpp index 4b5e4e4..cb4a612 100644 --- a/src/EC/Manager.hpp +++ b/src/EC/Manager.hpp @@ -564,7 +564,7 @@ namespace EC 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 1 (not multi-threaded). If the third + third parameter is default 1 (not multi-threaded). If the third parameter threadCount is set to a value greater than 1, then threadCount threads will be used. Note that multi-threading is based on splitting the task of calling the function across sections @@ -865,7 +865,7 @@ namespace EC std::make_tuple( signatureBitset, context, - [function, helper, this] + [function, helper, this] (std::size_t threadCount, std::vector matching, void* context) @@ -1367,7 +1367,7 @@ namespace EC threads[i].join(); } } - + // call functions on matching entities EC::Meta::forEachDoubleTuple( EC::Meta::Morph >{}, @@ -1563,7 +1563,7 @@ namespace EC threads[i].join(); } } - + // call functions on matching entities EC::Meta::forEachDoubleTuple( EC::Meta::Morph >{}, From 4db5e0caed439680c9e2b8015cda104bf99ec074 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Thu, 11 Jul 2019 21:11:03 +0900 Subject: [PATCH 2/4] Add fn to Manager allowing iterable indices as sig As an alternative to forMatchingSignatures, which calls the given function on entities matching the given Components/Tags in the signature, forMatchingIterable allows filtering entities by an iterable of indices that corresponds to Components/Tags. --- src/EC/Bitset.hpp | 22 +++++ src/EC/Manager.hpp | 67 +++++++++++++++ src/test/ECTest.cpp | 199 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 288 insertions(+) diff --git a/src/EC/Bitset.hpp b/src/EC/Bitset.hpp index 4317313..786b29c 100644 --- a/src/EC/Bitset.hpp +++ b/src/EC/Bitset.hpp @@ -75,6 +75,28 @@ namespace EC return bitset; } + + template + auto getCombinedBit(const IntegralType& i) { + static_assert(std::is_integral::value, + "Parameter must be an integral type"); + if(i >= Combined::size || i < 0) { + return (*this)[Combined::size]; + } else { + return (*this)[i]; + } + } + + template + auto getCombinedBit(const IntegralType& i) const { + static_assert(std::is_integral::value, + "Parameter must be an integral type"); + if(i >= Combined::size || i < 0) { + return (*this)[Combined::size]; + } else { + return (*this)[i]; + } + } }; } diff --git a/src/EC/Manager.hpp b/src/EC/Manager.hpp index cb4a612..5f10fd6 100644 --- a/src/EC/Manager.hpp +++ b/src/EC/Manager.hpp @@ -1649,6 +1649,73 @@ namespace EC deletedSet.clear(); resize(EC_INIT_ENTITIES_SIZE); } + + typedef void ForMatchingIterableFn(std::size_t, Manager*, void*); + + /*! + * \brief Similar to forMatchingSignature(), but with a collection of Component/Tag indices + * + * This function works like forMatchingSignature(), 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 ForMatchingIterableFn. + */ + template + void forMatchingIterable(Iterable iterable, ForMatchingIterableFn fn, void* userPtr = nullptr, std::size_t threadCount = 1) { + if(threadCount <= 1) { + 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, userPtr); + } + } else { + std::vector threads(threadCount); + std::size_t s = currentSize / threadCount; + for(std::size_t i = 0; i < threadCount; ++i) { + std::size_t begin = s * i; + std::size_t end = i == threadCount - 1 ? + currentSize : + s * (i + 1); + threads[i] = std::thread( + [this, &fn, &iterable, userPtr] (std::size_t begin, std::size_t end) { + bool isValid; + for(std::size_t i = begin; i < end; ++i) { + if(!std::get(this->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, userPtr); + } + }, + begin, end); + } + for(std::size_t i = 0; i < threadCount; ++i) { + threads[i].join(); + } + } + } }; } diff --git a/src/test/ECTest.cpp b/src/test/ECTest.cpp index f5f63fd..d15cc7a 100644 --- a/src/test/ECTest.cpp +++ b/src/test/ECTest.cpp @@ -39,6 +39,8 @@ using EmptyList = EC::Meta::TypeList<>; using MixedList = EC::Meta::TypeList; +using ListCombinedComponentsTags = EC::Meta::Combine; + typedef std::unique_ptr C0Ptr; struct Base @@ -1074,3 +1076,200 @@ TEST(EC, FunctionStorageOrder) EXPECT_EQ(5, v.at(4)); EXPECT_EQ(6, v.at(5)); } + +TEST(EC, forMatchingIterableFn) +{ + EC::Manager manager; + auto e0 = manager.addEntity(); + manager.addComponent(e0, 0, 1); + + auto e1 = manager.addEntity(); + manager.addComponent(e1, 2, 3); + manager.addTag(e1); + + auto e2 = manager.addEntity(); + manager.addComponent(e2, 4, 5); + manager.addTag(e2); + manager.addTag(e2); + + auto c0Index = EC::Meta::IndexOf::value; + auto c1Index = EC::Meta::IndexOf::value; + auto t0Index = EC::Meta::IndexOf::value; + auto t1Index = EC::Meta::IndexOf::value; + + { + // test valid indices + auto iterable = {c0Index}; + auto fn = [] (std::size_t i, decltype(manager)* m, void*) { + auto* c = m->getEntityComponent(i); + c->x += 1; + c->y += 1; + }; + manager.forMatchingIterable(iterable, fn, nullptr); + } + + { + auto* c = manager.getEntityComponent(e0); + EXPECT_EQ(c->x, 1); + EXPECT_EQ(c->y, 2); + + c = manager.getEntityComponent(e1); + EXPECT_EQ(c->x, 3); + EXPECT_EQ(c->y, 4); + + c = manager.getEntityComponent(e2); + EXPECT_EQ(c->x, 5); + EXPECT_EQ(c->y, 6); + } + + { + // test invalid indices + auto iterable = {c0Index, c1Index}; + auto fn = [] (std::size_t i, decltype(manager)* m, void*) { + auto* c = m->getEntityComponent(i); + c->x += 1; + c->y += 1; + }; + manager.forMatchingIterable(iterable, fn, nullptr); + } + + { + auto* c = manager.getEntityComponent(e0); + EXPECT_EQ(c->x, 1); + EXPECT_EQ(c->y, 2); + + c = manager.getEntityComponent(e1); + EXPECT_EQ(c->x, 3); + EXPECT_EQ(c->y, 4); + + c = manager.getEntityComponent(e2); + EXPECT_EQ(c->x, 5); + EXPECT_EQ(c->y, 6); + } + + { + // test partially valid indices + auto iterable = {c0Index, t1Index}; + auto fn = [] (std::size_t i, decltype(manager)* m, void*) { + auto* c = m->getEntityComponent(i); + c->x += 1; + c->y += 1; + }; + manager.forMatchingIterable(iterable, fn, nullptr); + } + + { + auto* c = manager.getEntityComponent(e0); + EXPECT_EQ(c->x, 1); + EXPECT_EQ(c->y, 2); + + c = manager.getEntityComponent(e1); + EXPECT_EQ(c->x, 3); + EXPECT_EQ(c->y, 4); + + c = manager.getEntityComponent(e2); + EXPECT_EQ(c->x, 6); + EXPECT_EQ(c->y, 7); + } + + { + // test partially valid indices + auto iterable = {c0Index, t0Index}; + auto fn = [] (std::size_t i, decltype(manager)* m, void*) { + auto* c = m->getEntityComponent(i); + c->x += 10; + c->y += 10; + }; + manager.forMatchingIterable(iterable, fn, nullptr); + } + + { + auto* c = manager.getEntityComponent(e0); + EXPECT_EQ(c->x, 1); + EXPECT_EQ(c->y, 2); + + c = manager.getEntityComponent(e1); + EXPECT_EQ(c->x, 13); + EXPECT_EQ(c->y, 14); + + c = manager.getEntityComponent(e2); + EXPECT_EQ(c->x, 16); + EXPECT_EQ(c->y, 17); + } + + { + // test invalid indices + auto iterable = {(unsigned int)c0Index, 1000u}; + auto fn = [] (std::size_t i, decltype(manager)* m, void*) { + auto* c = m->getEntityComponent(i); + c->x += 1000; + c->y += 1000; + }; + manager.forMatchingIterable(iterable, fn, nullptr); + } + + { + auto* c = manager.getEntityComponent(e0); + EXPECT_EQ(c->x, 1); + EXPECT_EQ(c->y, 2); + + c = manager.getEntityComponent(e1); + EXPECT_EQ(c->x, 13); + EXPECT_EQ(c->y, 14); + + c = manager.getEntityComponent(e2); + EXPECT_EQ(c->x, 16); + EXPECT_EQ(c->y, 17); + } + + { + // test concurrent update + auto iterable = {c0Index}; + auto fn = [] (std::size_t i, decltype(manager)* m, void*) { + auto *c = m->getEntityComponent(i); + c->x += 100; + c->y += 100; + }; + manager.forMatchingIterable(iterable, fn, nullptr, 3); + } + + { + auto* c = manager.getEntityComponent(e0); + EXPECT_EQ(c->x, 101); + EXPECT_EQ(c->y, 102); + + c = manager.getEntityComponent(e1); + EXPECT_EQ(c->x, 113); + EXPECT_EQ(c->y, 114); + + c = manager.getEntityComponent(e2); + EXPECT_EQ(c->x, 116); + EXPECT_EQ(c->y, 117); + } + + + { + // test invalid concurrent update + auto iterable = {(unsigned int)c0Index, 1000u}; + auto fn = [] (std::size_t i, decltype(manager)* m, void*) { + auto *c = m->getEntityComponent(i); + c->x += 1000; + c->y += 1000; + }; + manager.forMatchingIterable(iterable, fn, nullptr, 3); + } + + { + auto* c = manager.getEntityComponent(e0); + EXPECT_EQ(c->x, 101); + EXPECT_EQ(c->y, 102); + + c = manager.getEntityComponent(e1); + EXPECT_EQ(c->x, 113); + EXPECT_EQ(c->y, 114); + + c = manager.getEntityComponent(e2); + EXPECT_EQ(c->x, 116); + EXPECT_EQ(c->y, 117); + } +} From e0da16a63e917de65a40993a9dbc4b1e7b127c71 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Wed, 6 Nov 2019 15:47:16 +0900 Subject: [PATCH 3/4] Replace vector with deque for Component storage When reallocating more data, vector moves the original data to a new buffer. Deque preserves the location of the original data, preventing invalidated pointers to existing data when additional allocation is required. --- src/EC/Manager.hpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/EC/Manager.hpp b/src/EC/Manager.hpp index 5f10fd6..bca25fa 100644 --- a/src/EC/Manager.hpp +++ b/src/EC/Manager.hpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -67,14 +68,14 @@ namespace EC template struct Storage { - using type = std::tuple..., std::vector >; + using type = std::tuple..., std::deque >; }; using ComponentsStorage = typename EC::Meta::Morph >::type; // Entity: isAlive, ComponentsTags Info using EntitiesTupleType = std::tuple; - using EntitiesType = std::vector; + using EntitiesType = std::deque; EntitiesType entities; ComponentsStorage componentsStorage; @@ -103,7 +104,7 @@ namespace EC } EC::Meta::forEach([this, newCapacity] (auto t) { - std::get >( + std::get >( this->componentsStorage).resize(newCapacity); }); @@ -410,10 +411,10 @@ namespace EC constexpr auto index = EC::Meta::IndexOf::value; - // Cast required due to compiler thinking that vector at + // 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::vector*)(&std::get( + (*((std::deque*)(&std::get( componentsStorage ))))[entityID] = std::move(component); } From a310a8ae388585f4a95265b0261a64886bc5d20a Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Wed, 6 Nov 2019 16:33:12 +0900 Subject: [PATCH 4/4] Add forMatchingSimple, refactorings --- src/EC/Manager.hpp | 101 ++++++++++++++++++++++++++++++++++---------- src/test/ECTest.cpp | 66 +++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 23 deletions(-) diff --git a/src/EC/Manager.hpp b/src/EC/Manager.hpp index bca25fa..46e7b72 100644 --- a/src/EC/Manager.hpp +++ b/src/EC/Manager.hpp @@ -490,6 +490,23 @@ namespace EC ).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); + } + private: template struct ForMatchingSignatureHelper @@ -1634,36 +1651,73 @@ namespace EC ); } + typedef void ForMatchingFn(std::size_t, Manager*, void*); + /*! - \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); + * \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. + */ + template + void forMatchingSimple(ForMatchingFn fn, void *userData = nullptr, std::size_t threadCount = 1) { + const BitsetType signatureBitset = BitsetType::template generateBitset(); + if(threadCount <= 1) { + 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::vector threads(threadCount); + const std::size_t s = currentSize / threadCount; + for(std::size_t i = 0; i < threadCount; ++i) { + const std::size_t begin = s * i; + const std::size_t end = + i == threadCount - 1 ? + currentSize : + s * (i + 1); + threads[i] = std::thread( + [this] (const std::size_t begin, + const std::size_t end, + const BitsetType signatureBitset, + ForMatchingFn fn, + void *userData) { + for(std::size_t i = begin; i < end; ++i) { + if(!std::get(entities[i])) { + continue; + } else if((signatureBitset & std::get(entities[i])) == signatureBitset) { + fn(i, this, userData); + } + } + }, + begin, + end, + signatureBitset, + fn, + userData); + } + for(std::size_t i = 0; i < threadCount; ++i) { + threads[i].join(); + } + } } - typedef void ForMatchingIterableFn(std::size_t, Manager*, void*); - /*! - * \brief Similar to forMatchingSignature(), but with a collection of Component/Tag indices + * \brief Similar to forMatchingSimple(), but with a collection of Component/Tag indices * - * This function works like forMatchingSignature(), but instead of + * 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 ForMatchingIterableFn. + * defined typedef of type ForMatchingFn. */ template - void forMatchingIterable(Iterable iterable, ForMatchingIterableFn fn, void* userPtr = nullptr, std::size_t threadCount = 1) { + void forMatchingIterable(Iterable iterable, ForMatchingFn fn, void* userPtr = nullptr, std::size_t threadCount = 1) { if(threadCount <= 1) { bool isValid; for(std::size_t i = 0; i < currentSize; ++i) { @@ -1687,9 +1741,10 @@ namespace EC std::size_t s = currentSize / threadCount; for(std::size_t i = 0; i < threadCount; ++i) { std::size_t begin = s * i; - std::size_t end = i == threadCount - 1 ? - currentSize : - s * (i + 1); + std::size_t end = + i == threadCount - 1 ? + currentSize : + s * (i + 1); threads[i] = std::thread( [this, &fn, &iterable, userPtr] (std::size_t begin, std::size_t end) { bool isValid; diff --git a/src/test/ECTest.cpp b/src/test/ECTest.cpp index d15cc7a..3f1c503 100644 --- a/src/test/ECTest.cpp +++ b/src/test/ECTest.cpp @@ -1077,6 +1077,72 @@ TEST(EC, FunctionStorageOrder) EXPECT_EQ(6, v.at(5)); } +TEST(EC, forMatchingSimple) { + EC::Manager manager; + + auto e0 = manager.addEntity(); + manager.addComponent(e0, 0, 1); + + auto e1 = manager.addEntity(); + manager.addComponent(e1, 2, 3); + manager.addTag(e1); + + auto e2 = manager.addEntity(); + manager.addComponent(e2, 4, 5); + manager.addTag(e2); + manager.addTag(e2); + + // add 10 to C0 components + manager.forMatchingSimple>( + [] (std::size_t id, decltype(manager) *manager, void *) { + C0 *c0 = manager->getEntityData(id); + c0->x += 10; + c0->y += 10; + }, nullptr, 3); + + // verify + { + C0 *c0 = manager.getEntityData(e0); + EXPECT_EQ(c0->x, 10); + EXPECT_EQ(c0->y, 11); + c0 = manager.getEntityData(e1); + EXPECT_EQ(c0->x, 12); + EXPECT_EQ(c0->y, 13); + c0 = manager.getEntityData(e2); + EXPECT_EQ(c0->x, 14); + EXPECT_EQ(c0->y, 15); + } + + auto e3 = manager.addEntity(); + manager.addComponent(e3, 6, 7); + manager.addTag(e3); + manager.addTag(e3); + + // add 100 to entities with C0,T1 + manager.forMatchingSimple>( + [] (std::size_t id, decltype(manager) *manager, void *) { + C0 *c0 = manager->getEntityData(id); + c0->x += 100; + c0->y += 100; + }); + + // verify + { + C0 *c0 = manager.getEntityData(e0); + EXPECT_EQ(c0->x, 10); + EXPECT_EQ(c0->y, 11); + c0 = manager.getEntityData(e1); + EXPECT_EQ(c0->x, 12); + EXPECT_EQ(c0->y, 13); + c0 = manager.getEntityData(e2); + EXPECT_EQ(c0->x, 114); + EXPECT_EQ(c0->y, 115); + c0 = manager.getEntityData(e3); + EXPECT_EQ(c0->x, 106); + EXPECT_EQ(c0->y, 107); + } +} + TEST(EC, forMatchingIterableFn) { EC::Manager manager;