]> git.seodisparate.com - EntityComponentMetaSystem/commitdiff
Implement forMatchingSignatures for efficiency
authorStephen Seo <seo.disparate@gmail.com>
Thu, 9 Nov 2017 12:10:01 +0000 (21:10 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Thu, 9 Nov 2017 12:17:40 +0000 (21:17 +0900)
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 .

src/EC/Manager.hpp
src/test/ECTest.cpp

index a29ec58241ce2dd34cd2100f5bc20d43f581fb4a..378ca7a8956654bfd0e8d6f2922f15078b4bea84 100644 (file)
@@ -22,6 +22,7 @@
 #include <algorithm>
 #include <thread>
 #include <queue>
+#include <mutex>
 
 #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 <typename SigList, typename FuncTuple>
+        void forMatchingSignatures(FuncTuple funcTuple,
+            std::size_t threadCount = 1)
+        {
+            std::vector<std::vector<std::size_t> > multiMatchingEntities(
+                SigList::size);
+            BitsetType signatureBitsets[SigList::size];
+
+            // generate bitsets for each signature
+            EC::Meta::forEach<SigList>(
+            [this, &signatureBitsets] (auto signature) {
+                signatureBitsets[
+                        EC::Meta::IndexOf<decltype(signature), SigList>{} ] =
+                    BitsetType::template generateBitset<decltype(signature)>();
+            });
+
+            // 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<BitsetType>(entities[eid]))
+                                == signatureBitsets[i])
+                        {
+                            multiMatchingEntities[i].push_back(eid);
+                        }
+                    }
+                }
+            }
+            else
+            {
+                std::vector<std::thread> 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<BitsetType>(entities[eid]))
+                                        == signatureBitsets[i])
+                                {
+                                    std::lock_guard<std::mutex>{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<SigList>(
+            [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<decltype(signature),
+                    SigList>;
+                if(threadCount <= 1)
+                {
+                    for(auto iter = multiMatchingEntities[Index{}].begin();
+                        iter != multiMatchingEntities[Index{}].end(); ++iter)
+                    {
+                        Helper::call(*iter, *this,
+                            std::get<Index{}>(funcTuple));
+                    }
+                }
+                else
+                {
+                    std::vector<std::thread> 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<Index{}>(funcTuple));
+                            }
+                        }, begin, end);
+                    }
+                    for(std::size_t i = 0; i < threadCount; ++i)
+                    {
+                        threads[i].join();
+                    }
+                }
+            });
+        }
+
         /*!
             \brief Resets the Manager, removing all entities.
 
index 155e45e77c32d96f2e5d1bfa8a613551afdc6a5d..8100264ed0b7a73040b6ac8ece679cc84688b5ac 100644 (file)
@@ -582,3 +582,92 @@ TEST(EC, MultiThreaded)
     }
 }
 
+TEST(EC, ForMatchingSignatures)
+{
+    EC::Manager<ListComponentsAll, ListTagsAll> manager;
+
+    auto e = {
+        manager.addEntity(),
+        manager.addEntity(),
+        manager.addEntity(),
+        manager.addEntity()
+    };
+
+    for(auto id : e)
+    {
+        manager.addComponent<C0>(id);
+        manager.addComponent<C1>(id);
+        manager.addTag<T0>(id);
+
+        auto& c1 = manager.getEntityData<C1>(id);
+        c1.vx = 0;
+        c1.vy = 0;
+    }
+
+    using namespace EC::Meta;
+
+    manager.forMatchingSignatures<
+        TypeList<TypeList<C0>, TypeList<C0, C1> >
+    >(
+        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<C0>(id).x);
+        EXPECT_EQ(2, manager.getEntityData<C0>(id).y);
+        EXPECT_EQ(1, manager.getEntityData<C1>(id).vx);
+        EXPECT_EQ(1, manager.getEntityData<C1>(id).vy);
+    }
+
+    manager.forMatchingSignatures<
+        TypeList<TypeList<C0>, TypeList<C0, C1> >
+    >
+    (
+        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<C0>(eid).x);
+        EXPECT_EQ(2, manager.getEntityData<C0>(eid).y);
+        EXPECT_EQ(6, manager.getEntityData<C1>(eid).vx);
+        EXPECT_EQ(8, manager.getEntityData<C1>(eid).vy);
+    }
+}
+