C++ reflection/introspection system

Why do I need this?

     As some of you may know, I’m writing a game in my spare time. In order to make the level/game editor usable, I have to be able to quickly display and edit values. Doing the UI/Serialization/UndoRedo by hand for every type is just too much. The solution to that problem is usually some kind of reflection or introspection system.
    Unfortunately C++ does not have a built in solution. There are few libraries online that could do the job, but some of them were too complicated, some of them had missing features. So I’ve decided to roll my own.
In summary, the information that I need to implement the level editor is:

  • I should be able to retrieve all game object types and ask questions about them.
  • I should be able to retrieve all fields in a type.
  • I should be able to attach tags/attributes/ to types and fields in structs. Do I need to serialize this thing? Should I save this in the game’s level files? Are there any special getters/setters for that field? (and more like these)
  • In addition to that I want to keep my compile times low. A lot a libraries on the internet rely on macros and templates, which tremendously increase compilation/linking times.
  • Speaking of macros, I do not want to change the way I declare structures and fields. It is fine to decorate them, but I really do not want to wrap my code in macros, as this would make if hard to read and reason about.

The solution.

Code and example:
https://github.com/ongamex/blog.reflections

I’ve ripped off the system from my game, and wrote a minimal example of it. Here you can see the code and a small example. It is not a complete solution, it just tries to communicate the idea.

And here is an image to break the ice :P

How it works?

In the beginning of our main function we somehow describe all the types that we want to have information about and store that data in once place(a global variable in the example). Whit that done, we could later use that data however we like (UI, Serialization, UndoRedo, ect.).

So here is a small snippet that shows the idea in practice.

struct Data
{
    int x;
    char y;
    float z;
    MyEnum myEnumValue = myEnum_value0;
};

struct DataWithBaseClass : public Data
{
    std::string str = "hello!";
    quaternion quat;
};

// ...

int main()
{
    // Register our types, by hand or with some tool that generates the code for us.
    #define REGISTER_TYPE(type) g_typeRegister.registerType<type>(#type)
    #define MEMBER(TStruct, TMember) .member(#TMember, &TStruct::TMember)   

    REGISTER_TYPE(Data)
        .constructable<Data>().copyable<Data>()
        MEMBER(Data, x) 
        MEMBER(Data, y)
        MEMBER(Data, z)
        MEMBER(Data, myEnumValue);

    REGISTER_TYPE(DataWithBaseClass)
        .inherits<Data>()
        .constructable<DataWithBaseClass>().copyable<DataWithBaseClass>()
        MEMBER(DataWithBaseClass, str) 
        MEMBER(DataWithBaseClass, quat);

    // The program continues ....
    
    // Use the reflection as you like.
    foo(g_typeRegister.find<Data>()->name);
    bar(g_typeRegister.find<data>()->members[0].byteOffset);
}

     In the example above all the reflection data is described by hand. Ideally we could write a tool that takes our source code, parse it and generate all that data automatically. Good example for such tools are:
https://www.unrealengine.com/en-US/blog/unreal-property-system-reflection
http://onqtam.com/programming/2017-09-02-simple-cpp-reflection-with-cmake

Another idea would be to decorate the code. For example

// Pseudo code

REFLECT_STRUCT(Player, inherits Actor)
struct Player : Actor
{
    FIELD(serializable, visibleInUI, minUI=0, maxUI=100) float health;
    FIELD(serializable, setFn=setWeapon, getFn=getWeapon) enum currentWeapon;
};

These REFLECT_STRUCT, FIELD and so on, could be just a marcos that defer a function call (using static variables and function tricks).

However I found out that, it isn’t much of an effort to describe my data by hand for now. Probably I’ll automate this in the future.

Future improvements:

The system currently does the job, but these could be beneficial:

  • Multiple inheritance. (I’ll update the example in the near future in order to support it).
  • enum values names.
  • Cross DLL safety. The only thing that prevents us from achieving it is the QuickTypeId/typeid. Using the name of the type is a quick solution.
  • Checks if a type is fully described. This could be achieved with alignof and sizeof computations.
  • private members (usually not needed and not worth the effort I guess?)
  • A tool that generates the description for us.

I welcome all feedback and suggestions, so do not hesitate to ask in the section below ^_^

comments powered by Disqus