Okay, so, there are two components at the core: The class Catalogue that holds information about all registered classes, and the registration mechanism which is provided by two macros. The two macros are required and the reason we don't have to hack our own compiler together, there is one for the interface part of your class and one for the implementation side which provides all the glue code that is responsible for actually registering your class with the Catalogue.

All the information we have of the class is gathered at compile time through C++11's type traits, so we know what your class supports and how it can be constructed, so all it boils down to at runtime is calling a method on the class catalogue that will dissect the classes name into its namespace and store it. That is done once at startup when all static initializer run and is the only runtime overhead this system has.

When that is done, the Catalogue knows about your class, its superclass, what things it supports (serialisation, copying, default construction...) and it's querieable from the Catalogue, for example, in Downpour we query all registered classes and look if it inherits from the SceneNode class at some to then display it in the node class picker on the left side. Here is an example that uses the Sun class we have in our Testgame, but doesn't actually need to see the Sun interface header at any point to work!

Code:
RN::MetaClass *meta = RN::Catalogue::GetSharedInstance()->GetClassWithName("TG::Sun");
RN::Object *object = meta->Construct();

object->SetValueForKey(RN::Number::WithFloat(1000), "time");


(Real code should probably add some error checking)

Okay, so that's the general idea and there is nothing really exciting about that. More exciting is setting the time without actually knowing the setter!

On top of that systems sits the KVO and KVC layer of the Object class, this layer provides, at a very high level, the "SetValueForKey(), GetValueForKey(), AddObserver() and GetPropertiesForClass()" methods. They allow setting, getting and observing properties of instances at runtime, for example, the decal system uses that to automatically adapt itself to the underlying geometry, by simply adding itself as an observer for the objects transform it is placed on.

This system requires a bit of manual work, for two reasons:
1) We don't have a compiler
2) Not every property should be exposed and there needs to be a way to annotate what is exported and what isn't.

What you need to do is to wrap all your properties in the Observable class, for example:
Code:
class MyClass : public ...
{
public:
	...

private:
	RN::Observable<float> _myProperty;	
};



At construction time you can initialize the property with a name and additionally your own getters and setters for the property. You can also set wether it is read-only, atomic and what memory model it should use (if the observable wraps an Object). Right now, this has to be done at runtime which adds a bit of overhead, but I'm planning on moving that to compile time with template annotations.

Anyways, underlying of this is a Signal and glue code that provides high level setters and getters that take Object instances and convert them to the wrapped type (the example sets an RN::Number instance which is then unpacked into a float by the observable before being passed to the actual setter, similarly, the getters first pack the value into the appropriate RN::Object instance). This allows one interface that can serve all types of types, more complex objects like structs are wrapped in RN::Value instances.

The signal is emitted every time the value of the wrapped object changes, and provides the observable part. You can observe a property and request the runtime to provide you with the old, new and/or initial value so you can trace any change made to the object. Of course, this also works when setting the property directly, without the getter and setter part, and there is a fast path for when no observer is present so it only adds a very minimal runtime overhead when the feature isn't used.

Of course, there is also a query interface to get the exported properties. Downpour walks down the class hierarchy of an object and queries the exported properties for that class, to then display it in the inspector on the right side. Of course, it also adds itself as an observer of the properties. So when a property is updated over the network, the inspector automatically updates itself without the two systems having to know of each other.

Edit: I should note that non of this is a requirement. Everything will function even if your class doesn't participate in the class Catalogue or the property system. Some things won't work, but only directly affecting that class. For example, a SceneNode subclass that doesn't define itself in the class catalogue is still correctly identified as a SceneNode subclass.

Last edited by JustSid; 04/17/14 19:43.

Shitlord by trade and passion. Graphics programmer at Laminar Research.
I write blog posts at feresignum.com