]> git.seodisparate.com/gitweb - EntityComponentMetaSystem/commitdiff
Implement multithreading for function calls
authorStephen Seo <seo.disparate@gmail.com>
Fri, 6 Oct 2017 03:47:05 +0000 (12:47 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Fri, 6 Oct 2017 03:47:05 +0000 (12:47 +0900)
Each of the EC::Manager's function calls over entities can now be
multi-threaded.

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

index dd47d7043582e5a6306763d317077de4541eb42c..553670da6d11a58396656d4c1e950e565e488542 100644 (file)
@@ -17,6 +17,7 @@ set(EntityComponentSystem_HEADERS
     EC/EC.hpp)
 
 add_library(EntityComponentSystem INTERFACE)
+target_link_libraries(EntityComponentSystem INTERFACE pthread)
 target_include_directories(EntityComponentSystem INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
 
 
index 6fdf038b285cce5caf40d1cf53a2533892f8e06d..b1c4a698d13e932dc7385cb67e457cfecb7830b4 100644 (file)
@@ -20,6 +20,7 @@
 #include <set>
 #include <unordered_set>
 #include <algorithm>
+#include <thread>
 
 #include "Meta/Combine.hpp"
 #include "Meta/Matching.hpp"
@@ -500,18 +501,29 @@ namespace EC
             parameters. Tags specified in the Signature are only used as
             filters and will not be given as a parameter to the function.
 
+            The second parameter is default 1 (not multi-threaded). If the
+            second 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 of entities. Thus if there are only
+            a small amount of entities in the manager, then using multiple
+            threads may not have as great of a speed-up.
+
             Example:
             \code{.cpp}
                 manager.forMatchingSignature<TypeList<C0, C1, T0>>([] (
                     std::size_t ID, C0& component0, C1& component1) {
                     // Lambda function contents here
-                });
+                },
+                4 // four threads
+                );
             \endcode
             Note, the ID given to the function is not permanent. An entity's ID
             may change when cleanup() is called.
         */
         template <typename Signature, typename Function>
-        void forMatchingSignature(Function&& function)
+        void forMatchingSignature(Function&& function,
+            std::size_t threadCount = 1)
         {
             using SignatureComponents =
                 typename EC::Meta::Matching<Signature, ComponentsList>::type;
@@ -522,23 +534,70 @@ namespace EC
 
             BitsetType signatureBitset =
                 BitsetType::template generateBitset<Signature>();
-            for(std::size_t i = 0; i < currentSize; ++i)
+            if(threadCount <= 1)
             {
-                if(!std::get<bool>(entities[i]))
+                for(std::size_t i = 0; i < currentSize; ++i)
                 {
-                    continue;
-                }
+                    if(!std::get<bool>(entities[i]))
+                    {
+                        continue;
+                    }
 
-                if((signatureBitset & std::get<BitsetType>(entities[i]))
-                    == signatureBitset)
+                    if((signatureBitset & std::get<BitsetType>(entities[i]))
+                        == signatureBitset)
+                    {
+                        Helper::call(i, *this,
+                            std::forward<Function>(function));
+                    }
+                }
+            }
+            else
+            {
+                std::thread 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;
+                    if(i == threadCount - 1)
+                    {
+                        end = currentSize;
+                    }
+                    else
+                    {
+                        end = s * (i + 1);
+                    }
+                    threads[i] = std::thread([this, &function, &signatureBitset]
+                            (std::size_t begin,
+                            std::size_t end) {
+                        for(std::size_t i = begin; i < end; ++i)
+                        {
+                            if(!std::get<bool>(this->entities[i]))
+                            {
+                                continue;
+                            }
+
+                            if((signatureBitset
+                                    & std::get<BitsetType>(entities[i]))
+                                == signatureBitset)
+                            {
+                                Helper::call(i, *this,
+                                    std::forward<Function>(function));
+                            }
+                        }
+                    },
+                        begin,
+                        end);
+                }
+                for(std::size_t i = 0; i < threadCount; ++i)
                 {
-                    Helper::call(i, *this, std::forward<Function>(function));
+                    threads[i].join();
                 }
             }
         }
 
     private:
-        std::unordered_map<std::size_t, std::function<void()> >
+        std::unordered_map<std::size_t, std::function<void(std::size_t)> >
             forMatchingFunctions;
         std::size_t functionIndex = 0;
 
