I am trying to create a AddComponents()
method that creates and adds multiple components to an entity at once. I have already written a working method to add one component at a time.
It has the following signature:
template <class TComponent, typename... TArguments>
TComponent & AddComponent(TArguments&&... arguments);
and is used in the following way
entity.AddComponent<SomeComponent>(data1, data2, data3);
I would now like to make a function that adds multiple components at one i.e. takes a parameter pack of TComponents
. Of course, the data must be passed as well and here is where things get ugly; I basically need a parameter pack of parameter packs. The function should then iterate over the TComponents
(e.g. using int i < sizeof...(Types)
) and call AddComponent<T>(data...)
for each TComponent
. I have worked with templates quite a bit but I struggle to get my head around this.
Ideally, I want it to be used something like this:
entity.AddComponents<PositionComponent, SpriteComponent, TComponent>(
{ position },
{ texture, colour },
{ data1, data2 });
Internally I need a way to do something like
for each TComponent : TComponents
{
this->AddComponent<TComponent>(forward...(data))
}
In theory, I might be able to get away without this but it seems like an interesting problem nevertheless.
In case people might question what the function does here's the code.
template <class TComponent, typename... TArguments>
inline TComponent & Entity::AddComponent(TArguments&&... arguments)
{
auto typeId = detail::GetComponentTypeID<TComponent>();
auto component = std::make_shared<TComponent>(eventManager, *this, std::forward<TArguments>(arguments)...);
componentList.push_back(component);
componentDictionary.insert(std::make_pair(typeId, component));
entityManager.AddEntityToGroup(*this, typeId);
this->AddPolymorphism(typeId, component);
eventManager.RaiseEvent(mbe::event::ComponentsChangedEvent(*this));
return *component;
}
I would use something similar to what the STL ended up doing: Pass packs of packs as a pack of tuple.
Take emplace_back in a pair for example:
stuct Widget {
int a;
std::string name = {};
};
std::pair<Widget, Widget>{
std::piecewise_construct,
std::forward_as_tuple(12, "values"),
std::forward_as_tuple(31)
};
Here the first parameter tell that you want piecewise construct, so each argument will act as a "pack" that will be expanded into the members of the pair. In this case, the elements of the pair was construct as if:
Widget first{12, "values"};
Widget second{31};
In your case, it seems you always want to construct N
components. So the solution would be:
template<typename Component, typename Tuple>
void addComponentUnpack(Tuple&& tuple) {
std::apply([](auto&&... args) {
addComponent<Component>(std::forward<decltype(args)>(args)...);
}, std::forward<Tuple>(tuple));
}
template<typename... Components, typename... Tuples>
void addComponents(Tuples&&... tuples) {
(addComponentUnpack<Components, Tuples>(std::forward<Tuples>(tuples)), ...);
}
So for each Component
, you have a corresponding Tuple
in the packs Components
and Tuples
. Then, each tuples are sent to a function that construct a single component of type Component
using the tuple Tuple
.
This example uses C++17 features such as fold expressions and std::apply
but it can be implemented in C++14 with a bit more code.
If however you don't wish to use tuples as parameters, you could alway recieve instances of your types directly:
template<typename... Components>
void addComponents(Components&&... components) {
// ...
}
It's usage would be like:
addComponents(
PositionComponent{12, 43},
CollisionComponent{},
SpriteComponent{"texture name"}
);
// or
addComponents<PositionComponent, CollisionComponent, SpriteComponent>(
{12, 42},
{},
{"texture name"}
);
Of course, that would require to move component from the parameters into the entity, which may not be entirely free, depending on the case.
template<class...Ts>
struct types_t:
std::integral_constant<std::size_t, sizeof...(Ts)>
{};
template<class T0, class...Ts>
struct types_t<T0, Ts...>:
std::integral_constant<std::size_t, 1+sizeof...(Ts)>
{ using head=T0; using tail=types_t; };
template<class Componets>
auto ComponentWorker( Components ) {
return [this](auto components, auto&&...args) {
using TComp0 = typename decltype(components)::head;
// add TComp0 using all of args...
using TCompTail = typename decltype(components)::tail;
if constexpr( TCompTail::value != 0 )
return ComponentWorker( TCompTail{} );
else
return; // void
}
}
template <class TC0, class... TCs, class... TArguments>
auto AddComponent(TArguments&&... arguments) {
using components = types_t<TC0, TCs...>;
return ComponentWorker( components )( std::forward<TArguments>(arguments)... );
}
the syntax here is:
AddComponent<A,B,C>( A_args... )( B_args... )( C_args... );
failing to pass arguments results in the component not being added.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With