37

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?

michael.bartnett
  • 7,591
  • 1
  • 35
  • 45

4 Answers4

38

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.

Note: Joe Wreschnig has made some good points in the comments, mainly on how my previous implementation made too many assumptions about how good the compiler is at optimizing code; the issue was not detrimental, imo, but it did bug me as well. I also noticed that the former COMPONENT_REGISTER macro did not work with templates.

I've changed the code and now all of those problems should be fixed. The macro works with templates and the issues that Joe raised have been addressed: now it's much easier for compilers to optimize away unnecessary code.

component/component.h

#ifndef COMPONENT_COMPONENT_H
#define COMPONENT_COMPONENT_H

// Standard libraries
#include <string>

// Custom libraries
#include "detail.h"


class Component
{
    // ...
};


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

#define COMPONENT_REGISTER(TYPE, NAME)                                        \
    namespace component {                                                     \
    namespace detail {                                                        \
    namespace                                                                 \
    {                                                                         \
        template<class T>                                                     \
        class ComponentRegistration;                                          \
                                                                              \
        template<>                                                            \
        class ComponentRegistration<TYPE>                                     \
        {                                                                     \
            static const ::component::detail::RegistryEntry<TYPE>& reg;       \
        };                                                                    \
                                                                              \
        const ::component::detail::RegistryEntry<TYPE>&                       \
            ComponentRegistration<TYPE>::reg =                                \
                ::component::detail::RegistryEntry<TYPE>::Instance(NAME);     \
    }}}


#endif // COMPONENT_COMPONENT_H

component/detail.h

#ifndef COMPONENT_DETAIL_H
#define COMPONENT_DETAIL_H

// Standard libraries
#include <map>
#include <string>
#include <utility>

class Component;

namespace component
{
    namespace detail
    {
        typedef Component* (*CreateComponentFunc)();
        typedef std::map<std::string, CreateComponentFunc> ComponentRegistry;

        inline ComponentRegistry& getComponentRegistry()
        {
            static ComponentRegistry reg;
            return reg;
        }

        template<class T>
        Component* createComponent() {
            return new T;
        }

        template<class T>
        struct RegistryEntry
        {
          public:
            static RegistryEntry<T>& Instance(const std::string& name)
            {
                // Because I use a singleton here, even though `COMPONENT_REGISTER`
                // is expanded in multiple translation units, the constructor
                // will only be executed once. Only this cheap `Instance` function
                // (which most likely gets inlined) is executed multiple times.

                static RegistryEntry<T> inst(name);
                return inst;
            }

          private:
            RegistryEntry(const std::string& name)
            {
                ComponentRegistry& reg = getComponentRegistry();
                CreateComponentFunc func = createComponent<T>;

                std::pair<ComponentRegistry::iterator, bool> ret =
                    reg.insert(ComponentRegistry::value_type(name, func));

                if (ret.second == false) {
                    // This means there already is a component registered to
                    // this name. You should handle this error as you see fit.
                }
            }

            RegistryEntry(const RegistryEntry<T>&) = delete; // C++11 feature
            RegistryEntry& operator=(const RegistryEntry<T>&) = delete;
        };

    } // namespace detail

} // namespace component

#endif // COMPONENT_DETAIL_H

component/component.cpp

// Matching header
#include "component.h"

// Standard libraries
#include <string>

// Custom libraries
#include "detail.h"


Component* 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 nullptr;
    }

    detail::CreateComponentFunc func = it->second;
    return func();
}

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

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.

