36
\$\begingroup\$

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?

\$\endgroup\$
  • 1
    \$\begingroup\$ 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. \$\endgroup\$ – Christian Rau Sep 26 '11 at 21:33
  • \$\begingroup\$ I hear you, it was a toy example and I don't have the std::string api memorized . . . yet! \$\endgroup\$ – michael.bartnett Sep 26 '11 at 21:44
  • \$\begingroup\$ @bearcdp I've posted a major update on my answer. The implementation is now must more robust and efficient. \$\endgroup\$ – Paul Manta Jan 2 '12 at 17:11
  • \$\begingroup\$ @PaulManta Thanks so much for updating your answer! There's a lot of little things to learn from it. \$\endgroup\$ – michael.bartnett Jan 3 '12 at 10:27
35
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ 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. \$\endgroup\$ – michael.bartnett Sep 27 '11 at 4:37
  • 6
    \$\begingroup\$ 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. \$\endgroup\$ – Lucas Sep 27 '11 at 8:43
  • \$\begingroup\$ @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. \$\endgroup\$ – Paul Manta Sep 27 '11 at 9:29
  • 1
    \$\begingroup\$ @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". \$\endgroup\$ – user744 Oct 11 '11 at 20:13
  • 1
    \$\begingroup\$ @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. \$\endgroup\$ – user744 Dec 14 '11 at 13:57
9
\$\begingroup\$

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.

\$\endgroup\$
  • 1
    \$\begingroup\$ 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. \$\endgroup\$ – michael.bartnett Sep 26 '11 at 21:50
  • 2
    \$\begingroup\$ 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. \$\endgroup\$ – Tetrad Sep 26 '11 at 23:40
1
\$\begingroup\$

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!

\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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