Implement forMatchingSignatures for efficiency
EC::Manager::forMatchingSignatures is different from EC::Manager::forMatchingSignature in that it takes multiple signatures and functions and iterates through all entities once. The trade-off is that a vector of vectors is created to store matching entities. This function can be called to use multiple threads as well, similar to the "non-plural" version of this function. See the doxygen-style documentation in src/EC/Manager.hpp for forMatchingSignatures (or the generated doxygen html) for how to use. Usage example is in the last unit test function in src/test/ECTest.hpp .
This commit is contained in:
parent
d809d34716
commit
0dba6874ad
2 changed files with 270 additions and 0 deletions
|
@ -22,6 +22,7 @@
|
|||
#include <algorithm>
|
||||
#include <thread>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
|
||||
#include "Meta/Combine.hpp"
|
||||
#include "Meta/Matching.hpp"
|
||||
|
@ -965,6 +966,186 @@ namespace EC
|
|||
return forMatchingFunctions.erase(index) == 1;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/*!
|
||||
\brief Call multiple functions with mulitple signatures on all
|
||||
living entities.
|
||||
|
||||
(Living entities as in entities that have not been marked for
|
||||
deletion.)
|
||||
|
||||
This function requires the first template parameter to be a
|
||||
EC::Meta::TypeList of signatures. Note that a signature is a
|
||||
EC::Meta::TypeList of components and tags, meaning that SigList
|
||||
is a TypeList of TypeLists.
|
||||
|
||||
The second template parameter can be inferred from the function
|
||||
parameter which should be a tuple of functions. The function
|
||||
at any index in the tuple should match with a signature of the
|
||||
same index in the SigList. Behavior is undefined if there are
|
||||
less functions than signatures.
|
||||
|
||||
See the Unit Test of this function in src/test/ECTest.cpp for
|
||||
usage examples.
|
||||
|
||||
This function was created for the use case where there are many
|
||||
entities in the system which can cause multiple calls to
|
||||
forMatchingSignature to be slow due to the overhead of iterating
|
||||
through the entire list of entities on each invocation.
|
||||
This function instead iterates through all entities once,
|
||||
storing matching entities in a vector of vectors (for each
|
||||
signature and function pair) and then calling functions with
|
||||
the matching list of entities.
|
||||
|
||||
Note that multi-threaded or not, functions will be called in order
|
||||
of signatures. The first function signature pair will be called
|
||||
first, then the second, third, and so on.
|
||||
If this function is called with more than 1 thread specified, then
|
||||
the order of entities called is not guaranteed. Otherwise entities
|
||||
will be called in consecutive order by their ID.
|
||||
*/
|
||||
template <typename SigList, typename FuncTuple>
|
||||
void forMatchingSignatures(FuncTuple funcTuple,
|
||||
std::size_t threadCount = 1)
|
||||
{
|
||||
std::vector<std::vector<std::size_t> > multiMatchingEntities(
|
||||
SigList::size);
|
||||
BitsetType signatureBitsets[SigList::size];
|
||||
|
||||
// generate bitsets for each signature
|
||||
EC::Meta::forEach<SigList>(
|
||||
[this, &signatureBitsets] (auto signature) {
|
||||
signatureBitsets[
|
||||
EC::Meta::IndexOf<decltype(signature), SigList>{} ] =
|
||||
BitsetType::template generateBitset<decltype(signature)>();
|
||||
});
|
||||
|
||||
// find and store entities matching signatures
|
||||
if(threadCount <= 1)
|
||||
{
|
||||
for(std::size_t eid = 0; eid < currentSize; ++eid)
|
||||
{
|
||||
if(!isAlive(eid))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for(std::size_t i = 0; i < SigList::size; ++i)
|
||||
{
|
||||
if((signatureBitsets[i]
|
||||
& std::get<BitsetType>(entities[eid]))
|
||||
== signatureBitsets[i])
|
||||
{
|
||||
multiMatchingEntities[i].push_back(eid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<std::thread> threads(threadCount);
|
||||
std::size_t s = currentSize / threadCount;
|
||||
std::mutex sigsMutexes[SigList::size];
|
||||
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, &signatureBitsets, &multiMatchingEntities,
|
||||
&sigsMutexes]
|
||||
(std::size_t begin, std::size_t end) {
|
||||
for(std::size_t eid = begin; eid < end; ++eid)
|
||||
{
|
||||
if(!isAlive(eid))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for(std::size_t i = 0; i < SigList::size; ++i)
|
||||
{
|
||||
if((signatureBitsets[i]
|
||||
& std::get<BitsetType>(entities[eid]))
|
||||
== signatureBitsets[i])
|
||||
{
|
||||
std::lock_guard<std::mutex>{sigsMutexes[i]};
|
||||
multiMatchingEntities[i].push_back(eid);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
begin, end);
|
||||
}
|
||||
for(std::size_t i = 0; i < threadCount; ++i)
|
||||
{
|
||||
threads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
// call functions on matching entities
|
||||
EC::Meta::forEach<SigList>(
|
||||
[this, &multiMatchingEntities, &funcTuple, &threadCount]
|
||||
(auto signature) {
|
||||
using SignatureComponents =
|
||||
typename EC::Meta::Matching<
|
||||
decltype(signature), ComponentsList>::type;
|
||||
using Helper =
|
||||
EC::Meta::Morph<
|
||||
SignatureComponents,
|
||||
ForMatchingSignatureHelper<> >;
|
||||
using Index = EC::Meta::IndexOf<decltype(signature),
|
||||
SigList>;
|
||||
if(threadCount <= 1)
|
||||
{
|
||||
for(auto iter = multiMatchingEntities[Index{}].begin();
|
||||
iter != multiMatchingEntities[Index{}].end(); ++iter)
|
||||
{
|
||||
Helper::call(*iter, *this,
|
||||
std::get<Index{}>(funcTuple));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<std::thread> threads(threadCount);
|
||||
std::size_t s = multiMatchingEntities[Index{}].size()
|
||||
/ 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 = multiMatchingEntities[Index{}].size();
|
||||
}
|
||||
else
|
||||
{
|
||||
end = s * (i + 1);
|
||||
}
|
||||
threads[i] = std::thread(
|
||||
[this, &multiMatchingEntities, &funcTuple]
|
||||
(std::size_t begin, std::size_t end)
|
||||
{
|
||||
for(std::size_t i = begin; i < end; ++i)
|
||||
{
|
||||
Helper::call(multiMatchingEntities[Index{}][i],
|
||||
*this, std::get<Index{}>(funcTuple));
|
||||
}
|
||||
}, begin, end);
|
||||
}
|
||||
for(std::size_t i = 0; i < threadCount; ++i)
|
||||
{
|
||||
threads[i].join();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Resets the Manager, removing all entities.
|
||||
|
||||
|
|
|
@ -582,3 +582,92 @@ TEST(EC, MultiThreaded)
|
|||
}
|
||||
}
|
||||
|
||||
TEST(EC, ForMatchingSignatures)
|
||||
{
|
||||
EC::Manager<ListComponentsAll, ListTagsAll> manager;
|
||||
|
||||
auto e = {
|
||||
manager.addEntity(),
|
||||
manager.addEntity(),
|
||||
manager.addEntity(),
|
||||
manager.addEntity()
|
||||
};
|
||||
|
||||
for(auto id : e)
|
||||
{
|
||||
manager.addComponent<C0>(id);
|
||||
manager.addComponent<C1>(id);
|
||||
manager.addTag<T0>(id);
|
||||
|
||||
auto& c1 = manager.getEntityData<C1>(id);
|
||||
c1.vx = 0;
|
||||
c1.vy = 0;
|
||||
}
|
||||
|
||||
using namespace EC::Meta;
|
||||
|
||||
manager.forMatchingSignatures<
|
||||
TypeList<TypeList<C0>, TypeList<C0, C1> >
|
||||
>(
|
||||
std::make_tuple(
|
||||
[] (std::size_t eid, C0& c) {
|
||||
EXPECT_EQ(c.x, 0);
|
||||
EXPECT_EQ(c.y, 0);
|
||||
c.x = 1;
|
||||
c.y = 1;
|
||||
},
|
||||
[] (std::size_t eid, C0& c0, C1& c1) {
|
||||
EXPECT_EQ(c0.x, 1);
|
||||
EXPECT_EQ(c0.y, 1);
|
||||
EXPECT_EQ(c1.vx, 0);
|
||||
EXPECT_EQ(c1.vy, 0);
|
||||
c1.vx = c0.x;
|
||||
c1.vy = c0.y;
|
||||
c0.x = 2;
|
||||
c0.y = 2;
|
||||
})
|
||||
);
|
||||
|
||||
for(auto id : e)
|
||||
{
|
||||
EXPECT_EQ(2, manager.getEntityData<C0>(id).x);
|
||||
EXPECT_EQ(2, manager.getEntityData<C0>(id).y);
|
||||
EXPECT_EQ(1, manager.getEntityData<C1>(id).vx);
|
||||
EXPECT_EQ(1, manager.getEntityData<C1>(id).vy);
|
||||
}
|
||||
|
||||
manager.forMatchingSignatures<
|
||||
TypeList<TypeList<C0>, TypeList<C0, C1> >
|
||||
>
|
||||
(
|
||||
std::make_tuple(
|
||||
[] (std::size_t eid, C0& c) {
|
||||
EXPECT_EQ(2, c.x);
|
||||
EXPECT_EQ(2, c.y);
|
||||
c.x = 5;
|
||||
c.y = 7;
|
||||
},
|
||||
[] (std::size_t eid, C0& c0, C1& c1) {
|
||||
EXPECT_EQ(5, c0.x);
|
||||
EXPECT_EQ(7, c0.y);
|
||||
EXPECT_EQ(1, c1.vx);
|
||||
EXPECT_EQ(1, c1.vy);
|
||||
|
||||
c1.vx += c0.x;
|
||||
c1.vy += c0.y;
|
||||
|
||||
c0.x = 1;
|
||||
c0.y = 2;
|
||||
}),
|
||||
3
|
||||
);
|
||||
|
||||
for(auto eid : e)
|
||||
{
|
||||
EXPECT_EQ(1, manager.getEntityData<C0>(eid).x);
|
||||
EXPECT_EQ(2, manager.getEntityData<C0>(eid).y);
|
||||
EXPECT_EQ(6, manager.getEntityData<C1>(eid).vx);
|
||||
EXPECT_EQ(8, manager.getEntityData<C1>(eid).vy);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue