4
\$\begingroup\$

I have designed an reusable game engine for an project, and their functionality is like this:

Is a completely scripted game engine instead of the usual scripting languages as Lua or Python, this uses Runtime-Compiled C++, and an modified version of Cistron (an component-based programming framework).to be compatible with Runtime-Compiled C++ and so on.

Using the typical GameObject and Component classes of the Component-based design pattern, is serializable via JSON, BSON or Binary useful for selecting which objects will be loaded the next time.

The main problem: We want to use our custom GameObjects and their components properties in our level editor, before used hardcoded functions to access GameObject base class virtual functions from the derived ones, if do you want to modify an property specifically from that class you need inside into the code, this situation happens too with the derived classes of Component class, in little projects there's no problem but for larger projects becomes tedious, lengthy and error-prone.

I've researched a lot to find a solution without luck, i tried with the Ogitor's property system (since our engine is Ogre-based) but we find it inappropiate for the component-based design and it's limited only for the Ogre classes and can lead to performance overhead, and we tried some code we find in the Internet we tested it and worked a little but we considered the macro and lambda abuse too horrible take a look (some code omitted):

   IWE_IMPLEMENT_PROP_BEGIN(CBaseEntity)
     IWE_PROP_LEVEL_BEGIN("Editor");
       IWE_PROP_INT_S("Id", "Internal id", m_nEntID, [](int n) {}, true);
            IWE_PROP_LEVEL_END();
            IWE_PROP_LEVEL_BEGIN("Entity");
              IWE_PROP_STRING_S("Mesh", "Mesh used for this entity",
         m_pModelName, [pInst](const std::string& sModelName) {
           pInst->m_stackMemUndoType.push(ENT_MEM_MESH); pInst->m_stackMemUndoStr.push(pInst->getModelName());
           pInst->setModel(sModelName, false); pInst->saveState();
         }, false);
       IWE_PROP_VECTOR3_S("Position", m_vecPosition,
  [pInst](float fX, float fY, float fZ) {
    pInst->m_stackMemUndoType.push(ENT_MEM_POSITION); pInst->m_stackMemUndoVec3.push(pInst->getPosition()); pInst->saveState();
    pInst->m_vecPosition.Get()[0] = fX; pInst->m_vecPosition.Get()[1] = fY; pInst->m_vecPosition.Get()[2] = fZ;
    pInst->setPosition(pInst->m_vecPosition);
  }, false);
       IWE_PROP_QUATERNION_S("Orientation (Quat)", m_quatOrientation,
  [pInst](float fW, float fX, float fY, float fZ) {
    pInst->m_stackMemUndoType.push(ENT_MEM_ROTATE); pInst->m_stackMemUndoQuat.push(pInst->getOrientation()); pInst->saveState();
    pInst->m_quatOrientation.Get()[0] = fW; pInst->m_quatOrientation.Get()[1] = fX; pInst->m_quatOrientation.Get()[2] = fY; pInst->m_quatOrientation.Get()[3] = fZ;
    pInst->setOrientation(pInst->m_quatOrientation);
  }, false);

     IWE_PROP_LEVEL_END();
    IWE_IMPLEMENT_PROP_END()

We are finding an simplified way to this, without leading confusing the programmers, (will be released to the public) i find ways to achieve this but they are only available for the common scripting as Lua or editors using C#. also too portable, we can write "wrappers" for different GUI toolkits as Qt or GTK, also i'm thinking to using Boost.Wave to get additional macro functionality without creating my own compiler.

The properties designed to use in the editor they are removed in the game since the save file contains their data and loads it using an simple 'load' function to reduce unnecessary code bloat may will be useful if some GameObject property wants to be hidden instead.

In summary, there's a way to implement an reflection(property) system for a level editor based in properties from derived classes?

Also we can use C++11 and Boost (restricted only to Wave and PropertyTree)