Paul Manta
  • 3,177
  • 3
  • 30
  • 42
  • 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. – michael.bartnett Sep 27 '11 at 04:37
  • 6
    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 '11 at 08: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 '11 at 09:29
  • @bearcdp I've made a few non-crucial changes to my code to make it slightly simpler and more robust. – Paul Manta Oct 07 '11 at 14:18
  • Thanks @Paul! The ability to view the edit history in SE is actually proving really useful for this question. Having the component::destroy function makes a lot of sense to me, I was about to add the same thing. – michael.bartnett Oct 07 '11 at 17:11
  • The macro as given would instantiate a separate registry entry each time the header is included. This is probably not desirable, although in the given code it manifests as a no-op. –  Oct 10 '11 at 12:56
  • @Joe Do you mean every time it's used? There's really no other way to execute code before the main function (which is what is desirable here) except through a constructor. – Paul Manta Oct 11 '11 at 16:35
  • @Paul: No, I mean every time you include the header file that expands COMPONENT_REGISTER(X), it will create a new object with the same symbol and default linkage. –  Oct 11 '11 at 16:48
  • @Joe Yeah, but there's really no way around it. And the downside is theoretical only, you wouldn't feel any increase of memory use since it's going to be minuscule. – Paul Manta Oct 11 '11 at 18:48
  • 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". –  Oct 11 '11 at 20:13
  • @Joe Corrected. – Paul Manta Oct 12 '11 at 20:28
  • The bold and adventurous could make a COMPONENT macro that can be used in the place of class a la QT. – Jonathan Dickinson Oct 12 '11 at 20:40
  • @JonathanDickinson I don't personally like that idea. It's not clear what happens just by looking at its usage. – Paul Manta Oct 12 '11 at 20:41
  • Oh wait QT didn't do that - but you get the idea. class RenderingComponent : public IComponent { } /* register etc. */ class RenderingComponent : public IComponent. – Jonathan Dickinson Oct 12 '11 at 20:45
  • @Paul to each his own :). I am a DSL freak... – Jonathan Dickinson Oct 12 '11 at 20:46
  • @PaulManta: Hi, I'm using this method in my framework... But I got fed up of the multiple calls to register per component so now instead of registering my classes in the .h file I register them in my cpp files. What are the downsides to that approach? – Coyote Dec 07 '11 at 16:49
  • @Coyote The downsides to registering them in CPP files? Performance-wise, there shouldn't be any. The reason I think it's better to register them in the header is because the registration code is right next to the actual class, which makes maintenance easier (you don't have to remember which classes you registered and which ones you didn't). – Paul Manta Dec 07 '11 at 19:20
  • @PaulManta: OK, it makes sense. I already have a few macros for declaring generic functions and some other voodoo in my cpp files, so I will keep my declarations there. I do suspect that registering in the .h might be better if the components end-up being exported to a library. – Coyote Dec 07 '11 at 22:49
  • @Coyote If you register in the source files, you should make the RegistryEntry constructor a regular function, and use that instead of the macro. – Paul Manta Dec 08 '11 at 16:56
  • @JoeWreschnig Would include guards prevent multiple instantiations of the registry entry? – michael.bartnett Dec 08 '11 at 18:34
  • @bearcdp: No. Include guards would prevent multiple definitions within the same TU, but the ODR prevents that as well - you'd get an obvious compiler error if you forgot the include guards. With include guards, if the class is non-static, you will get strange linking issues; if the class is static, you will get an instantiation per TU. This is not an error per se but it means you must make instantiation idempotent, which means multiple registrations of the same time cannot cause an error. –  Dec 14 '11 at 12:46
  • (Coyote's solution is the best approach. Registration to the component system is not part of the type's interface - it might be considered part of the component system's interface but you can't express that without a circular dependency. Hence, it belongs in the implementation .cpp, not the interface .hpp.) –  Dec 14 '11 at 12:49
  • @Joe It's not part of the interface, but it's definitely advantageous to have the registry entry next to the actual type. I know I'm assuming that the compiler will remove the redundant objects that get created in every TU, but then again that assumption is made every time you instantiate templates. I should also mention that this approach is used by Boost.Serialization as well. – Paul Manta Dec 14 '11 at 13:47
  • 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. –  Dec 14 '11 at 13:57
  • @Joe It is static and const and in a nameless namespace. I agree it's a bit of a stretch, but I think it's reasonable for the sake of maintainability. It a lot harder to make sure all types are properly registered when the definition is in one place and the registration in entirely another. And I know this is an argument from authority, but I got this general idea from Boost.Serialization, as I've said. – Paul Manta Dec 14 '11 at 15:36
  • @PaulManta: It is now static, it was not originally, which is what I assumed bearcdp's question was about. As for your argument that it's "easier", the logical conclusion of it is to forego .cpp files entirely and write your entire class definition in your .hpp - after all, it is easier to make sure everything is properly defined. The middle ground seems untenable. Either you are separating .hpp and .cpp based on interface vs. implementation (except where language semantics forbid it, e.g. templates), or you're not and you might as well reap all the benefits of that approach. –  Dec 14 '11 at 19:00
  • @Joe Why must I go for an all or nothing approach? In this case it seems reasonable to me to do the unconventional thing because I don't have to worry about registering all the right classes: there's really no way for me to get it wrong by using the macro. And even if the compiler doesn't remove duplicates, the object will still have the minimum possible size (since it's an empty class) which, compared to the advantage in maintainability, it's insignificant. Actually, I'm not entirely sure, is your main objection a technical or a 'philosophical' one? – Paul Manta Dec 14 '11 at 19:54
  • @JoeWreschnig I modified my code and I think I've addressed all the issues you raised. Please tell me what you think. – Paul Manta Jan 02 '12 at 16:29
  • I found that this solution has a major drawback. When compiling the framework into a library the registrations are never executed. And having the registration code in the CPP makes it worse as it becomes impossible to register the classes by simply including the .H file. see http://gamedev.stackexchange.com/q/37813/8328 for reference. – Coyote Mar 13 '13 at 02:14
  • @Coyote Thanks for pointing that out. There are Boost libraries (I think Serialization) that use mechanisms similar to what I described here. The difference is that they implemented them in a way that prevents unused static variables from being removed by the compiler. – Paul Manta Mar 13 '13 at 07:54
  • @PaulManta Do you have any pointers to one of these examples? – Coyote Mar 13 '13 at 10:58
