1
\$\begingroup\$

I am trying to save data about various entities in each Level in my game in a JSON file and load it at Runtime(Like a Scene file in Unity). I can save simple stuff like floats and ints and parse them back to with functions like std::stof. I can even handle stuff like a Vector3(3 floats).

Say, I want to save a User-type like CollisionSystem which resolves Collision between entities with a ColliderComponent. CollisionSystem does not contain any data and only Behaviour. How can I infer the Type from just the string and create an Object of CollisionSystem?

{
    "MovementSystem" : {},
    "RenderSystem" : {},
    "CollisionSystem" : {},
    "entities" : [
        {
            "PositionComponent" : {
                "x" : 10,
                "y" : 0,
                "z" : 10
            },
            "SpriteComponent" : {
                "source" : "\"assets/textures/Goblin.png\""
            },
            "ColliderComponent" : {}
        },
        {
            "PositionComponent" : {
                "x" : 0,
                "y" : 0,
                "z" : 30
            },
            "SpriteComponent" : {
                "source" : "\"assets/textures/Hero.png\""
            },
            "ColliderComponent" : {}
        }
    ]
}

The Brute Force method I could think of is a Switch case statement in the Parser like so:

System* GetSystem(string system_type)
{
    switch(system_type)
    {
        case "MovementSystem":
            return new MovementSystem();

        case "CollisionSystem"
            return new CollisionSystem();

        .
        .
        .

        default:
            return nullptr;
    }
}

which can be called like:

System* moveSystem = GetSystem("MovementSystem");
System* collisionSystem = GetSystem("CollisionSystem");

moveSystem->update();
collisionSystem->update();

However, I don't think it's the right way to do this. I am new to C++. So I might be missing some language features that might do this. Is there an elegant way to do this?

\$\endgroup\$
  • 1
    \$\begingroup\$ Why do you want to save your systems to JSON? \$\endgroup\$ – Vaillancourt May 25 '19 at 14:09
  • 1
    \$\begingroup\$ As a side note, you may want to use a library (such as jsoncpp) instead of rewriting your own. \$\endgroup\$ – Vaillancourt May 25 '19 at 14:18
  • \$\begingroup\$ @AlexandreVaillancourt Because I need to know which systems are needed. I don't need a MovementSytem or CollisionSystem to be updated in the Main Menu Scene. \$\endgroup\$ – Vignesh Gunasekaran May 25 '19 at 15:17
  • \$\begingroup\$ I don't think there is much of a hurt if you have a system that is there and that updates 0 component. You could instantiate a system only if you encounter the component it uses. \$\endgroup\$ – Vaillancourt May 25 '19 at 18:04
0
\$\begingroup\$

Since C++ doesn't support reflection like C# it's not possible to do this in an automatic way. At the end of the day you will need some form of a jump table that converts the string to a type.

To automate this process as much as possible and be less error prone, a common strategy is to use macros to mark classes and a global hash map with lambdas that does the transformation for you.

class SystemRegister
{
    public:
        using CreationFunction = System*(*)(); // the signature of a function that takes no arguments and returns a System pointer.

        // Simple way to have a global singleton object
        static SystemRegister& Instance()
        {
            static SystemRegister sys_register;
            return sys_register;
        }

        // This needs to return a value so we can use it outside of the scope of a function. 
        // Without that the macro won't work.
        bool Register(const char* type_id, CreationFunction function)
        {
            m_creation_functions[type_id] = function;
            return true;
        }

        // This is the simplest form this method can have
        // Factory methods like this one should in return a std::unique_ptr (if you are using modern c++)
        // Additionally, you will probably have to add some asserts if the type_id doesn't exist in the map.
        System* Create(const char* type_id)
        {
            return m_creation_functions[type_id]();
        }

    private:
        // This map will do the transformation. Given a string it can return you a new object of the type.
        std::map<const char*, CreationFunction> m_creation_functions;
};
// With this macro you can register your systems in the map
// The bool is used as dummy value just to trick the compiler to allow this to be called outside of the scope of a function. (You can't call void functions on the global scope)
// registration##class_id will concatonate registration with the argument you pass to the macro ex. registrationCollisionSystem.
// #class_id will convert your argument to a c string. ex. "CollisionSystem"
// And finally the lambda will return a newly constructed class_id object as a System pointer.
#define REGISTER_CLASS(class_id) bool registration##class_id =\
                                 SystemRegister::Instance().Register(#class_id, []() -> System* { return new class_id; })

How to use:

.h
class CollisionSystem : public System
{
    ...
};

.cpp
REGISTER_CLASS(CollisionSystem);

void Foo()
{
    System* system = SystemRegister::Instance().Create("CollisionSystem");
}

```
\$\endgroup\$
  • \$\begingroup\$ Hey! This is exactly what I am trying to do, but I need Creation Functions that'll take Parameters for Constructors as well. So, I am also trying to generate these Creation Functions with a Separate JSON. This JSON contains metadata about the Class I am trying to Serialize/Deserialize like the Name, The Constructor Parameters and using these I am Generating the Parser/Creation Functions. Hopefully, that'll work out! \$\endgroup\$ – Vignesh Gunasekaran Jun 9 '19 at 7:44
  • \$\begingroup\$ You can change the signature of the CreationFunction to accept a JSON object and then do all the parsing inside the CreationFunction. \$\endgroup\$ – Exaila Jun 9 '19 at 11:14
  • \$\begingroup\$ Yup! Doing that! Coming from C# I miss Reflection very much! But thanks for your Help! \$\endgroup\$ – Vignesh Gunasekaran Jun 9 '19 at 16:41

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.