\$\endgroup\$
2
  • \$\begingroup\$ What exactly is your question here? \$\endgroup\$
    – user1430
    Jun 6, 2014 at 22:20
  • \$\begingroup\$ Damn, i forgot it. i'm a searching a way to implement an property system for a world editor (reflection) \$\endgroup\$ Jun 6, 2014 at 22:31

2 Answers 2

8
\$\begingroup\$

This is an incredibly complicated problem. I had an old series on blog about it but (a) I no longer recommend that approach and (b) old posts on my blog can't be read easily due to a snafu with a plugin and me not having time to fix it all.

The gist of what you need to do on the "simple" end is to keep a table of IProperty objects which are specialized to the type of property you're binding. Accepting a very limited set of property types will help.

It's not trivial to do. You need to be comfortable with advanced C++ including writing templates, partial specializations, pointers to members, and so on.

// common parts of a property; probably needs a name and description for editor reflection
class IProperty {
public:
  virtual void ReadFromString(void* object, const string& value) = 0;
  virtual void WriteToString(const void* object, string& out) = 0;
};

// handle simple types like int, float, etc. supported by lexical_cast
template <typename ObjectT, PropertyT>
class ScalarProperty : public IProperty {
  using PointerT = ObjectT::*PropertyT;
  PointerT _pointer;
public:
  ScalarProperty(PointerT pointer) : _pointer(pointer) {}
  virtual void ReadFromString(void* object, const string& value) override {
    ObjectT* typed = static_cast<ObjectT*>(object);
    typed->*_pointer = lexical_cast<PropertyT>(value);
  }
  virtual void WriteToString(const void* object, string& out) override {
    ObjectT* typed = static_cast<ObjectT*>(object);
    out = to_string(typed->*_pointer);
  }
};

// call make_property(&MyObjecet::m_property) to bind a particular int property
template <typename ObjectT>
unique_ptr<IProperty>make_property(ObjectT::*int pointer) {
  return make_scoped<ScalarProperty<ObjectT, int>>(pointer);
}
// do this for float, etc. you can use SFINAE, a macro, or so on to get all the versions you need

That covers the harder part. The rest is making a class that encapsulates a type and pushing all the IProperty handles into it. You can use macros to wrap this up, a custom registration function, etc.

Another popular approach that many engines use is to use a special preprocessor to generated reflection data. You can just prefix a class's member with editable or use a macro next to the declaration like REFLECT("name", flags). This has the requirement that you have a tool capable of the subset of C++ found in declarations so that it can generate the necessary code. There are also tools like clReflect but this sadly hasn't been updated in some time.

Overall, this is just a massive pain in the butt. The ISO committee is currently investigating ways of adding compile-time reflection to C++ which will make building these kinds of systems much easier. In the meantime, though, pain and suffering.

A simpler option I've seen but never tried myself is to have a method called Reflect that uses the visitor pattern:

class IReflector {
public:
  virtual void Reflect(int& value, const char* name, const char* description) = 0;
  virtual void Reflect(Vector3d& value, const char* name, const char* description) = 0;
  virtual void Reflect(string& value, const char* name, const char* description) = 0;
};


class MyComponent {
  int foo;
  Vector3d bar;
  string name;

public:
  virtual void Reflect(IReflector& refl) {
    refl->Reflect(foo, "Foo", "The foo");
    refl->Reflect(bar, "Bar", "Bar is the place");
    refl->Reflect(name, "Name", "My name");
  }
};

You can then write different derived versions of IReflector that read in values, write out values, or even just ignore the value itself and collect the type/name/description to build up a UI table. You can generalize a little further to support use of this system without needing an instance of the object in question, keep registration of types/factories, etc., but that will get you moving.

