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/Bitset.hpp b/src/EC/Bitset.hpp index efc3bc8..11184d3 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 c6dc5ec..9b02286 100644 --- a/src/EC/Manager.hpp +++ b/src/EC/Manager.hpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -66,14 +67,14 @@ namespace EC template struct Storage { - using type = std::tuple...>; + using type = std::tuple...>; }; 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; @@ -102,7 +103,7 @@ namespace EC } EC::Meta::forEach([this, newCapacity] (auto t) { - std::get >( + std::get >( this->componentsStorage).resize(newCapacity); }); @@ -243,7 +244,7 @@ namespace EC { if constexpr (EC::Meta::Contains::value) { - return &std::get >(componentsStorage) + return &std::get >(componentsStorage) .at(index); } else @@ -291,7 +292,7 @@ namespace EC { if constexpr (EC::Meta::Contains::value) { - return &std::get >(componentsStorage) + return &std::get >(componentsStorage) .at(index); } else @@ -400,7 +401,7 @@ namespace EC entities[entityID] ).template getComponentBit() = true; - std::get >(componentsStorage)[entityID] + std::get >(componentsStorage)[entityID] = std::move(component); } } @@ -491,6 +492,23 @@ namespace EC } } + /*! + \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 @@ -566,7 +584,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 @@ -867,7 +885,7 @@ namespace EC std::make_tuple( signatureBitset, context, - [function, helper, this] + [function, helper, this] (std::size_t threadCount, std::vector matching, void* context) @@ -1369,7 +1387,7 @@ namespace EC threads[i].join(); } } - + // call functions on matching entities EC::Meta::forEachDoubleTuple( EC::Meta::Morph >{}, @@ -1565,7 +1583,7 @@ namespace EC threads[i].join(); } } - + // call functions on matching entities EC::Meta::forEachDoubleTuple( EC::Meta::Morph >{}, @@ -1635,21 +1653,126 @@ namespace EC ); } + typedef void ForMatchingFn(std::size_t, Manager*, void*); + /*! - \brief Resets the Manager, removing all entities. + * \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(); + } + } + } - 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(); + /*! + * \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. + */ + template + 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) { + if(!std::get(entities[i])) { + continue; + } - currentSize = 0; - currentCapacity = 0; - deletedSet.clear(); - resize(EC_INIT_ENTITIES_SIZE); + 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..3f1c503 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,266 @@ TEST(EC, FunctionStorageOrder) EXPECT_EQ(5, v.at(4)); 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; + 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); + } +}