9

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.

Tetrad
  • 30,124
  • 12
  • 94
  • 143
  • 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. – michael.bartnett Sep 26 '11 at 21:50
  • 2
    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 '11 at 23:40
1

I worked with Paul Manta's design from the chosen answer for a while and eventually came to this more generic and concise factory implementation below that I'm willing to share for anyone coming to this question in the future. In this example, every factory object derives from the Object base class:

struct Object {
    virtual ~Object(){}
};

The static Factory class is as follows:

struct Factory {
    // the template used by the macro
    template<class ObjectType>
    struct RegisterObject {
        // passing a vector of strings allows many id's to map to the same sub-type
        RegisterObject(std::vector<std::string> names){
            for (auto name : names){
                objmap[name] = instantiate<ObjectType>;
            }
        }
    };

    // Factory method for creating objects
    static Object* createObject(const std::string& name){
        auto it = objmap.find(name);
        if (it == objmap.end()){
            return nullptr;
        } else {
            return it->second();
        }
    }

    private:
    // ensures the Factory cannot be instantiated
    Factory() = delete;

    // the map from string id's to instantiator functions
    static std::map<std::string, Object*(*)(void)> objmap;

    // templated sub-type instantiator function
    // requires that the sub-type has a parameter-less constructor
    template<class ObjectType>
    static Object* instantiate(){
        return new ObjectType();
    }
};
// pesky outside-class initialization of static member (grumble grumble)
std::map<std::string, Object*(*)(void)> Factory::objmap;

The macro for registering a sub-type of Object is as follows:

#define RegisterObject(type, ...) \
namespace { \
    ::Factory::RegisterObject<type> register_object_##type({##__VA_ARGS__}); \
}

Now usage is as follows:

struct SpecialObject : Object {
    void beSpecial(){}
};
RegisterObject(SpecialObject, "SpecialObject", "Special", "SpecObj");

...

int main(){
    Object* obj1 = Factory::createObject("SpecialObject");
    Object* obj2 = Factory::createObject("SpecObj");
    ...
    if (obj1){
        delete obj1;
    }
    if (obj2){
        delete obj2;
    }
    return 0;
}

The capacity for many string id's per sub-type was useful in my application, but the restriction to a single id per sub-type would be fairly straightforward.

I hope this has been useful!

alter_igel
  • 111
  • 4
1

Building off of @TimStraubinger's answer, I built a factory class using C++14 standards which can store derived members with an arbitrary number of arguments. My example, unlike Tim's, only takes one name/key per function. Like Tim's, every class being stored is derived from a Base class, mine being called Base.

Base.h

#ifndef BASE_H
#define BASE_H

class Base{
    public:
        virtual ~Base(){}
};

#endif

EX_Factory.h

#ifndef EX_COMPONENT_H
#define EX_COMPONENT_H

#include <string>
#include <map>
#include "Base.h"

struct EX_Factory{
    template<class U, typename... Args>
    static void registerC(const std::string &name){
        registry<Args...>[name] = &create<U>;
    }
    template<typename... Args>
    static Base * createObject(const std::string &key, Args... args){
        auto it = registry<Args...>.find(key);
        if(it == registry<Args...>.end()) return nullptr;
        return it->second(args...);
    }
    private:
        EX_Factory() = delete;
        template<typename... Args>
        static std::map<std::string, Base*(*)(Args...)> registry;

        template<class U, typename... Args>
        static Base* create(Args... args){
            return new U(args...);
        }
};

template<typename... Args>
std::map<std::string, Base*(*)(Args...)> EX_Factory::registry; // Static member declaration.


#endif

main.cpp

#include "EX_Factory.h"
#include <iostream>

using namespace std;

struct derived_1 : public Base{
    derived_1(int i, int j, float f){
        cout << "Derived 1:\t" << i * j + f << endl;
    }
};
struct derived_2 : public Base{
    derived_2(int i, int j){
        cout << "Derived 2:\t" << i + j << endl;
    }
};

int main(){
    EX_Factory::registerC<derived_1, int, int, float>("derived_1"); // Need to include arguments
                                                                    //  when registering classes.
    EX_Factory::registerC<derived_2, int, int>("derived_2");
    derived_1 * d1 = static_cast<derived_1*>(EX_Factory::createObject<int, int, float>("derived_1", 8, 8, 3.0));
    derived_2 * d2 = static_cast<derived_2*>(EX_Factory::createObject<int, int>("derived_2", 3, 3));
    delete d1;
    delete d2;
    return 0;
}

Output

Derived 1:  67
Derived 2:  6

I hope this helps people needing to use a Factory design which does not require an identity constructor to work. It was fun designing, so I hope it helps people needing more flexibility in their Factory designs.