@@ -602,19 +661,64 @@ namespace EC
 
             forMatchingFunctions.emplace(std::make_pair(
                 functionIndex,
-                [function, signatureBitset, helper, this] ()
+                [function, signatureBitset, helper, this] 
+                    (std::size_t threadCount)
             {
-                for(std::size_t i = 0; i < this->currentSize; ++i)
+                if(threadCount <= 1)
                 {
-                    if(!std::get<bool>(this->entities[i]))
+                    for(std::size_t i = 0; i < this->currentSize; ++i)
                     {
-                        continue;
+                        if(!std::get<bool>(this->entities[i]))
+                        {
+                            continue;
+                        }
+                        if((signatureBitset
+                            & std::get<BitsetType>(this->entities[i]))
+                                == signatureBitset)
+                        {
+                            helper.callInstance(i, *this, function);
+                        }
+                    }
+                }
+                else
+                {
+                    std::thread threads[threadCount];
+                    std::size_t s = this->currentSize / 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 = this->currentSize;
+                        }
+                        else
+                        {
+                            end = s * (i + 1);
+                        }
+                        threads[i] = std::thread(
+                            [this, &function, &signatureBitset, &helper]
+                                (std::size_t begin,
+                                std::size_t end) {
+                            for(std::size_t i = begin; i < end; ++i)
+                            {
+                                if(!std::get<bool>(this->entities[i]))
+                                {
+                                    continue;
+                                }
+                                if((signatureBitset
+                                    & std::get<BitsetType>(this->entities[i]))
+                                        == signatureBitset)
+                                {
+                                    helper.callInstance(i, *this, function);
+                                }
+                            }
+                        },
+                        begin, end);
                     }
-                    if((signatureBitset
-                        & std::get<BitsetType>(this->entities[i]))
-                            == signatureBitset)
+                    for(std::size_t i = 0; i < threadCount; ++i)
                     {
-                        helper.callInstance(i, *this, function);
+                        threads[i].join();
                     }
                 }
             }));
@@ -625,6 +729,14 @@ namespace EC
         /*!
             \brief Call all stored functions.
 
+            A second parameter can be optionally used to specify the number
+            of threads to use when calling the functions. Otherwise, this
+            function is by default not multi-threaded.
+            Note that multi-threading is based on splitting the task of calling
+            the functions across sections of entities. Thus if there are only
+            a small amount of entities in the manager, then using multiple
+            threads may not have as great of a speed-up.
+
             Example:
             \code{.cpp}
                 manager.addForMatchingFunction<TypeList<C0, C1, T0>>([] (
@@ -635,23 +747,34 @@ namespace EC
                 // call all stored functions
                 manager.callForMatchingFunctions();
 
+                // call all stored functions with 4 threads
+                manager.callForMatchingFunctions(4);
+
                 // remove all stored functions
                 manager.clearForMatchingFunctions();
             \endcode
         */
-        void callForMatchingFunctions()
+        void callForMatchingFunctions(std::size_t threadCount = 1)
         {
             for(auto functionIter = forMatchingFunctions.begin();
                 functionIter != forMatchingFunctions.end();
                 ++functionIter)
             {
-                functionIter->second();
+                functionIter->second(threadCount);
             }
         }
 
         /*!
             \brief Call a specific stored function.
 
+            A second parameter can be optionally used to specify the number
+            of threads to use when calling the function. Otherwise, this
+            function is by default not multi-threaded.
+            Note that multi-threading is based on splitting the task of calling
+            the function across sections of entities. Thus if there are only
+            a small amount of entities in the manager, then using multiple
+            threads may not have as great of a speed-up.
+
             Example:
             \code{.cpp}
                 std::size_t id =
@@ -662,18 +785,22 @@ namespace EC
 
                 // call the previously added function
                 manager.callForMatchingFunction(id);
+
+                // call the previously added function with 4 threads
+                manager.callForMatchingFunction(id, 4);
             \endcode
 
             \return False if a function with the given id does not exist.
         */
-        bool callForMatchingFunction(std::size_t id)
+        bool callForMatchingFunction(std::size_t id,
+            std::size_t threadCount = 1)
         {
             auto iter = forMatchingFunctions.find(id);
             if(iter == forMatchingFunctions.end())
             {
                 return false;
             }
-            iter->second();
+            iter->second(threadCount);
             return true;
         }
 
