Add ptr versions of forMatchingSignature fns

This commit is contained in:
Stephen Seo 2017-11-14 13:11:32 +09:00
parent 648e47aae9
commit 248cf2676a
2 changed files with 426 additions and 2 deletions

View file

@ -514,6 +514,18 @@ namespace EC
);
}
template <typename CType, typename Function>
static void callPtr(
const std::size_t& entityID,
CType& ctype,
Function* function)
{
(*function)(
entityID,
ctype.template getEntityData<Types>(entityID)...
);
}
template <typename CType, typename Function>
void callInstance(
const std::size_t& entityID,
@ -525,6 +537,18 @@ namespace EC
ctype,
std::forward<Function>(function));
}
template <typename CType, typename Function>
void callInstancePtr(
const std::size_t& entityID,
CType& ctype,
Function* function) const
{
ForMatchingSignatureHelper<Types...>::callPtr(
entityID,
ctype,
function);
}
};
public:
@ -632,6 +656,112 @@ namespace EC
}
}
/*!
\brief Calls the given function on all Entities matching the given
Signature.
The function pointer given to this function must accept std::size_t
as its first parameter and Component references for the rest of the
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}
auto function = [] (std::size_t ID, C0& component0,
C1& component1) {
// Lambda function contents here
};
manager.forMatchingSignaturePtr<TypeList<C0, C1, T0>>(
&function, // ptr
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 forMatchingSignaturePtr(Function* function,
std::size_t threadCount = 1)
{
using SignatureComponents =
typename EC::Meta::Matching<Signature, ComponentsList>::type;
using Helper =
EC::Meta::Morph<
SignatureComponents,
ForMatchingSignatureHelper<> >;
BitsetType signatureBitset =
BitsetType::template generateBitset<Signature>();
if(threadCount <= 1)
{
for(std::size_t i = 0; i < currentSize; ++i)
{
if(!std::get<bool>(entities[i]))
{
continue;
}
if((signatureBitset & std::get<BitsetType>(entities[i]))
== signatureBitset)
{
Helper::callPtr(i, *this, function);
}
}
}
else
{
std::vector<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::callPtr(i, *this, 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::size_t)> >
forMatchingFunctions;
@ -712,7 +842,7 @@ namespace EC
& std::get<BitsetType>(this->entities[i]))
== signatureBitset)
{
helper.callInstance(i, *this, function);
helper.callInstancePtr(i, *this, &function);
}
}
}
@ -746,7 +876,7 @@ namespace EC
& std::get<BitsetType>(this->entities[i]))
== signatureBitset)
{
helper.callInstance(i, *this, function);
helper.callInstancePtr(i, *this, &function);
}
}
},
@ -1151,6 +1281,190 @@ namespace EC
});
}
/*!
\brief Call multiple functions with mulitple signatures on all
living entities.
(Living entities as in entities that have not been marked for
deletion.)
Note that this function requires the tuple of functions to hold
pointers to functions, not just functions.
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 forMatchingSignaturesPtr(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> guard(
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>;
constexpr std::size_t index = Index{};
if(threadCount <= 1)
{
for(auto iter = multiMatchingEntities[index].begin();
iter != multiMatchingEntities[index].end(); ++iter)
{
Helper::callPtr(*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 j = begin; j < end; ++j)
{
Helper::callPtr(multiMatchingEntities[index][j],
*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.

View file

@ -791,3 +791,113 @@ TEST(EC, ForMatchingSignatures)
}
}
TEST(EC, forMatchingPtrs)
{
EC::Manager<ListComponentsAll, ListTagsAll> manager;
std::size_t e[] = {
manager.addEntity(),
manager.addEntity(),
manager.addEntity(),
manager.addEntity(),
manager.addEntity(),
manager.addEntity(),
manager.addEntity()
};
auto& first = e[0];
auto& last = e[6];
for(auto eid : e)
{
if(eid != first && eid != last)
{
manager.addComponent<C0>(eid);
manager.addComponent<C1>(eid);
}
else
{
manager.addComponent<C0>(eid);
manager.addTag<T0>(eid);
}
}
const auto func0 = [] (std::size_t eid, C0& c0, C1& c1)
{
c0.x = 1;
c0.y = 2;
c1.vx = 3;
c1.vy = 4;
};
const auto func1 = [] (std::size_t eid, C0& c0)
{
c0.x = 11;
c0.y = 12;
};
using namespace EC::Meta;
manager.forMatchingSignaturePtr<TypeList<C0, C1> >(
&func0
);
manager.forMatchingSignaturePtr<TypeList<C0, T0> >(
&func1
);
for(auto eid : e)
{
if(eid != first && eid != last)
{
C0& c0 = manager.getEntityData<C0>(eid);
EXPECT_EQ(1, c0.x);
EXPECT_EQ(2, c0.y);
c0.x = 0;
c0.y = 0;
C1& c1 = manager.getEntityData<C1>(eid);
EXPECT_EQ(3, c1.vx);
EXPECT_EQ(4, c1.vy);
c1.vx = 0;
c1.vy = 0;
}
else
{
C0& c = manager.getEntityData<C0>(eid);
EXPECT_EQ(11, c.x);
EXPECT_EQ(12, c.y);
c.x = 0;
c.y = 0;
}
}
manager.forMatchingSignaturesPtr<TypeList<
TypeList<C0, C1>,
TypeList<C0, T0> > >(
std::make_tuple(&func0, &func1)
);
for(auto eid : e)
{
if(eid != first && eid != last)
{
C0& c0 = manager.getEntityData<C0>(eid);
EXPECT_EQ(1, c0.x);
EXPECT_EQ(2, c0.y);
c0.x = 0;
c0.y = 0;
C1& c1 = manager.getEntityData<C1>(eid);
EXPECT_EQ(3, c1.vx);
EXPECT_EQ(4, c1.vy);
c1.vx = 0;
c1.vy = 0;
}
else
{
C0& c = manager.getEntityData<C0>(eid);
EXPECT_EQ(11, c.x);
EXPECT_EQ(12, c.y);
c.x = 0;
c.y = 0;
}
}
}