genesis-3d_engine/Engine/foundation/core/core.dox
zhongdaohuan 6e8fbca745 genesis-3d engine version 1.3.
match the genesis editor version 1.3.0.653.
2014-05-05 14:50:33 +08:00

334 lines
14 KiB
Plaintext

/**
@namespace Core
@section Nebula3CoreSystem The Nebula3 Core Subsystem
The Nebula3 Core subsystem (as the name implies) implements the core concepts
of Nebula3 which are:
- a RefCounted base class which implements a strong ref counting mechanism
- a runtime type information system
- a templated smart pointer class Ptr<> which manages the life time of RefCounted objects
- a factory mechanism which allows to construct C++ objects from their string class name
- a central Server object which setsup a basic Nebula3 runtime environment
@subsection Nebula3ObjectModel The Nebula3 Object Model
Nebula3 implements a basic object model which implements the following
new features on top of the C++ object model:
- lifetime management by refcounting and smart pointers
- object creation by string or fourcc class identifier
- a runtime type information system
@subsection Nebula3ClassImplementation Implementing A New Nebula3 Class
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:
- if the class wants to make use of the extended Nebula3 object model
features like refcounting, RTTI, and so forth, it must be derived from
the Core::RefCounted class
- if the class is a typical small helper or utility class, like a
dynamic array class, a math vector class, or something similar, it
often does not make sense to derive from Core::RefCounted.
Deriving from the Core::RefCounted class implies some restrictions:
- RefCounted-derived objects may never be created directly in the
local C++ context as stack objects, since stack objects are lifetime-
managed by C++ (they are destroyed when the current C++ context is
left, circumventing Nebula3's refcounted lifetime management completely)
- RefCounted-derived classes only have a default constructor.
- RefCounted-derived classes must have a virtual destructor.
- RefCounted-derived classes must not be copied, since this
would confuse the refcounting mechanism.
To make use of the Nebula3 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:
@code
namespace MyNamespace
{
class MyClass : public Core::RefCounted
{
DeclareClass(MyClass);
public:
/// constructor
MyClass();
/// destructor
virtual ~MyClass();
...
};
RegisterClass(MyClass);
@endcode
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 Nebula3-specific information to the class declaration for
the RTTI and factory mechanism. The DeclareClass() macro generally hides the internals
of the Nebula3 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 Nebula3 specific
information:
@code
namespace MyNamespace
{
ImplementClass(MyNamespace::MyClass, 'MYCL', Core::RefCounted);
}
@endcode
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 macro is the class fourcc code, which must be
unique across all classes (you'll get a runtime error at application startup
if 2 classes try to register with the same fourcc code). The third argument
is the C++ class name of the parent class. This is used by the RTTI mechanism
to reconstruct the class tree.
@subsection RefCountingAndSmartPointers RefCounting And Smart Pointers
Nebula3 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:
- accessing a 0-pointer will give you an easy to debug assertion instead of a
memory fault
- you'll never have to call AddRef() or Release() on you refcounted objects (in fact
if you have, there's something seriously wrong)
- smart pointers work nicely in container classes, an array of smart pointers
instead of plain pointers eliminates all the typical lifetime management problems,
you never need to take care about releasing the objects behind the pointers, instead
the array just behaves like it would contain real C++ objects
- with smart pointers, you generally don't need to define "object ownership" as
is often the case when using plain pointers (who's responsible to delete objects,
and so on...)
There are also some disadvantages with smart pointers:
- Performance: Copying and assigning smart pointers involves calling AddRef() and/or Release() on
their objects, de-referencing a smart pointer involves an assertion-check that the
contained object pointer is valid. The resulting performance hit is usually neglibe, but
you may have to be aware of it in inner loops.
- Presumably dead objects still alive: Since objects managed by smart pointers are
only deleted when the last client gives up ownership, objects may exist longer then
intended. Often this is points to a bug. Nebula3 will notify you about any refcounting
leaks (that is, refcounting objects that still exist at application shutdown)
@subsection CreatingNebula3Objects Creating Nebula3 Objects
Nebula3 objects that are derived from Core::RefCounted can be created in 3 different
ways:
Directly through the static create method:
@code
Ptr<MyClass> myObj = MyClass::Create();
@endcode
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 Nebula3 method is by class name:
@code
using namespace Core;
Ptr<MyClass> myObj = (MyClass*) Factory::Instance()->Create("MyNamespace::MyClass");
@endcode
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:
@code
using namespace Core;
using namespace Util;
Ptr<MyClass> myObj = (MyClass*) Factory::Instance()->Create(FourCC('MYCL'));
@endcode
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.
@subsection Nebula3RTTI The Nebula3 Runtime Type Information System
The Nebula3 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:
@code
using namespace Util;
using namespace Core;
// check whether an object is instance of a specific class
if (myObj->IsInstanceOf(MyClass::RTTI))
{
// it's a MyClass object
}
// check whether an object is instance of a derived class
if (myObj->IsA(RefCounted::RTTI))
{
// it's a RefCounted instance or some RefCounted-derived instance
}
// get the class name of my object, this yields "MyNamespace::MyClass"
const String& className = myObj->GetClassName();
// get the fourcc class identifier of my object, this yields 'MYCL'
const FourCC& fourcc = myObj->GetClassFourCC();
@endcode
You can also query the central factory object whether a given class has been
registered:
@code
using namespace Core;
// check if a class has been registered by class name
if (Factory::Instance()->ClassExists("MyNamespace::MyClass"))
{
// yep, the class exists
}
// check if a class has been registered by class fourcc code
if (Factory::Instance()->ClassExists(FourCC('MYCL')))
{
// yep, the class exists
}
@endcode
@subsection Nebula3Singletons Nebula3 Singletons
Many central Nebula3 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:
@code
// obtain a pointer to the Core::Server singleton
Ptr<Core::Server> coreServer = Core::Server::Instance();
@endcode
You can also check for the existance of a given singleton:
@code
// does the Core::Server object exist?
if (Core::Server::HasInstance())
{
// yep, the core server exists
}
@endcode
Nebula3 provides some helper macros to implement a singleton class:
@code
// declare a singleton class
class MySingletonClass : public Core::RefCounted
{
DeclareClass(MySingletonClass);
DeclareSingleton(MySingletonClass);
public:
/// constructor
MySingletonClass();
/// destructor
virtual ~MySingletonClass();
...
};
// implement the singleton class
ImplementClass(MyNamespace::MySingletonClass, 'MYSC', Core::RefCounted);
ImplementSingleton(MyNamespace::MySingletonClass);
//------------------------------------------------------------------------------
/**
Implements the Singleton constructor.
*/
MySingletonClass::MySingletonClass()
{
ConstructSingleton;
}
//------------------------------------------------------------------------------
/**
Implements the Singleton destructor.
*/
MySingletonClass:~MySingletonClass()
{
DestructSingleton;
}
@endcode
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 <b>ConstructSingleton</b> and <b>DestructSingleton</b>
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 Nebula3 application isn't accessible from another thread. This
follows the "Parallel Nebulas" paradigm which simplifies multithreaded programming
a lot. The idea behind "Parallel Nebulas" is, that a typical Nebula3 application contains
of a few "fat threads" each running ideally on a separate CPU core. Fat threads implement
for instance asynchronous IO, rendering, physics, and so on. Each of those fat threads
initializes its own Nebula3 runtime, which just contains the minimal Nebula3 environment
needed to perform the Fat Threads specific task. This basically eliminates the need for
fine-grained synchronisation in almost all of the Nebula3 code and concentrates "thread-aware"
code to a few well-defined code areas which deals with communication between fat threads.
Another positive side effect of the "Parallel Nebulas" paradigm is, that a programmer
typically doesn't have to care too much about running in a multithreaded environment.
Most of the typical Nebula3 code looks just like normal singlethreaded code, yet can
still run in its own fat thread.
@subsection CorePerfAndMemConsideratins Performance And Memory Footprint Considerations
One of the design goals of the Nebula3 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:
- The RefCounted class just adds 4 bytes per-instance data for the reference count
member, Nebula2's nRoot class added >60 bytes overhead to each instance.
- The RTTI mechanism adds somewhere between 30 and 60 bytes overhead, but this is
per-class, not per instance.
- A smart pointer is just 4 bytes, just like a raw pointer. The similar Nebula2 nRef
class was 16 bytes per instance.
- Several householding structures are only allocated in debug mode, most notably
the RefCountedList, which is used to detect refcounting leaks.
Here are some timings for creating a million RefCounted objects by the 3 different ways.
These timings are on a notebool with Intel Pentium M running at 800 MHz:
- Create(): 0.29 seconds
- by FourCC: 0.65 seconds
- by class name: 1.45 seconds
*/