up vote 10 down vote favorite
10
share [fb]

I'm working on an entity component system in C++ that I hope to follow the style of Artemis (http://piemaster.net/2011/07/entity-component-artemis/) in that components are mostly data bags and it's the Systems that contain the logic. I'm hoping to take advantage of the data-centric-ness of this approach and build some nice content tools.

However, one hump I'm coming across is how to take some identifier string or GUID from a data file and use that to construct component for an Entity. Obviously I could just have one big parse function:

Component* ParseComponentType(const std::string &typeName)
{
    if (typeName == "RenderComponent") {
        return new RenderComponent();
    }

    else if (typeName == "TransformComponent") {
        return new TransformComponent();
    }

    else {
        return NULL:
    }
}

But that's really ugly. I intend to be adding and modifying components frequently, and hopefully building some sort of ScriptedComponentComponent, such that you could implement a component and system in Lua for the purposes of prototyping. I'd like to be able to write a class inheriting from some BaseComponent class, maybe toss in a couple of macros to make everything work, and then have the class available for instantiation at runtime.

In C# and Java this would be pretty straightforward, since you get nice reflection APIs to look up classes and constructors. But, I'm doing this in C++ because I want to increase my proficiency in that language.

So How is this accomplished in C++? I've read about enabling RTTI, but it seems most people are wary about that, especially in a situation where I only need it for a subset of object types. If a custom RTTI system is what I need there, where can I go to start learning to write one?

link|improve this question

Quite unrelated comment: If you want to get proficient in C++, then use C++ and not C, regarding strings. Sorry for that, but it had to be said. – Christian Rau Sep 26 at 21:33
I hear you, it was a toy example and I don't have the std::string api memorized . . . yet! – bearcdp Sep 26 at 21:44
feedback

2 Answers

up vote 11 down vote accepted

A comment:
The Artemis implementation is interesting. I came up with a similar solution, except I called my components "Attributes" and "Behaviors". This approach of separating types of components has worked very nicely for me.

Regarding the solution:
The code is easy to use, but the implementation might be hard to follow if you're not experienced with C++. So...

The desired interface

What I did is to have a central repository of all components. Each component type is mapped to a certain string (which represents the component name). This is how you use the system:

// Every time you write a new component class you have to register it.
// For that you use the `COMPONENT_REGISTER` macro.
class RenderingComponent : public Component
{
    // Bla, bla
};
COMPONENT_REGISTER(RenderingComponent, "RenderingComponent")

int main()
{
    // To then create an instance of a registered component all you have
    // to do is call the `create` function like so...
    Component* comp = component::create("RenderingComponent");

    // I found that if you have a special `create` function that returns a
    // pointer, it's best to have a corresponding `destroy` function
    // instead of using `delete` directly.
    component::destroy(comp);
}

The implementation

The implementation is not that bad, but it's still pretty complex; it requires some knowledge of templates and function pointers.

The header file

#include <map>
#include <string>


class Component
{
};


namespace component
{    
    Component* create(const std::string& name);
    void destroy(const Component* comp);

    // This namespace contains implementation details.
    namespace detail
    {
        template<class T>
        Component* createComponent()
        {
            return new T;
        }

        typedef std::map<std::string, Component* (*)()> ComponentRegistry;
        ComponentRegistry& getComponentRegistry();

        template<class T>
        struct RegistryEntry
        {
            RegistryEntry(const std::string& name)
            {
                ComponentRegistry& reg = getComponentRegistry();
                reg[name] = createComponent<T>;
            }
        };

    } // namespace detail

} // namespace component


#define COMPONENT_REGISTER(TYPE, NAME) \
    namespace { \
        static const component::detail::RegistryEntry<TYPE> reg_ent_##TYPE(NAME); \
    }

The source file

#include "component.h"
#include <string>

namespace component 
{
    namespace detail
    {
        ComponentRegistry& getComponentRegistry()
        {
            static ComponentRegistry reg;
            return reg;
        }
    }


    Component* create(const std::string& name)
    {
        detail::ComponentRegistry& reg = detail::getComponentRegistry();
        detail::ComponentRegistry::iterator it = reg.find(name);

        if (it == reg.end()) {
            // This happens when there is no component registered to this
            // name. Here I return a null pointer, but you can handle this
            // error differently if it suits you better.
            return 0;
        }

        return it->second();
    }

    void destroy(const Component* comp)
    {
        delete comp;
    }

} // namespace component

Extending with Lua

I should note that with a bit of work (it's not very hard), this can be used to seamlessly work with components defined in either C++ or Lua, without ever having to think about it.

link|improve this answer
Thank you! You're right, I'm not yet fluent enough in the black arts of C++ templates to totally understand that. But, the one-line macro is exactly what I was looking for, and on top of that I'll use this to begin to more deeply understand templates. – bearcdp Sep 27 at 4:37
5  
I agree that this is basically the right approach but two things that stick out to me: 1. Why not just use a templated function and store a map of function pointers instead of making ComponentTypeImpl instances that will leak on exit (Not really a problem unless you are making a .SO/DLL or something though) 2. The componentRegistry object could break due to the so-called "static initialization order fiasco". To ensure componentRegistry is made first you need to make a function that returns a reference to a local static variable and call that instead of using componentRegistry directly. – Lucas Sep 27 at 8:43
@Lucas Ah, you're totally right about those. I changed the code accordingly. I don't think there were any leaks in the previous code though, since I used shared_ptr, but your advice is still good. – Paul Manta Sep 27 at 9:29
1  
@Paul: Okay, but it's not theoretical, you should at least make it static to avoid possible symbol visibility leakage / linker complaints. Also your comment "You should handle this error as you see fit" should instead say "This is not an error". – Joe Wreschnig Oct 11 at 20:13
1  
@PaulManta: Functions and types are sometimes allowed to "violate" the ODR (e.g. as you say, templates). However here we're talking about instances and those always must follow the ODR. Compilers are not required to detect and report these errors if they occur in multiple TUs (it's generally impossible) and so you enter the realm of undefined behavior. If you absolutely must smear poo all over your interface definition, making it static at least keeps the program well-defined - but Coyote has the right idea. – Joe Wreschnig Dec 14 at 13:57
show 22 more comments
feedback

It seems like what you want is a factory.

http://en.wikipedia.org/wiki/Factory_method_pattern

What you can do is have your various components register with the factory what name they correspond to, and then you have some map of string identifier to constructor method signature to generate your components.

link|improve this answer
1  
So I'd still need to have some section of code that is aware of all of my Component classes, calling ComponentSubclass::RegisterWithFactory(), right? Is there a way to set this up do it more dynamically and automagically? The workflow I'm looking for is 1. Write a class, looking at only the corresonding header and cpp file 2. Re-compile game 3. Start level editor and new component class is available for use. – bearcdp Sep 26 at 21:50
1  
There's really no way for it to happen automagically. You can break it down to a 1 line macro call on a per script basis, though. Paul's answer goes into that a bit. – Tetrad Sep 26 at 23:40
feedback

Your Answer

 
or
required, but never shown

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