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:
parent
aad97d6d42
commit
a08b1575d6
3 changed files with 264 additions and 22 deletions
|
@ -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})
|
||||
|
||||
|
||||
|
|
|
@ -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,6 +534,8 @@ namespace EC
|
|||
|
||||
BitsetType signatureBitset =
|
||||
BitsetType::template generateBitset<Signature>();
|
||||
if(threadCount <= 1)
|
||||
{
|
||||
for(std::size_t i = 0; i < currentSize; ++i)
|
||||
{
|
||||
if(!std::get<bool>(entities[i]))
|
||||
|
@ -532,13 +546,58 @@ namespace EC
|
|||
if((signatureBitset & std::get<BitsetType>(entities[i]))
|
||||
== 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:
|
||||
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,7 +661,10 @@ namespace EC
|
|||
|
||||
forMatchingFunctions.emplace(std::make_pair(
|
||||
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)
|
||||
{
|
||||
|
@ -617,6 +679,48 @@ namespace EC
|
|||
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++;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue