Inside niven Part. I
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.
RTTI
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.
Object handling
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 iObject*
. 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.