11-14-07 - 3

Some thoughts on metadata and reflection. So far I'm just really happy with the cblib system for prefs and reflection, and I was thinking about why exactly. Maybe it's just cuz I designed it and I'm an egomaniacal programmer like everyone who loves their own work way too much. I'm going to compare to the standard alternative, a data-based metadata system. The data-based system basically stores a list of the members with typeinfo & offsets for each marked up class. This is pretty standard and what we had at Oddworld. (The other common method is an autogenerated metadata system which is often done by putting a comment on the line with the variables; the autogen just creates the data definition for you though, so this isn't really a different system at all, though it does have some minor advantages).

BTW the "Reflection" method I'm talking about is an "imperative templated visitor" system. It's probably easiest to look at the code in cblib to tell what's really going on. Note that there's absolutly zero code to implement the "Reflection system". It's simply a policy for client classes that want to be reflected and for the functors that want to visit them.

First of all, if your metadata system is designed well it's about the same amount of work for the client in either case. In an autogen system you have to comment the members with whether they get metadata or not. At the end of the Oddworld system we had nice macros to just list all the members and it would create the right metadata through template type detection. The reflection system is about the same, you just call reflect on all the members the template finds the type.

Basically the reflection system will have client code that looks like this :

	void MyClass::Reflection(T & functor)

While the Metadata system will have client code that looks like this :


In either case you could also add info about valid ranges and descriptions and whatever else extra markup junk you want to add.

So, what's the difference? There are two issues. One is how do you add a new type to the system, and the other is how do you handle weirdo nonstandard junk in your marked up classes.

Adding a new type in the description-based system required me to make a new metadata type for that class, or to somehow tell the system that it's the same as some previous type so you can use that description. Using templates here is an advantage already because it will autoconvert for identical types, as opposed to like a parser-based system that will struggle, but data-based or reflection-based can both use templates. Also in either case if your system is good, you should generate a reasonable compile error when you try to metadata or reflect a type that isn't supported. Actually that seems to be pretty much identical.

The big difference comes from nonstandard iterations. The "standard" case is that your class is just a bag of other types, and you iterate over each of those members and call the functor on those members. Of course reflection and metadata are identical for the standard case. The nonstandard case happens when you have some nonstandard junk.

The cool thing about the reflection system in this case is that it's "imperative" ; in Casey World we would say that the reflection way is "immediate mode" and the metadat way is "retained mode". Basically this is cool because it lets us interject code right into the member iteration.

For example you can do conditionals, like :

	void MyClass::Reflection(T & functor)
		if ( isAngular )
			length = width = height = radius;
			radius = (length+width+height)/3.0;
			angle = 2*pi;

Of course you can do stuff like this in the metadata method too but it's messy. Also, the derived-data type of fixups are normally done in a metadata system with "Finalize" type of call. The problem with that is that it's done after the metadata iteration is all done, which means that you are temporarily in a state where it's not fixed up. With the imperative method, you can fix derived data and then immediately be using it for the rest of the iteration.

For example, say you own some little other class that you don't want to bother to mark up, you can just reflect its members :

	void MyClass::Reflection(T & functor)

Basically you can do whatever you want because it's just code. More than anything, that freedom just feel liberating. You're no longer locked into a system where you "must do it like this" for it to work.

Oh yeah, the big disadvantage of the Reflection way is that it has to be in the header. IMHO that's not so horrible. It's no worse than having the members in the header. If you want to hide the members you have a pImpl thing already, or a derived class, or whatever. So you have the members and the Reflection there with the pImpl. Now, you can't get through to it with arbitrary template functors, but you can still get there with specific calls passed through a concrete class dispatcher. Anyway, an advantage of the metadata method is you can iterate around in peoples' data without even seeing their header, in fact it provides a whole alternative way to do linkage, such as named variable access if you want to go down that route (I don't approve of that).

In fact you need that dispatcher anyway so that when you call Reflection on a parent class it will get passed down to the derived class. This doesn't work automatically in C++ because you can't do virtual template functions (yuck). So you have to have a virtual that will get you down to the most-derived type. You can see such a thing in "Prefs.h" in cblib. (there are different ways to do this too if you want to do more of an RTTI type of thing with a description of the classes).

No comments:

old rants