index 028b1754d4b14d2c1c862356e20e0fea6a64d3ab..9cc98c3febe551d35a7cb388f7a5d5ee116ea9f8 100644 (file)
@@ -458,3 +458,117 @@ TEST(EC, DeletedEntityID)
     EXPECT_TRUE(manager.hasEntity(0));
 }
 
+TEST(EC, MultiThreaded)
+{
+    EC::Manager<ListComponentsAll, ListTagsAll> manager;
+
+    for(unsigned int i = 0; i < 17; ++i)
+    {
+        manager.addEntity();
+        manager.addComponent<C0>(i, 0, 0);
+        EXPECT_EQ(0, manager.getEntityData<C0>(i).x);
+        EXPECT_EQ(0, manager.getEntityData<C0>(i).y);
+    }
+
+    manager.forMatchingSignature<EC::Meta::TypeList<C0> >(
+        [] (const std::size_t& eid, C0& c) {
+            c.x = 1;
+            c.y = 2;
+        }
+    );
+
+    for(unsigned int i = 0; i < 17; ++i)
+    {
+        EXPECT_EQ(1, manager.getEntityData<C0>(i).x);
+        EXPECT_EQ(2, manager.getEntityData<C0>(i).y);
+    }
+
+    for(unsigned int i = 3; i < 17; ++i)
+    {
+        manager.deleteEntity(i);
+    }
+    manager.cleanup();
+
+    for(unsigned int i = 0; i < 3; ++i)
+    {
+        EXPECT_EQ(1, manager.getEntityData<C0>(i).x);
+        EXPECT_EQ(2, manager.getEntityData<C0>(i).y);
+    }
+
+    manager.forMatchingSignature<EC::Meta::TypeList<C0> >(
+        [] (const std::size_t& eid, C0& c) {
+            c.x = 3;
+            c.y = 4;
+        },
+        8
+    );
+
+    for(unsigned int i = 0; i < 3; ++i)
+    {
+        EXPECT_EQ(3, manager.getEntityData<C0>(i).x);
+        EXPECT_EQ(4, manager.getEntityData<C0>(i).y);
+    }
+
+    manager.reset();
+
+    for(unsigned int i = 0; i < 17; ++i)
+    {
+        manager.addEntity();
+        manager.addComponent<C0>(i, 0, 0);
+        EXPECT_EQ(0, manager.getEntityData<C0>(i).x);
+        EXPECT_EQ(0, manager.getEntityData<C0>(i).y);
+    }
+
+    auto f0 = manager.addForMatchingFunction<EC::Meta::TypeList<C0> >(
+        [] (const std::size_t& eid, C0& c) {
+            c.x = 1;
+            c.y = 2;
+        }
+    );
+
+    manager.callForMatchingFunctions(2);
+
+    for(unsigned int i = 0; i < 17; ++i)
+    {
+        EXPECT_EQ(1, manager.getEntityData<C0>(i).x);
+        EXPECT_EQ(2, manager.getEntityData<C0>(i).y);
+    }
+
+    auto f1 = manager.addForMatchingFunction<EC::Meta::TypeList<C0> >(
+        [] (const std::size_t& eid, C0& c) {
+            c.x = 3;
+            c.y = 4;
+        }
+    );
+
+    manager.callForMatchingFunction(f1, 4);
+
+    for(unsigned int i = 0; i < 17; ++i)
+    {
+        EXPECT_EQ(3, manager.getEntityData<C0>(i).x);
+        EXPECT_EQ(4, manager.getEntityData<C0>(i).y);
+    }
+
+    for(unsigned int i = 4; i < 17; ++i)
+    {
+        manager.deleteEntity(i);
+    }
+    manager.cleanup();
+
+    manager.callForMatchingFunction(f0, 8);
+
+    for(unsigned int i = 0; i < 4; ++i)
+    {
+        EXPECT_EQ(1, manager.getEntityData<C0>(i).x);
+        EXPECT_EQ(2, manager.getEntityData<C0>(i).y);
+    }
+
+    manager.callForMatchingFunction(f1, 8);
+
+    for(unsigned int i = 0; i < 4; ++i)
+    {
+        EXPECT_EQ(3, manager.getEntityData<C0>(i).x);
+        EXPECT_EQ(4, manager.getEntityData<C0>(i).y);
+    }
+}
+