More friendly console objects
by James Steele · 08/05/2008 (2:33 am) · 6 comments
Introduction
As I started on a TGEA project last week, it occured to me that the while the DECLARE_CONOBJECT and IMPLEMENT_XXXXXXXXX macros used for console classes are perfectly ok, the process was a bit too involved for me. As it stands, you have (as I see it) four seperate steps in setting up a console object, which are;
1) Derive from the required console object
2) Declare the console object with the DECLARE_CONOBJECT macro
3) Declare a typedef to the parent class
4) Remember to use the IMPLEMENT_XXXXXXXX macro depending on the object type (datablock, netobject etc.)
That's ok if all you're doing is creating the occasional new console object, but I'm in the process of creating lots of new data block and net objects. I don't know about you guys, but I am a very lazy programmer, and I like to automate as much as I can possibly get away with, if it let's me focus on the task at hand.
So how can I reduce the number of steps required? The answer is quite simple. I can compress all of the above steps into one step, and do it in such away that it will be immediately clear to somebody reading the code that it is a specific type of console object.
How is this done? With C++ templates!
How the template classes are used
As a quick example, here is what a typical DataBlock class will look like when using the template for a DataBlock.
class MyNewDataBlock : public IDataBlock<MyNewDataBlock, SimDataBlock>
{
...
...
...
};And that's all she wrote. This template takes care of deriving, declaring that this is indeed a console datablock object and does away the need for seperate IMPLEMENT_XXXXXXXX macro. There's no need for anything else, not even in the .cpp file. So how does this all work? First it helps if we take a look in the console/consoleobjects.h file. This is where the DECARE_CONOBJECT and IMPLEMENT_XXXXXXXX macros live.
They look a little bit like this...
#define DECLARE_CONOBJECT(className) \
static ConcreteClassRep<className> dynClassRep; \
static AbstractClassRep* getParentStaticClassRep(); \
static AbstractClassRep* getStaticClassRep(); \
virtual AbstractClassRep* getClassRep() const
#define IMPLEMENT_CONOBJECT(className) \
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \
ConcreteClassRep<className> className::dynClassRep(#className, 0, -1, 0, className::getParentStaticClassRep())
#define IMPLEMENT_CO_NETOBJECT_V1(className) \
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \
ConcreteClassRep<className> className::dynClassRep(#className, NetClassGroupGameMask, NetClassTypeObject, 0, className::getParentStaticClassRep())
#define IMPLEMENT_CO_DATABLOCK_V1(className) \
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \
ConcreteClassRep<className> className::dynClassRep(#className, NetClassGroupGameMask, NetClassTypeDataBlock, 0, className::getParentStaticClassRep())There is nothing terribly complicated here. The DECLARE_CONOBJECT macro just declares a static member variable, some static methods to access it and a virtual method to get the type from a class instance. All of the IMPLEMENT_XXXXXXXX macros are almost the same as each other, except in the flags passed to the constructor of dynClassRep.
The only really tricky part is that the IMPLEMENT_XXXXXXXX macros pass the class name, but this is no real issue. We can use the the compilers built-in rtti for this, although it may present a few issues on non-Microsoft compilers (more on this later).
So let's dive right on in. All of the IMPLEMENT_XXXXXXXX macros share a common functionality that we can exploit in a single base template that we shall call IConsoleObject. The template looks like this
// Define the base console object template
template<typename C, typename P, int F0=0, int F1=1, int F2=0>
class IConsoleObject : public P
{
public:
typedef P Parent;
static ConcreteClassRep<C> dynClassRep;
static AbstractClassRep* getParentStaticClassRep()
{
return &P::dynClassRep;
}
static AbstractClassRep* getStaticClassRep()
{
return &dynClassRep;
}
virtual AbstractClassRep* getClassRep() const
{
return &dynClassRep;
}
};
// Instansiate the static member for the template
template<typename C, typename P, int F0, int F1, int F2>
IConsoleObject<C,P,F0,F1,F2>::ConcretClassRep<C> IConsoleObject<C,P,F0,F1,F2>::dynClassRep(typeinfo.name()+6, F0, F1, F2);There's nothing overly complicated here. The template has a bunch of arguments which are;
C - The class you are deriving
P - The parent (or base) class that you are deriving from. This must be a console object of some sort.
F0, F1, F2 - Flags passed to the dynClassRep constructor. The default values are set to those in the
IMPLEMENT_CONOBJECT macro.
I won't explain what all of the things in the template do, as this isn't what the post is about.
I explained earlier that I use the compilers built-in rtti. This is done with the typeino structure, where I get the class name from, happily relacing the #classname string insertion in the original macro. In Microsoft compilers, I can use this structure with rtti turned off, and the compiler will happily store the string returned from this in the const data section. Other GCC-based compilers are likely to complain though, so you will have to turn on RTTI and live with the extra few hundred KB in executeable size.
Datablocks and Network objects
We can just use the IConsoleObject template as it is, for DataBlocks and NetObjects. We just replace the F0, F1 and F2 parameters with the NetClassGroupGameMask, NetClassTypeDataBlock and NetClassTypeDataBlock flags. But as I noted earlier, I'm a very lazy person. Having to type in those parameters sounds like too much work for my little fingers.
Instead, I have another two templates which specificaly implement Network and Datablock objects. They look a little like this;
template<typename C, typename P>
class INetObject : public IConsoleObject<C, P, NetClassGroupGameMask, NetClassTypeObject>
{};
template<typename C, typename P>
class IDataBlock : public IConsoleObject<C, P, NetClassGroupGameMask, NetClassTypeDataBlock>
{};And that's it. I have all of these templates in a single header which is included in the relevant files in my project.
Finaly..
I know this probably seems a little bit extreme just so a programmer can avoid a few small steps in creating console
objects, but those steps can quickly add up on a large project. It also helps make it clear exactly what kind of console object somebody is dealing with, without having to hunt for for the relevant IMPLEMENT macro. You could argue that the name of the class might make it clear too, and this is true. But not everybody uses the same naming conventions, and it can be hard to enforce coding standards on a project with team members you never/rarely see face-to-face.
This is just something from my own little box of tricks that I thought I would share. From my point of view, it makes life with Torque a little bit easier and so it's well worth having.
About the author
#2
08/05/2008 (12:07 pm)
Great stuff there! Wrapping up the conobject macro declarations inside an interface is pretty swank. I don't so much mind the steps in setting up conobjects, but I think the real benefit here is the reduction of the possibilities of programmer error. Forgetting to set the parent object to the right class, or making a copy/paste error in the IMPLEMENT or DECLARE macros are all things that can bite you if you're not careful. So yeah, very nice!
#3
One thing that I would suggest is that you test to make sure this doesn't break on any of the major platforms--the entire purpose of the DECLARE_ and IMPLEMENT_ macros is to provide cross-platform compilation capability.
On first glance it doesn't appear as if there are any issues with the implementation, but it would be nice to confirm :)
08/05/2008 (3:07 pm)
This looks pretty awesome :)One thing that I would suggest is that you test to make sure this doesn't break on any of the major platforms--the entire purpose of the DECLARE_ and IMPLEMENT_ macros is to provide cross-platform compilation capability.
On first glance it doesn't appear as if there are any issues with the implementation, but it would be nice to confirm :)
#4
@Guy
Good idea! I've just done this, as well as adding the complete header file. I'll add the link to it once it's been approved.
@Stephen
You make a great point on cross-platform issues. As I said, GCC can be a bit problematic because you have to turn on RTTI in the compiler, which can bloat your exe a little bit. The only compile/link problem that might exist is because of the templated static member variables that are instansiated in the header.
I've tested something similar using a singleton pattern with GCC on a few consoles, as well as Linux and have thus far, have only run into an issue with a linker for the Nintendo DS, which was fixed within a couple of days. I doubt that anybody is going to try and port Torque to the DS though :)
08/06/2008 (12:21 am)
Thanks for the feedback guys, it's nice to be able to share something that other people can benifit from.@Guy
Good idea! I've just done this, as well as adding the complete header file. I'll add the link to it once it's been approved.
@Stephen
You make a great point on cross-platform issues. As I said, GCC can be a bit problematic because you have to turn on RTTI in the compiler, which can bloat your exe a little bit. The only compile/link problem that might exist is because of the templated static member variables that are instansiated in the header.
I've tested something similar using a singleton pattern with GCC on a few consoles, as well as Linux and have thus far, have only run into an issue with a linker for the Nintendo DS, which was fixed within a couple of days. I doubt that anybody is going to try and port Torque to the DS though :)
#5
08/12/2008 (7:44 am)
hey, how is your tanks? was tracking progress - interstimg to hear how it is going??
#6
It's funny you should mention the tanks stuff. I lost alot of my work after a big hard drive failure, so life took over and I was distracted by other things (getting married, work, life in general).
I'm working on it again, but for a project that isn't my opwn. I'll talk to the guy in charge about releasing a basic tracked vehicle resource based on it though.
08/15/2008 (2:07 pm)
@MaximIt's funny you should mention the tanks stuff. I lost alot of my work after a big hard drive failure, so life took over and I was distracted by other things (getting married, work, life in general).
I'm working on it again, but for a project that isn't my opwn. I'll talk to the guy in charge about releasing a basic tracked vehicle resource based on it though.

Torque Owner Guy Allard
Default Studio Name