Monday 11 November 2013

Reflection in lame engine - Part 1

Finally I found some spare time to write an article for my blog, short one I suppose, but better than none. I currently have much on my plate but when I capable I try to spent some time on my game engine. One big part of the whole thingy, which is currently really broken btw, is reflection mechanism. It took me quite some time to plan the whole thing, do research and while there are still some issues I need to solve and parts that need refactoring (or even proper implementation alltogether) the core is actually functioning and I am quite happy with it. I plan to make a series of articles as putting it all together in one piece would be mundane task for me, and probably even more painful for potential readers. The first article in the series will talk a little bit about goals, overall design, API interface and some random stuff.


Ranting about what reflection is and why is it so cool is kinda pointless and was done multiple times in different places, therefore I will focus more on approach I have taken. 

The goals were actually pretty simple:
  • Portable and intrusive system with manual registration - There are options like scanning pdb files (for Visual Studio thus Windows and XBox only) or even some pre-processing of headers to generate the meta-code but seriously I find writing code for registration even better practice as It gives me more control on what and how is being reflected. Anyways, even with automatic or semi-automatic systems you have to mark teh codez with some sort of special syntax for preprocessing. I see a logic failure here. If you don't need any control  on what and how something is being reflected and exposed, then such solutions are nice and dandy I guess.
  • Efficiency of operations - this is meant to be used extensively throughout the whole engine so it must be fast. Real-time systems are supposed to be fast. What I mean here is also const-time dynamic_cast, querying types for implemented interfaces and checking if type is ancestor of another.
  • Minimize memory overhead as much as possible - I don't want to clunk the memory with unnecessary meta-data therefore type information is split into two parts, obligatory one that contains information such as type name as string, size class GUID and CCID (more on that later) and second - optional part which in turn contains information about methods, properties, fields, constructors etc. My approach is template based and templates are really happy with polluting your code. This in turn might lead to code-bloat which can be minimized with various tricks I had to implement.
  • No typeid, dynamic_cast, exceptions and stuff like that - actually both are disabled in the project and doing reflection while using built-in typeid and dynamic_cast makes little sense I guess?
  • Cute syntax - because I like unicorns pissing rainbows in my codebase and unfortunately this part is which I still hammer hard as I am not 100% happy with it, although monsters have been slained in the process and as a result it is not so wanky as some of the libraries I saw while researching the topic.
  • No dynamic memory allocations - this one was crazy and for quite some time I was doing fine but at one point I gave up with this one as it was not worth the complications it introduced. Reflection meta data (at least with my current implementation) are created at some point during engine initialization - I don't relay too much on static initialization and pre-main stuff for a couple of reasons which I won't go into at this point - and it lives for the entirety of process life-time so I guess I am not doing something that terrible allocating stuff here and there. However, this one is a subject for future changes that I am planning.

So now a little bit about the first part - how to actually achieve const-time operations. The trick I read somewhere in regard to prime numbers. If you make a product of two prime numbers, the result will be divisible only by itself, one - this is the obvious part - and prime factors that were part of the product, which is the essential part. If you use 3 primes or N primes the outcome is the same - the product divides by each of the primes used. So this is really nice property that can be used to assemble an ID for any type given. You assign a unique prime number to each class (in my case named GUID) and you calculate a value, lets name it CCID (Composite Class ID) gathering all GUID's from base classes and implemented interfaces and just multiplying the factors. There is a problem though - since prime numbers grow really fast, and products of prime numbers require a lot of bytes to represent such values. I decided to use 64 bytes variable to store the CCID and it works fine until you plan to add shitloads of classes.Of course 64 bytes CCID still requires some mambo-jumbo related to GUID assigment as you have to deduce what class should receive what primes. Classes that are bases for a lot of other types or interfaces that are frequently specialized should get the lowest prime possible, as their product will yield the lowest possible CCID. This is the overall idea - I will skip the implementation details for next posts in the series.

I mentioned that the implementation is heavily based on templates and meta-programming. Macros are nothing uncommon either, therefore the problem of code bloat due to template specialization and code substitution was a real threat. I try to minimize the damage by using idiom I encourage every beginning C++ developer toying with templates to use - Thin templates.

There are a lot of things I didn't even mention or started to explain so the reader might feel disappointed but I promised to keep this short so enough is enough and I will end this post with a really shitty cliffhanger (for those who watched Dexter finale, even the best might end up badly ;)

This is an example of registration code (not showing a lot of stuff  but I am out of time and without a better example at the moment) - the details and some implementation stuff next time guys (or gals):

CLASS(Component)
{
  MetaBuilder::Emit<Component>()
    [UIReadOnly, ScriptHidden]
    ->AddProperty("Id", &GetId)
    ->AddProperty("Hash", &GetHash)
    ->AddProperty("Name", &GetName, &SetName)
    ->AddProperty("Enabled", &IsEnabled, &SetEnabled)
    ->AddProperty("Dead", &IsDead, &SetDead);
    [UIEntityPicker]
    ->AddProperty("Owner", &GetOwner, &SetOwner)
}

No comments:

Post a Comment