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:
Stephen Seo 2017-11-09 21:10:01 +09:00
parent d809d34716
commit 0dba6874ad
2 changed files with 270 additions and 0 deletions

View file

@ -22,6 +22,7 @@
#include <algorithm> #include <algorithm>
#include <thread> #include <thread>
#include <queue> #include <queue>
#include <mutex>
#include "Meta/Combine.hpp" #include "Meta/Combine.hpp"
#include "Meta/Matching.hpp" #include "Meta/Matching.hpp"
@ -965,6 +966,186 @@ namespace EC
return forMatchingFunctions.erase(index) == 1; 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. \brief Resets the Manager, removing all entities.

View file

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