The way I do it, though this is not for the faint of heart as it involves variable-length structs, reinterpret casts, placement new and manual dtor invocations, and requires some knowledge of proper memory alignment if it is to be compacted as much as possible, is like this:
Each component type is stored in its own contiguous memory block as a VLS. There are no heap/store allocations/deallocations on a per-component instance level (they are stored contiguously in an array that grows, similar to std::vector
, and you can use that here, though it needs to behave like a free list with O(1) removals from the middle without invalidating any other indices).
And a pair of indices (type + index into the container for the type) adding up to 48-bits basically links the entities and components associated to that entity together, with -1 indicating the null terminator.
And yes, there is casting going on (in my case a reinterpret cast which is even more ugly than a dynamic cast) to retrieve a component of a particular type from an entity when we write like:
MotionComponent* motion = entity.get<MotionComponent>();
But that is only one get
method in the codebase that requires such a cast, and there's runtime checking going on to make sure the cast is legal to mitigate a lot of the usual problems associated with casting pointers.
In my use cases it's not uncommon for various systems to process half a million components per frame, so it's rather tuned for a specific use case. It might be a bit overkill for your needs but it's something that has served me for a long time now if you want to roll up your sleeves and get down and dirty with the bits and bytes.
The way you have it now is, if you'll forgive me, about the most inefficient way I can imagine to do it for every kind of use case I can imagine. The problem there is that first you're dynamically-allocating every single component instance. That's often going to result in a loss of spatial locality between components. And then storing a separate container per entity which actually has its own size and capacity and pointer and dynamic allocations is also going to be quite a memory and performance overhead if you have a large, large number of entities. Lastly if you aren't storing components of a particular type contiguously in their own container, then that means when your system wants to fetch a list of components of a particular type (probably the most common query to an ECS), the ECS is going to have to iterate through every single entity in the entire scene that exists checking to see if it has that component.
If that's okay though, then one dynamic_cast
in a central place is very understandable to implement ECS. Any time you want a syntax like this:
MotionComponent* motion = entity.get<MotionComponent>();
... that pretty much implies that there's some cast going on somewhere under the hood unless all that information is available statically at compile-time (in my case at least that's not feasible since plugin developers can introduce brand new component types into the software at runtime). The dynamic_cast
is at least the safest kind of pointer cast there is and, if you centralize it to one get
method as part of the entity interface, then it's not that big of a deal in practice.
As for your particular implementation, if you go with it, I'd seek to add something like this:
class Entity
{
public:
template <class T>
T* get() const
{
for (size_t j=0; j < components.size(); ++j)
{
T* ptr = dynamic_cast<T*>(components[j]);
if (ptr)
return ptr;
}
return nullptr;
}
private:
vector<BaseComponent*> components;
};
That centralizes the dynamic_cast
to that one get<T>
method in the codebase as opposed to having to litter casts and type checks all over your systems*.