\$\endgroup\$
4
  • \$\begingroup\$ And sorry about this "newbie" question, the visitor pattern is new for me but the refl->Property must be Reflect? and which is the best way to store that data for that function?, here some example implementation (optional)?, that could help a lot. \$\endgroup\$ Jun 7, 2014 at 5:08
  • \$\begingroup\$ That first code snippet confused me a little, i think my Macro-Lambda approach was worse, (no offense intended). \$\endgroup\$ Jun 7, 2014 at 5:12
  • \$\begingroup\$ Sean, I think in the MyComponent::Reflect method by refl->Property you actually meant refl->Reflect, no? \$\endgroup\$
    – glampert
    Jun 7, 2014 at 18:35
  • \$\begingroup\$ @CristopherIsmaelSosaAbarca: Yeah, I misspelled the last bits. As for the first example: yes, it's complicated. It is likely similar to the code that backs your macro example. If you want to understand and develop really high-end generalized reflection for C++, you're going to have to learn how to read and write moderately advanced C++ like that. \$\endgroup\$ Jun 7, 2014 at 19:10
4
\$\begingroup\$

The best way I've seen is to make use of some custom introspection. As Sean pointed out, this is pretty difficult to do on your own since it's not a direct standard C++ feature in any way. So if you're up for implementing something more advanced then read on.


I actually implemented this (not the editor, just generic properties) in an open source project. The project isn't finished, but the property part is. Here's the link. You can see that this makes heavy use a generic function wrapper to call member functions.

This lets you write code like:

Class object;
Property p = Property( &Class::gettor, &Class::settor );
p.Bind( object );
Type value = p.Get( );
p.Set( value );

The overall idea is to use templates to extract type information out of a function or member function for all the parameters, and the this pointer type. From here you can create a way to call functions in a generic manner, and enumerate type information during run-time. You can see what type the property is representing, and getting/setting the property (from an interface standpoint) is trivial. The editor can enumerate type information at run-time and detect whether it's dealing with an integer, a string, or something else and generate necessary user input windows/editor grids to modify the proper.

Storing type information is about registering information about the basic C++ types (like int, float, c-strings, etc.), and then representing more complex data by combinations of the fundamental types. In implementation this would look like making a simple TypeInfo struct to store information about a type (any information you want, for an editor you'd want name, size, etc.):

struct TypeInfo
{
    const char* name;
    u32 size;
}

A property could consist of a gettor/settor combo. Each gettor/settor would be a nice wrapper for a function (C-function or class/struct method), along with an array of TypeInfo structs to represent the type of each parameter. Another TypeInfo could be used to give details about the this pointer if a class/struct method is being used.

Run-time assertions can also be made to ensure type safety!

A simpler approach would be to instead of using a generic function wrapper, to use void *'s to access the memory of your objects directly. This can be made type-safe, and would be easier to implement. The idea here is to add a new type of introspection struct:

struct Member
{
  const char* memberName;
  u32 offset;
}

The TypeInfo struct can carry an array of Member struct, where each Member struct describes where in memory this member exists relative to the host object, and the name of the member in code. Both of these bits of data are needed to modify the value and represent it within an editor.

In all the best online resource I know for this stuff is my own source code. Like Sean (the other answer here) I do have some older articles on this topic, but they aren't the best and are old now. The introspection stuff is all in here.


There is another style of implementation that is even more difficult to achieve explained in a talk by Sergiy Migdalksiy from Valve. Instead of registering type information at run-time and funneling it through generic code, it reads C++ code and generates new C++ as a pre-compilation step. This can be used to automatically generate new property functions in whatever specific interface you desire.

\$\endgroup\$
3
  • \$\begingroup\$ Looks simple, thanks. one comment is in the TypeInfo struct will be good using a Variant?, like QVariant , or Boost.Variant? \$\endgroup\$ Jun 7, 2014 at 5:26
  • \$\begingroup\$ @CristopherIsmaelSosaAbarca I don't know what those Variants are, but you should be able to make something like the Variable class in my source code. \$\endgroup\$
    – RandyGaul
    Jun 7, 2014 at 7:12
  • \$\begingroup\$ Thanks for your repository link, seems to contains valuable resources to how create an game engine, specially the Introspection one. \$\endgroup\$ Jun 7, 2014 at 17:05

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .