This is the first of a series of articles that describe the inner workings of niven. In the first article, I’m gonna tell you something about niven’s RTTI system, object handling and other core stuff.
Niven has a custom tailored RTTI system, which I covered partially in a previous post. But let’s take a closer look at it. All classes which want to use the RTTI system have to be derived from
iObject - a abstract base class. Depending on what type of class you have, you need to add something like
DEFINE_CLASS(myClassName,myParent) to your header file and
IMPLEMENT_CLASS(myClassName, Class::Interface, myPackage) to your source file. This adds a static member to your class of type
Class which is a class that holds information about your class. Now, you can easily query the type of a class by callings its
type () function which returns the run-time type, even if called through a base class interface. If you need the type of a class (not of an instance, but for the class itself, for example, when you want to check whether an instance is of a certain type) you use the
staticType () function which returns the static type of the class.
For this one, I have to tell you a bit about how the engine worked, and why I changed it. Previously, it had a global
SingletonManager which kept track of all Singletons (classes like PluginManager, ResourceManager etc.). For example, it was used in the OpenGLDrv implementation. It was loaded using the PluginManager, which called the
Plugin->init () function. This function would then create an instance of
OpenGLRenderer and register it at the SingletonManager by using
SingletonManager::register ("Renderer", new OpenGLRenderer);. This looks allright, but the problems cropped up step by step. First of all, the SingletonManager was actually abused as it was used to keep track of 10 Singletons that needed to be loaded always anyway, i.e. it was just a global variable that kept pointer to other global variables (and you couldn’t even bet on the SingletonManager, the
getSingletonManager call could fail …). And second, you couldn’t control what happened when you loaded a plugin - and as users are also supposed to provide plugins, this was clearly the wrong way (users could do stuff they were not supposed to do, and there was no easy way to protect the core classes).
So, I decided to abandon the SingletonManager completely and put the core classes into a global
Engine class. This worked of course fine for the classes like PluginManager, but when I came to the renderer there was a problem. Previously, the renderer registered itself at the engine by calling a function from the engine - this way of self-registering was not available any longer due to the reasons above. The engine would have to ask the plugin to create a class instance or an interface instance, and then the engine would register this instance. I tried several approaches to accomplish that, including my own COM-like system (which would add a function
extern "C" API_EXPORT bool CreateInterface(const GUID& guid, iObject** object); to each Plugin, and you would load objects with those awful GUIDs), a string based factory (
extern "C" API_EXPORT bool CreateInstance (const std::string& name, const VersionInfo& minimalVersion, iObject** object)) and a few more approaches like that. The problem all of these have in common: Your createFunction needs to know about all objects it can create, and it is time-consuming and error-prone to keep it in sync.
Here is the solution I came up with finally: When you add the
IMPLEMENT_CLASS to your source file, and you do it for a non-abstract class, it adds an exported function
getClassMyClass - this function returns a pointer to the RTTI class for this class. And now the real trick: For non-abstract classes, the RTTI
Class stores a function pointer to a function that returns
DEFINE_CLASS adds a new function to the class,
static iObject* staticConstruct(); which returns a pointer to a new instance of the class. Example: You have a class
MyClass, and you call
iObject* myClassInstance = MyClass::staticConstruct ();. When you query the run-time type of
myClassInstance you’ll see it’s really an instance of
MyClass. Looks good so far? Well, there is something to keep in mind. Let’s assume you have a class with a private constructor, what happens if you call
staticConstruct()? It returns you a class instance, as it is a class member and may also access private members. Surely not what you wanted. The trick I used to overcome this is to use a special template functor class that takes care of the instancing - this template class is never a friend of your class, so you get compile-time errors when trying to add
DEFINE_CLASS to an abstract class. The error messages are useful, they tell you about not being able to create an instance of an abstract class, which should tell you to look at the documentation and use the
DEFINE_CLASS_ABSTRACT macro instead. So much for now, got some serious work to do. Next part will be hopefully about object serialization, package/plugin management, the event queue or static linkage of packages/plugins.