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)
|
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})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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,23 +534,70 @@ namespace EC
|
||||||
|
|
||||||
BitsetType signatureBitset =
|
BitsetType signatureBitset =
|
||||||
BitsetType::template generateBitset<Signature>();
|
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]))
|
if((signatureBitset & std::get<BitsetType>(entities[i]))
|
||||||
== signatureBitset)
|
== 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:
|
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,19 +661,64 @@ 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)
|
||||||
{
|
{
|
||||||
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]))
|
else
|
||||||
== signatureBitset)
|
{
|
||||||
|
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.
|
\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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue