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.