Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
94 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Miscellanous Utilities

Hmm, that could probably be a good name for a geek band :)

There are a few base classes that will be used from time to time across the engine; there are a few more which, while I won't cover here, use the same or similar techniques. Many of these base classes are provided by common libraries such as boost (or, in fact, the STL itself) - but I'm here to educate, and a couple of implemented design patterns never hurt anyone. It may not be totally obvious how some of these will be useful at this stage, but I will be using them all; as such, it will help if you can read, understand, and have them ready to hand in later articles.

Functors

A functor (or, to be more precise, a 'bound functor') is a way of wrapping up a function somewhere in an object. Have you ever tried to work with a pointer to a member function? You can't, for example, dereference a function pointer to CLog::Write() without a pointer to the object it needs to be called on (otherwise, what does the 'this' pointer equal?). With functors, you can wrap up the pointer to the object *and* the pointer to the member function within that object, and use the functor to call it in an easy way. So, we have our functor base class:

class Functor : public IMMObject
{
public:
   virtual void operator ()()=0;
};

Firstly, it's memory-managed, meaning that we can throw as many functors as we want around the place and the engine will clean up after us. Secondly, though, it's very obviously an abstract base class for something else. Why? Because the class we're about to derive from it is a templated class:

template<class T>
class ObjFunctor : public Functor
{
   protected:
      T *obj;
      typedef void (T::*funcType)();
      funcType func;
   public:
      AUTO_SIZE;
   
      ObjFunctor(T *o, funcType f) 
      { obj=o; func=f; }
   
      void operator ()() 
      { (obj->*func)(); }
};

That's more like it. The pointer-to-member-function type is typedef'd for easy use; the AUTO_SIZE macro makes its appearance to satify IMMObject::size(). But what's with this base->derived business? Why bother with the base class at all, and not just have ObjFunctor derived from IMMObject?

It's like this. When you create an ObjFunctor, you'll give the type of the object that it works with - going from our earlier example, ObjFunctor<CLog> will let you store pointers to functions on any CLog object. Now, let's say you want to keep a generalised list of ObjFunctor objects - say, a list of functions to call in the event that something happens - you'll find you won't be able to. Your std::list< ObjFunctor * > tells you that ObjFunctor requires a template parameter; but if you give it that, you fix the list as being a list of <CLog> function pointers, or whatever you specify. That's not much good - you want to be able to point to any function, anywhere. That's why we use the base class; you create your list as std::list< Functor * >, and then can store any ObjFunctor in it - and the fact that the () operator is virtual means that calls get passed down in the correct way to the ObjFunctor class.

Lastly, as you may have guessed from the existence of the () operator, the syntax for calling a Functor object (not a pointer, mind) is exactly the same as calling a normal function - fMyFunctor() will call whatever function fMyFunctor is bound to. If fMyFunctor is actually a pointer, rather than an object (as will often be the case), (*fMyFunctor)() will do the trick.

There's one more special case. The ObjFunctor doesn't take into account the reference-counting system; the object that it points to could be freed without its knowledge. Thus, we derive a second class from Functor:

template<class T>
class MMObjFunctor : public Functor
{
protected:
   CMMPointer<T> obj;
   typedef int (T::*funcType)();
   funcType func;
public:
   AUTO_SIZE;

   MMObjFunctor(T *o, funcType f) 
   { obj=o; func=f; }

   int operator ()() 
   { return (obj->*func)(); }
};

Near-identical, except that the obj pointer is now a CMMPointer. You won't necessarily want to use this all the time, as the other version is slightly faster.

One useful feature I tried to implement (and couldn't, because MSVC doesn't support partial specialization) was the ability to set the functor's return type. If you're using a compiler which supports it, here's a hint: all the Functor classes need to be specialized for the void return type. This is because return (obj->*func) doesn't compile if func returns void. So, you'd have Functor<class R>, and then ObjFunctor<class T, class R> (which is where MSVC breaks down, because I need to specify the 'void' for R but can't specify anything for T), and so on.

So, the Functor allows us to wrap up a function inside an object. It could be useful for, say, callback handlers when a button is pressed, in a UI system.

Singleton

I give credit for this to Scott Bilas, who presented the technique in Game Programming Gems (an excellent series of books, if I may say so).

