Nebula
|
The Nebula Core subsystem (as the name implies) implements the core concepts of Nebula which are:
Nebula implements a basic object model which implements the following features on top of the C++ object model:
The first decision when implementing a new class should be whether the new class should be derived from the Core::RefCounted class or whether it should be a traditional C++ class. The following points should help to find the answer:
Deriving from the Core::RefCounted class implies some restrictions:
To make use of the Nebula object model features, one needs to derive from the Core::RefCounted class and annotate the new class with some additional information in the class declaration and in the header file:
A normal RefCounted-derived class declaration usually looks like this:
Notice the DeclareClass() macro, the default constructor and the virtual destructor and the RegisterClass() macro outside of the class declaration. The DeclareClass() macro adds some minimal Nebula-specific information to the class declaration for the RTTI and factory mechanism. The DeclareClass() macro generally hides the internals of the Nebula object model from the programmer, so that (hopefully), internals of the object model can be changed without affecting existing classes. The RegisterClass() macro is optional and registers the class with the central factory object. If you know that objects of this class will never be created by string class name or fourcc code, the RegisterClass() macro can be omitted.
The .cc side of the class needs to contain the following Nebula specific information:
The ImplementClass() macro registers the class with the RTTI mechanism, the first parameter describes the C++ class name (note that the namespace must be present here. The second parameter is the class fourcc code, which must be unique across all classes (you'll get a runtime error at application startup if two classes try to register with the same fourcc code). The third parameter is the C++ class name of the parent class. This is used by the RTTI mechanism to reconstruct the class tree.
Nebula uses traditional refcounting to manage the lifetime of its objects. A templated smart pointer class Ptr<> exists to hide the refcounting details from the programmer. As a general rule of thumb, always use smart pointers to point to RefCounted-derived objects unless you can make sure that within a given code block, the refcount of an object will not change.
Smart pointers have a number of advantages over plain pointers:
There are also some disadvantages with smart pointers:
Nebula objects that are derived from Core::RefCounted can be created in 3 different ways:
Directly through the static create method:
The static Create() method is added to the class through the DeclareClass() macro described before. This is basically just syntactic sugar for the C++ operator::new(). In fact, the Create() method is nothing more then an inline method with a call to the new operator inside. Also note the correct use of a smart pointer to hold the new object.
Another way to create a Nebula method is by class name:
Creating an object by its string class name is useful if you don't know the object class at compile time, which is usually the case when serialized objects are restored, or when some sort of scripting interface is used. Note the type cast. This is necessary because the factory Create() method returns a generic pointer to a Core::RefCounted object.
A variation of the create-by-class-name method is to create the object by its class fourcc code:
This method looks less intuitive, but it is often faster as create-by-name and the fourcc class identifier uses less space (4 bytes) then the string class name, which may be of advantage when objects are encoded/decoded to and from binary streams.
The Nebula RTTI system gives you access to an objects class type at runtime and lets you check whether an object is the exact instance of a class, or an instance of a derived class. You can also get the class name or the class fourcc identifier directly from an object. All this functionality is implemented behind the scenes in the DeclareClass() and ImplementClass() macros. The RTTI mechanism is more efficient and easier to use then the RTTI mechanism in Nebula1 and Nebula2.
Here's some example code:
You can also query the central factory object whether a given class has been registered:
Many central Nebula objects are singletons, that is, an object which only exists once in the application and often is known to all other objects in the application.
Access to singleton objects can be gained through the static Instance() method, which returns a pointer to the single instance of the singleton class. The returned pointer is guaranteed to be valid. If the singleton object doesn't exist at the time the Instance() method is called, an assertion will be thrown:
You can also check for the existance of a given singleton:
Nebula provides some helper macros to implement a singleton class:
The DeclareSingleton() and ImplementSingleton() macros are similar to the DeclareClass() and ImplementClass() macros. They add some static methods to the class (namely the Instance() and HasInstance() methods). The constructor and destructor of the class must contain a ConstructSingleton and DestructSingleton macros. ConstructSingleton initializes a private static singleton pointer and makes sure that no other instance of the class exists (otherwise, an assertion will be thrown). DestructSingleton invalidates the static singleton pointer.
Access to singletons is by default thread-local. This means that a singleton created in one thread of a Nebula application isn't accessible from another thread.
One of the design goals of the Nebula Core Layer was to reduce the memory footprint of low level code to make the system better suited for small host platforms like handheld consoles (and a small memory footprint doesn't hurt on bigger platforms either). Here are some points how these goals are accomplished:
Here are some timings for creating a million RefCounted objects by the 3 different ways. These timings are on a notebook with Intel Pentium M running at 800 MHz: