Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

(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<string, Component*> 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<SampleNode> 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.

share|improve this question
add comment

2 Answers 2

up vote 10 down vote accepted

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<const std::type_info* , Component *> components;

an addComponent function like:

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

and a getComponent:

template <typename T>
T* getComponent()
{
    if(components.count(&typeid(T)) != 0)
    {
        return static_cast<T*>(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<T*> 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.

share|improve this answer
    
I've been using C++ for nearly 6 years now, yet every week I learn some new trick. –  knight666 May 22 '13 at 7:06
    
Thanks for answering. I will try using the first method first, and if I maybe later I'll think of a way of using the other one. But, wouldn't the addComponent() method need to be a template method too? If I define a addComponent(Component* c), any sub-component I add will be stored in a Component pointer, and typeid will always refer to the Component base class. –  Fede Rico May 22 '13 at 13:22
1  
Typeid will give you the actual type of the object being pointed to even if the pointer is of a base class –  Chewy Gumball May 22 '13 at 13:30
    
I really liked chewy's answer, so I tried an implementation of it on mingw32. I ran into the issue mentioned by fede rico where addComponent() stores everything as a component because typeid is returning component as the type for everything. Somebody here mentioned that typeid should give the actual type of the object being pointed to even if the pointer is to a base class, but I think it may vary based on compiler, etc. Can anybody else confirm this? I was using g++ std=c++11 mingw32 on windows 7. I ended up just modifying getComponent() to be a template, then saved the type from that into th –  zallik Sep 20 '13 at 1:02
    
This is not compiler specific. You probably did not have the right expression as an argument to the typeid function. –  Chewy Gumball Sep 20 '13 at 5:01
add comment

Chewy has it right, but if you're using C++11 you have some new types you can use.

Instead of using const std::type_info* as the key in your map, you could use std::type_index (see cppreference.com), which is a wrapper around the std::type_info. Why would you use it? The std::type_index actually stores the relationship with the std::type_info as a pointer, but that's one pointer less for you to worry about.

If you are indeed using C++11, I would recommend storing the Component references inside smart pointers. So the map could be something like:

std::map<std::type_index, std::shared_ptr<Component> > components

Adding a new entry could be done so:

components[std::type_index(typeid(*component))] = component

where component is of type std::shared_ptr<Component>. Retrieving a reference to a given type of Component could look like:

template <typename T>
std::shared_ptr<T> getComponent()
{
    std::type_index index(typeid(T));
    if(components.count(std::type_index(typeid(T)) != 0)
    {
        return static_pointer_cast<T>(components[index]);
    }
    else
    {
        return NullComponent
    }
}

Note also the use of static_pointer_cast instead of static_cast.

share|improve this answer
1  
I'm actually using this kind of approach in my own project. –  vijoc Jun 12 '13 at 14:14
    
This is actually quite convenient, since I have been learning C++ using the C++11 standard as reference. One thing I've noticed though, is that all entity-component systems I've found around the web use some sort of cast. I am starting to think that it would be impossible to implement this, or a similar system design without casts. –  Fede Rico Jun 13 '13 at 1:54
    
@Fede Storing Component pointers in a single container does necessarily require casting them down to the derived type. But, like Chewy pointed out, you do have other options available to you, which don't require casting. I myself don't see anything "bad" in having this type of casts in the design, as they're relatively safe. –  vijoc Jun 13 '13 at 4:08
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.