You should already know what a singleton is (and if you don't, I apologise - my previous mentioning of the term may have confused you a little). However, it's a bit tedious (to say the least) to have to implement the same singleton code, each time you want a new class as a singleton. Ideally, there should be a Singleton base class - and with the magic of templates, there is.

template<typename T>
class Singleton
{
   static T* ms_singleton;
   public:
      Singleton()
      {
         assert(!ms_singleton);
         //use a cunning trick to get the singleton pointing to the start of the whole, rather than
         //the start of the Singleton part of the object
         int offset = (int)(T*)1 - (int)(Singleton <T>*)(T*)1;
         ms_singleton = (T*)((int)this + offset);
      }
      ~Singleton()
      {
         assert(ms_singleton);
         ms_singleton=0;
      }
      static T& GetSingleton()
      {
         assert(ms_singleton);
         return *ms_singleton;
      }
      static T* GetSingletonPtr()
      {
         assert(ms_singleton);
         return ms_singleton;
      }
};

template <typename T> T* Singleton <T>::ms_singleton = 0;

To use the singleton class, we derive a class SomeClass from Singleton<SomeClass>. One thing to note about this type of singleton is that we - not the loader - are responsible for creating the singleton and destroying it again when we're done. We create it simply by calling new SomeClass() somewhere in code - the constructor takes care of the rest, so we don't even need to store the pointer that new returns. To destroy it, we call delete SomeClass::GetSingletonPtr(); that also sets the singleton pointer back to zero, so we can recreate the singleton if we want.

The Singleton will come in useful for many key engine systems, such as the kernel or settings manager, of which we only ever want one.

Ring buffer

This one I came up with purely on my own. :)

A ring buffer is, as the name suggests, a 'ring-shaped buffer' - a circular buffer, which has no specific start or end. You create it to store a maximum number of a specific type of object, and then read and write to it like reading or writing to a stream. Obviously, the buffer has to have it's block of storage space as a plain, linear block of memory internally; but it stores read/write pointers, which it 'wraps' to the beginning of the block whenever they pass the end. Provided that the read pointer doesn't catch up to the write pointer (i.e. the buffer is empty), or vice versa (i.e. the buffer is full), then the buffer seems infinitely long. There's no time-consuming memcpy() operations involved; the only limitation is that the size must be determined at compile-time, rather than at runtime (although even that could be fixed, if you needed to).

template<class T, unsigned long bufSize>
class ringbuffer
{
   protected:
      T buf[bufSize];
      unsigned long read, write;
   public:
      ringbuffer()
      {
         read=0; write=1;
      }
      bool operator << (T &obj)
      {
         buf[write]=obj;
         ++write;
         while(write>=bufSize)write-=bufSize;
         if(write==read)
         {
            --write; //make sure this happens next time
            while(write<0)write+=bufSize;
            return false;
         }
         return true;
      }
      bool operator >> (T &res)
      {
         ++read;
         while(read>=bufSize)read-=bufSize;
         if(read==write)
         {
            ++write; //make sure this happens next time we call and the buf is still empty
            while(write>=bufSize)write-=bufSize;
            return false;
         }
         res=buf[read];
         return true;
      }
      unsigned long dataSize()
      {
         unsigned long wc=write;
         while(wc<read)wc+=bufSize;
         return wc-read-1;
      }
      void flood(const T &value)
      {
         //loop through all indices, flooding them
         //this is basically a reset
         read=0;
         for(write=0;write<bufSize;++write)
         {
            buf[write]=value;
         }
         write=1;
      }
};

So, reading and writing to the buffer is done through the >> and << operators. There's also a dataSize() function, which will tell you how many elements are available for reading, and a flood() function, which is useful for wiping the buffer (initialising all slots to a particular value).

The ring buffer will prove useful in the C/S systems, eventually. It's like a FIFO (First In First Out) queue, but it doesn't need to allocate any memory, making it quite a bit faster.

Coda: Gangsta Rappa Game Developer

(Geddit? *sigh*)

That's all for this time. Next time we'll finish the kernel layer, I think, if you're up for it - the Settings system and Task Manager / Kernel Core systems await. But now I'm going to go check my email...




Contents
  Introduction
  Memory Management
  Smart Pointers
  Error Logging
  Miscellanous Utilities

  Source code
  Printable version
  Discuss this article

The Series
  Part I
  Part II
  Part III
  Part IV
  Part V