Friday, December 22, 2017

How can I properly access the components in my C++ Entity-Component-Systems?


(What I'm describing is based on this design: What is an entity system framework?, scroll down and you'll find it)



I'm having some problems creating an entity-component system in C++. I have my Component class:


class Component { /* ... */ };

Which is actually an interface, for other components to be created. So, to create a custom component, I just implement the interface and add the data that will be used in-game:


class SampleComponent : public Component { int foo, float bar ... };

These components are stored inside an Entity class, which gives each instance of Entity a unique ID:


class Entity {
int ID;
std::unordered_map components;

string getName();
/* ... */
};

Components are added to the entity by hashing the component's name (this probably isn't such a great idea). When I add a custom component, it is stored as a Component type (base class).


Now, on the other hand, I have a System interface, which uses a Node interface inside. The Node class is used to store some of a single entity's components (as the System isn't interested in using all of the entity's components). When the System has to update(), it only need to iterate through the Nodes it stored created from different entities. So:


/* System and Node implementations: (not the interfaces!) */

class SampleSystem : public System {
std::list nodes; //uses SampleNode, not Node

void update();
/* ... */
};

class SampleNode : public Node {
/* Here I define which components SampleNode (and SampleSystem) "needs" */
SampleComponent* sc;
PhysicsComponent* pc;
/* ... more components could go here */
};


Now the problem: let's say I build the SampleNodes by passing an entity to the SampleSystem. The SampleNode then "checks" if the entity has the required components to be used by the SampleSystem. The problem appears when I need access the desired component inside the Entity: the component is stored in a Component (base class) collection, so I can't access the component and copy it over to the new node. I've temporarily solved the problem by casting the Component down to a derived type, but I wanted to know if there is a better way of doing this. I understand if this would mean re-designing what I already have. Thanks.



Answer



If you are going to be storing the Components in a collection all together then you must use a common base class as the type stored in the collection, and thus you must cast to the correct type when you try to access the Components in the collection. The problems of trying to cast to the wrong derived class can be eliminated by clever use of templates and the typeid function, however:


With a map declared like so:


std::unordered_map components;

an addComponent function like:


components[&typeid(*component)] = component;


and a getComponent:


template 
T* getComponent()
{
if(components.count(&typeid(T)) != 0)
{
return static_cast(components[&typeid(T)]);
}
else
{

return NullComponent;
}
}

You will not get a miscast. This is because typeid will return a pointer to the type info of the runtime type (the most derived type) of the component. Since the component is stored with that type info as it's key, the cast can not possibly cause issues because of mismatched types. You also get compile time type checking on the template type as it has to be a type derived from Component or the static_cast will have mismatched types with the unordered_map.


You do not need to store the components of different types in common collection, though. If you abandon the idea of an Entity containing Components, and instead have each Component store an Entity (in reality, it will probably be just an integer ID), then you can store each derived component type in its own collection of the derived type instead of as the common base type, and find the Components "belonging to" an Entity through that ID.


This second implementation is a bit more unintuitive to think about than the first, but it could probably be hidden as implementation details behind an interface so the users of the system don't need to care. I won't comment on which is better as I have not really used the second, but I don't see using static_cast as a problem with as strong a guarantee on types as the first implementation provides. Note that it requires RTTI which may or may not be an issue depending on platform and/or philosophical convictions.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...