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)
add_library(EntityComponentSystem INTERFACE)
target_link_libraries(EntityComponentSystem INTERFACE pthread)
target_include_directories(EntityComponentSystem INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

View 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)
{
Helper::call(i, *this, std::forward<Function>(function));
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:
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);
}
}
if((signatureBitset
& std::get<BitsetType>(this->entities[i]))
== signatureBitset)
}
else
{
std::thread threads[threadCount];
std::size_t s = this->currentSize / threadCount;
for(std::size_t i = 0; i < threadCount; ++ i)
{
helper.callInstance(i, *this, function);
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();
}
}
}));
@ -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;
}

View 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);
}
}