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
109 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:

Solution?

The requirements here can be broken down into the following:

Requirement #1: Messages should be represented by a fixed number of parameters, and as few as possible, without ever having wasted parameters.
Requirement #2: We should avoid "…" and void* at all costs for passing information.
Requirement #3: The developer should not have to know anything about the values assigned to various message identifiers, and simply used them by identifier.
Requirement #4: Message identifiers should not clash.
Requirement #5: Messages must be able to convey any amount of data.

To meet requirement #1, we shall limit the number of parameters to just one, so it is becoming obvious that we shall be creating a message class of some sort, which we shall pass either by pointer or reference, but we won't lock ourselves down to one or the other yet. We will need a base class for all other types of messages, which I will call MGeneric (M standing for Message), and so HandleMessage and OnMessage shall either be passed a MGeneric* or an MGeneric&.

While taking care of requirement #1, we have fulfilled requirement #2 (since we shall have a sole parameter of type MGeneric* or MGeneric&, we naturally shall not have a parameter of type void* nor a "…") and requirement #5 (through inheritance, MGeneric can be subclassed to represent any amount of data we wish).

However, this leads to the question of how exactly we will access the extended data from derived classes. Naturally, this will have to occur through some sort of casting. Typically, I make use of dynamic_cast for this, and because of this choice, I favor MGeneric* over MGeneric& as the parameter type (since a failed dynamic_cast of a pointer returns the a null pointer, whereas with a reference, a bad_cast exception is thrown-so pointers are easier).

So, here's a simple implementation for MGeneric:

typedef unsigned long messageid_t;

class MGeneric
{
private:
  messageid_t m_messageid;
protected:
  void SetMessageID(messageid_t messageid){m_messageid=messageid;}
public:
  MGeneric(messageid_t messageid):m_messageid(messageid){}
  ~MGeneric(){}
  messageid_t GetMessageID(){return(m_messageid);}
};

Within MGeneric is the minimal amount of functionality needed to represent a message, namely the identifier (a messageid_t value, which is just an alias for unsigned long, a type sufficiently large enough for most purposes, but your mileage may vary). The SetMessageID member function is protected, so that only friends and derived classes may access it (for encapsulation purposes). GetMessageID is public, and will typically be the first value checked in an IMessageHandler::OnMessage function, usually in a chain of if/else if blocks.

Now we need to modify IMessageHandler::HandleMessage and IMessageHandler::OnMessage like so:

bool IMessageHandler::HandleMessage(MGeneric* pMessage,bool bDelete=true)
{
  bool bResult=false;
  if(pMessage!=0)
  {
    if(OnMessage(pMessage))
      bResult=true;
    else if(HasParent())
      bResult=GetParent()->HandleMessage(pMessage,false);
    if(bDelete) 
      delete pMessage;
  }
  return(bResult);
}

This function is now made a little more complicated by the fact that pMessage is an MGeneric*, so we have to cleanly handle null pointer values, and since the pointer is likely going to be dynamically allocated within a function call, we have to do some garbage collection (hence the bDelete parameter).

Yes, I know I made a big to-do about useless parameters, but this one isn't useless, I guarantee you. For example, consider the following. Would you rather write code like this:

pHandler->HandleMessage(new MGeneric(0));

-OR-

MGeneric message(0);
pHandler->HandleMessage(&message);

Sending a message, any message, is logically a single action, and single actions should take place on a single statement, which the first option gives you, and the second does not. There is nothing really wrong with either approach, I just prefer the former, which means I need a bDelete parameter.

The OnMessage function remains nearly the same(still virtual, still protected):

bool IMessageHandler::OnMessage(MGeneric* pMessage)
{
  return(false);
}

The only change was the parameter list. It now has but one parameter, an MGeneric*.

So we've got three of the five requirements out of the way, we still need a way of assigning unique messageid_t values, and a way to keep them hidden from the user. I do it with a class that makes use of the factory patter and singleton pattern. I call the class FMessageID:

class FMessageID
{
private:
  static messageid_t s_messageid_next=0;
  static bool s_check_overflow=false;
public:
  static messageid_t Next()
  {
    if(s_check_overflow)
    {
      if(s_messageid_next==0)
      {
        //overflow!
      }
    }
    else
    {
      s_check_overflow=true;  
    }
    return(s_messageid_next++);
  }
};

Each time the user calls FMessageID::Next(), he gets a new number, starting with 0. If, for whatever reason, it gets back around to 0 again, then an overflow occurs (typically accompanied by some sort of exception being thrown). This is taken care of my the s_check_overflow variable, which skips the overflow check only once… when 0 is first being assigned.

As for how we start to attach these messageid_t values to classes, we just add them as static members to the classes that they pertain to. For example, if we created a subclass of IMessageHandler called IApplication, and wanted to associate a messageid_t with a quit message, this is how we would go about it (ignoring all of the OTHER stuff that would go into an application class):

class IApplication: public IMessageHandler
{
private:
  static messageid_t s_MSGID_Quit=FMessageID::Next();
public:
  static messageid_t MSGID_Quit(){return(s_MSGID_Quit);}
};

We make the actual variable private, so that it cannot be interfered with accidentally, and then access the value from the static member function MSGID_Quit, making it a read-only property very effectively.

As for what the actual value of IApplication::s_MSGID_Quit, I have no idea, and more importantly, I don't care. It could be 0, it could be 500, and it doesn't really matter. As long as it is uniquely identified by a call to IApplication::MSGID_Quit(), I'm happy.

Other Uses

Truth be told, the solution I have presented here was not originally implemented for messages. It was developed as a solution for exception classes. The solution looks rather a lot like MGeneric and FMessageID(so I'll spare you the code, and just fill in what is different), with the corresponding classes being EGeneric and FErrorID and errorid_t is used instead of messageid_t. In addition, EGeneric contains an additional member of type std::string that contains a textual description of the exception. For most exceptions, a simple error id and a string is all that is needed, but of course inheritance can extend them.

Additional uses I have found for this pattern are useful in AI. Imagine a generic action class called AGeneric, with actionid_t values (assigned, of course, by the FActionID class) such as AXNID_North, AXNID_South and so on. Also imagine a generic goal class, GGeneric, with goalid_t values assigned by the FGoalID class, representing both types of goals (like GOALID_Kill), priorities of goals, and extended information about the goal (like who to kill). Goals can then be placed into priority queues, and used to decide on a sequence of actions.

Conclusion

In my lexicon of design patterns, the one that comes closest to doing what this one does is Command, but I sort of think of it as being called "Variant". I have found it to be a useful pattern, and I imagine others would as well, and so I'm sharing it.


Contents
  The Issue
  Solution

  Printable version
  Discuss this article