Implement multithreading for function calls

Each of the EC::Manager's function calls over entities can now be
multi-threaded.
This commit is contained in:
Stephen Seo 2017-10-06 12:47:05 +09:00
parent aad97d6d42
commit a08b1575d6
3 changed files with 264 additions and 22 deletions

View file

@ -17,6 +17,7 @@ set(EntityComponentSystem_HEADERS
EC/EC.hpp) EC/EC.hpp)
add_library(EntityComponentSystem INTERFACE) add_library(EntityComponentSystem INTERFACE)
target_link_libraries(EntityComponentSystem INTERFACE pthread)
target_include_directories(EntityComponentSystem INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(EntityComponentSystem INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

View file

@ -20,6 +20,7 @@
#include <set> #include <set>
#include <unordered_set> #include <unordered_set>
#include <algorithm> #include <algorithm>
#include <thread>
#include "Meta/Combine.hpp" #include "Meta/Combine.hpp"
#include "Meta/Matching.hpp" #include "Meta/Matching.hpp"
@ -500,18 +501,29 @@ namespace EC
parameters. Tags specified in the Signature are only used as parameters. Tags specified in the Signature are only used as
filters and will not be given as a parameter to the function. 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: Example:
\code{.cpp} \code{.cpp}
manager.forMatchingSignature<TypeList<C0, C1, T0>>([] ( manager.forMatchingSignature<TypeList<C0, C1, T0>>([] (
std::size_t ID, C0& component0, C1& component1) { std::size_t ID, C0& component0, C1& component1) {
// Lambda function contents here // Lambda function contents here
}); },
4 // four threads
);
\endcode \endcode
Note, the ID given to the function is not permanent. An entity's ID Note, the ID given to the function is not permanent. An entity's ID
may change when cleanup() is called. may change when cleanup() is called.
*/ */
template <typename Signature, typename Function> template <typename Signature, typename Function>
void forMatchingSignature(Function&& function) void forMatchingSignature(Function&& function,
std::size_t threadCount = 1)
{ {
using SignatureComponents = using SignatureComponents =
typename EC::Meta::Matching<Signature, ComponentsList>::type; typename EC::Meta::Matching<Signature, ComponentsList>::type;
@ -522,6 +534,8 @@ namespace EC
BitsetType signatureBitset = BitsetType signatureBitset =
BitsetType::template generateBitset<Signature>(); BitsetType::template generateBitset<Signature>();
if(threadCount <= 1)
{
for(std::size_t i = 0; i < currentSize; ++i) for(std::size_t i = 0; i < currentSize; ++i)
{ {
if(!std::get<bool>(entities[i])) if(!std::get<bool>(entities[i]))
@ -532,13 +546,58 @@ namespace EC
if((signatureBitset & std::get<BitsetType>(entities[i])) if((signatureBitset & std::get<BitsetType>(entities[i]))
== signatureBitset) == signatureBitset)
{ {
Helper::call(i, *this, std::forward<Function>(function)); 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)
{
threads[i].join();
} }
} }
} }
private: private:
std::unordered_map<std::size_t, std::function<void()> > std::unordered_map<std::size_t, std::function<void(std::size_t)> >
forMatchingFunctions; forMatchingFunctions;
std::size_t functionIndex = 0; std::size_t functionIndex = 0;
@ -602,7 +661,10 @@ namespace EC
forMatchingFunctions.emplace(std::make_pair( forMatchingFunctions.emplace(std::make_pair(
functionIndex, functionIndex,
[function, signatureBitset, helper, this] () [function, signatureBitset, helper, this]
(std::size_t threadCount)
{
if(threadCount <= 1)
{ {
for(std::size_t i = 0; i < this->currentSize; ++i) for(std::size_t i = 0; i < this->currentSize; ++i)
{ {
@ -617,6 +679,48 @@ namespace EC
helper.callInstance(i, *this, function); 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);
}
for(std::size_t i = 0; i < threadCount; ++i)
{
threads[i].join();
}
}
})); }));
return functionIndex++; return functionIndex++;
@ -625,6 +729,14 @@ namespace EC
/*! /*!
\brief Call all stored functions. \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: Example:
\code{.cpp} \code{.cpp}
manager.addForMatchingFunction<TypeList<C0, C1, T0>>([] ( manager.addForMatchingFunction<TypeList<C0, C1, T0>>([] (
@ -635,23 +747,34 @@ namespace EC
// call all stored functions // call all stored functions
manager.callForMatchingFunctions(); manager.callForMatchingFunctions();
// call all stored functions with 4 threads
manager.callForMatchingFunctions(4);
// remove all stored functions // remove all stored functions
manager.clearForMatchingFunctions(); manager.clearForMatchingFunctions();
\endcode \endcode
*/ */
void callForMatchingFunctions() void callForMatchingFunctions(std::size_t threadCount = 1)
{ {
for(auto functionIter = forMatchingFunctions.begin(); for(auto functionIter = forMatchingFunctions.begin();
functionIter != forMatchingFunctions.end(); functionIter != forMatchingFunctions.end();
++functionIter) ++functionIter)
{ {
functionIter->second(); functionIter->second(threadCount);
} }
} }
/*! /*!
\brief Call a specific stored function. \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: Example:
\code{.cpp} \code{.cpp}
std::size_t id = std::size_t id =
@ -662,18 +785,22 @@ namespace EC
// call the previously added function // call the previously added function
manager.callForMatchingFunction(id); manager.callForMatchingFunction(id);
// call the previously added function with 4 threads
manager.callForMatchingFunction(id, 4);
\endcode \endcode
\return False if a function with the given id does not exist. \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); auto iter = forMatchingFunctions.find(id);
if(iter == forMatchingFunctions.end()) if(iter == forMatchingFunctions.end())
{ {
return false; return false;
} }
iter->second(); iter->second(threadCount);
return true; return true;
} }

View file

@ -458,3 +458,117 @@ TEST(EC, DeletedEntityID)
EXPECT_TRUE(manager.hasEntity(0)); 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);
}
}