Published: 2015-11-18 | Category: [»] Programming.

There has been a trend in the gaming industry for about a decade now to step away from the traditional inheritance programming paradigm to describe game entities and to replace them by a component based system. You can read Bjarne Rene article “Component Based Object Management” in Game Programming Gems 5 (Charles River Media, 2005) for an introduction on the topic.

Basically, game entities describe anything that belongs to the gaming world. They can be evil monsters, treasure chests, lights, ambience sounds, some artificial intelligence triggers for cut scenes… Traditionally, game entities were programmed with a hierarchy of classes which related to their behaviours: you had a master class “Entity” from which derived the “physical” and “non-physical” groups,which themselves divides as “moving” and “non-moving” for the physical branch, “actionable” and “non-actionable”, “with intelligence” and “without intelligence” and so on. Regrouping similar features through hierarchy allowed saving repetition of source code for entities that had the same behaviour. It was also more easy to manage the code that way (imagine there is a bug in the motion of one hundred entities and that you have to update one hundred time the same source code). However, as the complexity of games increased, the programmers started hitting the limits of such hierarchy systems. Namely, how do we implement entities that share behaviours of different branches of the hierarchy tree? And while it is possible to minimize troubles by thinking carefully on how you divide the behaviours into groups, it was often necessary to refactor the hierarchy tree as the game design evolved. At the end, programmers often came up with gigantic classes that could handle every possible situation. This works but does not make good usage of programming patterns whose purpose is to save time and ease code management.

The answer to these increasing problems was the component based system. Here, no more hierarchy: game entities are described as collections of behaviours which are implemented into components. Components are also very specific but highly re-usable. For instance, you have a component handling the health-point/damage behaviour, a component handling the fact that the entity has a physical position and/or orientation in the world, a component for rendering, a component for artificial intelligence and so on. Actually, components are even more specific than that. Imagine, for example, that you have a component for “patrol & seeking target AI” and a component “chase target AI”. At any moment, you can detach one component and attach another to change the behaviour of your entity from patrolling to chasing a target. The same is true for rendering with rendering of 3D models, rendering of sprites, rendering of 3D models with shadows, glows… The goal here is to be as specific as possible.

Such a system is extremely flexible and not only does it allow you to change the behaviour of entities in real-time by loading/unloading components, it also enables you to describe entirely entities by meta-data such as a text file including the components required for the entity and the default parameters (such as life, speed, model file…). And because each component performs individually, you can include new components from dynamic link library (.dll) files, eventually without having to disclose your source code.

There are various ways to implement a component based system in terms of programming paradigms and the first people will usually think of is through multiple inheritances. However, not all programming language supports multiple inheritances and it cannot also produce the dynamic, run-time, behaviour that I described previously. In the past, I have also tried using the Proxy pattern to alter the behaviour of passed functions but I really do not recommend it as it was neither stable nor practical to use. What most people use today, and which is also what Bjarne Rene uses in his article, are has-a relations through entities having collections of objects deriving from a master component interface. This works pretty well, and if you add this to the possibility of [»] instancing objects from strings that I presented some time ago, you get a really powerful tool.

However, there is one glitch in this perfect plan: how do components exchange information with each other? Components are designed as isolated ultra-specific behaviours but they exist in a world where they are a part of a whole. For example, the rendering component needs to know the position of the entity to render it at the proper location; the AI system may need to access the remaining health points to do the most pertinent action (such as not chasing a level 99+ warrior when it has only 1 HP left!) and so on. And it is the precise point here where I beg to differ with Bjarne Rene approach.

In Bjarne Rene approach, to access data located in another component you first locate the component (if it exists!), raw cast a pointer to it and directly read the information through conventional accessing technique such as pComponent->getHP();. But this is a really the last thing you want to do to your component system because it violates the encapsulation principle. To do the raw cast you need to have at least some information on the component specific interface (getHP function in our example) to do this. It may seem like a little trouble, but we are actually losing a lot of flexibility that way.

A better approach can already be found in Matthew Harmon article “A System for Managing Game Entities” in Game Programming Gems 4 (Charles River Media, 2004). In his system, entities communicate with each other by sending messages such as pEntity->sendMessage(MSG_GET_HP); where MSG_GET_HP would be a unique identifier, usually in the form of an integer for speed issues. It also allows every entity to interpret messages as they would like, or even to discard them if not applicable. For example, sending the “update” message to all entities would result in rendering the renderable components, updating the AI of AI component and so on.

I have been working with such systems for a very long time and it works pretty fine. If you have worked with the Half-Life SDK, you will also remember that it was the way used to exchange information between client and server sides so it would not be a too big burden for you to adapt this paradigm for inter-component communication.

Although the system works fine, it also has its downside. Here, it is in how parameters of the messages are handled. In Matthew Harmon approach (and this was also the case in the Half-Life SDK), parameters are sent as chunks of raw data which had to be encoded by the emitter and decoded by the receiving side. This requires having a precise knowledge on the communication packet structure. Actually, we have moved the encapsulation knowledge required from the class to the message packet. This is already much better but it is really pernicious in the long way because you will start to get problems after your game has reached a certain level of complexity. Proper documentation and code follow-up is mandatory to succeed with messaging.

Recently, I then decided to modify the approach of dealing with messages to make it easier for the programmer by adding one more layer of abstraction. The price to pay for abstraction is, on the other-hand and as always, a slower execution time due to added overhead. While this could be an issue if you were working on the next AAA game, the programmer-friendly benefits that it brings surpasses the eventual downsides of slower code execution for smaller amateur projects. Sorry Ubisoft, this won’t work for the next Assassins Creed Vs. Predator Colonial Marines ;-) Jokes put aside, I have done some early test applications with this technology and it performs really well so I would definitively recommend it for the amateurs.

The overall idea is to implement messages as class-deriving objects. That way, we simply send copy of objects and share the interface of the messages classes to other components. One of the nice advantages it has, is that we can implement the reception of the message directly using functions like:

void receiveMessage(const msgUpdate& rMsg) { //... }

without having to dispatch all the messages from a master message receive function. Also, in the future, automatic packing and unpacking functions could be added to the messages classes such that they can travel through a networking system. I did not cover these aspects so far because multiplayer applications bring a lot more trouble into the game and I already have a lot issues to solve at the moment with my single player game (such as “how to finish my game before 2050” that we all know so well).

The post here will now focus on how to implement the messaging abstraction layer. Please note that messaging can be implemented in any system and not only between game entities and components. For example, you can use the very same messaging system for your user interface or hardware (keyboard/mouse/joypad) management.

In this system, messagees exchange messages through a message manager. Messagees derive from a MessageableObject interface (itself deriving from a MessageableObjectBase class) and messages derive from an IMessage interface. Game object components themselves will then derives from the MessageableObject interface and from an IComponent interface (itself deriving from IInstanciableObject interface and IStorableObject interface. The role of these is to implement the [»] instantiation by string and [»] serialization functionalities that I covered in earlier posts). I will not cover them here. Entities also derive from the MessageableObject interface and configure all its components to share the same messagee id such that they all receive copies of the messages begin send to a given entity. That is an important point because we are addressing messages to entities and not exactly to individual components. Finally, the World master class which contains all the entities is itself a messagee and possesses a MessageManager class which stores a list of all the current messagees in the system and their ids. If you would like to have different messaging systems, simply create several message managers (by the way, this is the reason why the message manager was not implemented as a singleton). A diagram is presented on Figure 1.

Figure 1 - Classes dependencies

Let us now dig a bit further in the actual implementations of these classes. Let us start with the Message class:

/* message interface */ class IMessage { public: virtual ~IMessage(void) {} /* return message type ID */ virtual unsigned long getUniqueID(void) const = 0; }; template<typename Type> class Message : public IMessage { public: static const unsigned long ulUniqueID; virtual unsigned long getUniqueID(void) const { return ulUniqueID; } Type& reply(void) const { return const_cast<Type&>(*(const Type*)this); } }; /* register type to message manager */ #define REGISTER_MESSAGE(Class) const unsigned long Message<Class> :: ulUniqueID = (MessageTypesManager :: getInstance())->genUniqueID(#Class)

As you can see, the Message class is itself based on the IMessage interface which implements a virtual function whose role is to return a unique identifier for each message object type. The Message class is actually a CRTP (Curiously Recurring Template Pattern) template such that we can write:

class msgTest : public Message<msgTest> { public: msgTest(void) { } }; REGISTER_MESSAGE(msgTest);

If you are not familiar with the CRTP pattern, take the time to grab some info on it because it will be very helpful in this article. Concerning the reply function, do not pay too much attention to it as it serves basically as a macro to convert a const reference into something mutable in case we need to write something into the object (usually, in the context of a reply message).

When the REGISTER_MESSAGE macro is called, it invokes a MessageTypesManager class, which is implemented as a singleton, to generate a unique identifier for that class. I will come back on the usefulness of identifiers later. The code for the MessageTypesManager class is a bit long but all it does is to update a hashmap:

#define UNKNOWN_MESSAGE 0 class MessageTypesManager { public: ~MessageTypesManager(void) { } /* create a unique id for message type using an hash map */ unsigned long genUniqueID(const char *pszMessageName) { unsigned long ulUniqueID = getMessageUniqueID(pszMessageName); if(ulUniqueID != UNKNOWN_MESSAGE) return ulUniqueID; ulUniqueID = ++this->m_ulUniqueIDGenerator; this->m_cHashMap.add(pszMessageName, ulUniqueID); return ulUniqueID; } /* retrieve unique id for a given message type through the hash map */ unsigned long getMessageUniqueID(const char *pszMessageName) { try { return this->m_cHashMap.get(pszMessageName); } catch(...) {} return UNKNOWN_MESSAGE; } static MessageTypesManager *pInstance; static MessageTypesManager *getInstance(void) { if(pInstance == null) pInstance = new MessageTypesManager(); return pInstance; } private: MessageTypesManager(void) { this->m_ulUniqueIDGenerator = UNKNOWN_MESSAGE; } unsigned long m_ulUniqueIDGenerator; HashMap<unsigned long> m_cHashMap; }; MessageTypesManager *MessageTypesManager :: pInstance = null; class AutoMessageTypesManager { public: AutoMessageTypesManager(void) { MessageTypesManager :: getInstance(); } ~AutoMessageTypesManager(void) { delete MessageTypesManager :: pInstance; MessageTypesManager :: pInstance = null; } }; AutoMessageTypesManager g_cAutoMessageTypesManager;

And now comes the fun: the MessageableObject class. Like for the Message class, it is a CRTP template deriving from the MessageableObjectBase class which actually contains a lot more feature. The purpose of this CRTP class is to allow the programmer to have one receive function per message, like in our previous example. To do this, the MessageableObjectBase class contains a list of message handlers deriving from an interface IMessageHandler which contains a virtual function whose job is to “handle” a message upon reception. What the MessagableObject CRTP class does is to define a custom message handler deriving from that interface whose handle function actually calls a callback function named onReceive but whose parameter type is different for each message types. The compiler then does all the rest for us by detailing all these functions during the template unwrapping at compilation.

/* * CRTP idiom required to implement message handling. * Messageable objects should derive from themselves through this template. */ template<class ClassType> class MessageableObject : public MessageableObjectBase { protected: MessageableObject(void) { } MessageableObject(MessageManager *pMessageManager) : MessageableObjectBase(pMessageManager) { } MessageableObject(MessageManager *pMessageManager, messagee_id ulMessageeHook) : MessageableObjectBase(pMessageManager, ulMessageeHook) { } virtual ~MessageableObject(void) { } template<typename MsgType> void addMessageHandler(ClassType *pObject, void (ClassType::*pFuncPtr)(messagee_id, const MsgType&)) { MessageableObjectBase::addMessageHandler(new CustomMessageHandler<MsgType>(pObject, pFuncPtr)); } private: template<typename MsgType> class CustomMessageHandler : public IMessageHandler { typedef void (ClassType::*pfnMessageHandlerCallback)(messagee_id, const MsgType&); public: CustomMessageHandler(ClassType *pObject, pfnMessageHandlerCallback pCallback) { this->m_pObject = pObject; this->m_pCallback = pCallback; } /* dynamic_cast approach is too slow and don't handle inherited message very well. Use Message Type ID instead. */ virtual bool handle(messagee_id ulSenderID, IMessage &rMessage) { if(rMessage.getUniqueID() != MsgType :: ulUniqueID) return false; MsgType *pCast = static_cast<MsgType*>(&rMessage); (this->m_pObject->*this->m_pCallback)(ulSenderID, *pCast); return true; /* MsgType *pCast = null; try { pCast = dynamic_cast<MsgType*>(pMessage); } catch(...) {} if(pCast == null) return false; (this->m_pObject->*this->m_pCallback)(*pCast); return true; */ } private: ClassType *m_pObject; pfnMessageHandlerCallback m_pCallback; }; }; /* macro to help code writing repetitions */ #define HANDLE_MESSAGE(MsgType, ClassType) addMessageHandler<MsgType>(this, &ClassType::onReceive) #define ON_RECEIVE(Class) void onReceive(messagee_id ulSenderID, const Class &rMsg)

which allows us to write code as simple as:

class gocTest : public MessageableObject<gocTest> { public: ON_RECEIVE(msgTest) { printf("gocTest received test message.\r\n"); } gocTest(void) { HANDLE_MESSAGE(msgTest, gocTest); } };

Now if you look at the “handle” function in the custom message handler class, you see that I have commented out a few lines. This was the former message identification mechanism and was based on dynamic casts. Although it is conceptually better, it performed poorly as it was both too slow and unable to distinguish messages which had inheritance relationship between them. This is normal as you can always dynamic cast a child to its parent. To get better and faster results, I have then replaced the dynamic cast by a static cast and a check based on the message identifiers that I was talking about previously. If you wish to use the dynamic cast version, you can remove the message registering features and the message types manager.

The MessageableObjectBase class is a bit longer but it does mainly a lot of book-keeping. The important thing to note is that it registers itself into the message manager upon creation and maintains the message handlers list. On deletion, it frees itself from the message manager. This will give any messageable object an identifier as soon as it is created, without requiring the programmer to specify anything.

/* base class for types that accept messages */ class MessageableObjectBase { friend class MessageManager; public: MessageableObjectBase(void) { this->m_pMessageManager = null; this->m_ulMnemonic = INVALID_MESSAGEE; } MessageableObjectBase(MessageManager *pMessageManager) { this->m_pMessageManager = null; this->m_ulMnemonic = INVALID_MESSAGEE; assign(pMessageManager); } MessageableObjectBase(MessageManager *pMessageManager, messagee_id ulHook) { this->m_pMessageManager = null; this->m_ulMnemonic = INVALID_MESSAGEE; assignHook(pMessageManager, ulHook); } virtual ~MessageableObjectBase(void) { clearMessagee(); } /* each object has a unique messagee id */ messagee_id getMessageeID(void) const { return this->m_ulMessageeID; } protected: void clearMessagee(void); void assign(MessageManager *pMessageManager); void assignHook(MessageManager *pMessageManager, messagee_id ulHook); bool sendMessage(messagee_id ulRecipientID, IMessage &rMessage); void registerMnemonic(messagee_id ulMnemonic); MessageManager *getMessageManager(void) { return this->m_pMessageManager; } /* add a message handler to the list */ void addMessageHandler(IMessageHandler *pMessageHandler) { size_t nSize = this->m_pMessageHandlers.getSize(); for(size_t i=0;i<nSize;i++) { if(this->m_pMessageHandlers[i] != null) continue; this->m_pMessageHandlers[i] = pMessageHandler; return; } this->; this->m_pMessageHandlers[nSize] = pMessageHandler; } /* remove a given message handler from the list */ void removeMessageHandler(IMessageHandler *pMessageHandler) { for(size_t i=0;i<this->m_pMessageHandlers.getSize();i++) if(this->m_pMessageHandlers[i] == pMessageHandler) { delete this->m_pMessageHandlers[i]; this->m_pMessageHandlers[i] = null; return; } } private: /* Send incomming message to all message handlers in the list. */ bool dispatchMessage(messagee_id ulSenderID, IMessage &rMessage) { for(size_t i=0;i<this->m_pMessageHandlers.getSize();i++) if(this->m_pMessageHandlers[i] != null && this->m_pMessageHandlers[i]->handle(ulSenderID, rMessage)) return true; return false; } MessageManager *m_pMessageManager; Array<IMessageHandler*> m_pMessageHandlers; messagee_id m_ulMessageeID, m_ulMnemonic; size_t m_iGroup; }; /* remove the object from the message manager on destroy */ void MessageableObjectBase :: clearMessagee(void) { for(size_t i=0;i<this->m_pMessageHandlers.getSize();i++) delete this->m_pMessageHandlers[i]; this->m_pMessageHandlers.clear(); if(this->m_pMessageManager == null) return; /* clear mnemonic before messagee */ if(this->m_ulMnemonic != INVALID_MESSAGEE) { this->m_pMessageManager->unregisterMnemonic(this->m_ulMnemonic, getMessageeID()); this->m_ulMnemonic = INVALID_MESSAGEE; } if(this->m_ulMessageeID != INVALID_MESSAGEE) { this->m_pMessageManager->unregisterMessagee(this, this->m_ulMessageeID); this->m_ulMessageeID = INVALID_MESSAGEE; } } void MessageableObjectBase :: assignHook(MessageManager *pMessageManager, messagee_id ulHook) { this->m_pMessageManager = pMessageManager; if(this->m_pMessageManager == null) throwException(MessageManagerNotSetException()); this->m_ulMessageeID = this->m_pMessageManager->registerMessagee(this, ulHook); } void MessageableObjectBase :: assign(MessageManager *pMessageManager) { this->m_pMessageManager = pMessageManager; if(this->m_pMessageManager == null) throwException(MessageManagerNotSetException()); this->m_ulMessageeID = this->m_pMessageManager->registerMessagee(this); } /* send a message to the message manager */ bool MessageableObjectBase :: sendMessage(unsigned long ulRecipientID, IMessage &rMessage) { if(this->m_pMessageManager == null) throwException(MessageManagerNotSetException()); return this->m_pMessageManager->dispatchMessage(getMessageeID(), ulRecipientID, rMessage); } /* register as mnemonic */ void MessageableObjectBase :: registerMnemonic(unsigned long ulMnemonic) { if(this->m_pMessageManager == null) throwException(MessageManagerNotSetException()); if(this->m_pMessageManager->registerMnemonic(ulMnemonic, getMessageeID())) this->m_ulMnemonic = ulMnemonic; }

Finally, the MessageManager is the big chunk of meat. It assigns ids to messagees and processes messages between messagees inside the same messaging systems. It does a lot of book-keeping and handles three different types of messagees: objects, hooks and mnemonics. Addressing to an object is the common way of sending messages as you give the messagee id of the recipient. Hooks are special command which allows registering an object under an already existing messagee id. This is how all the components from a given entity to receive and send messages with the same unique identifier. Last but not least is the mnemonic which is a special id that you can use to address messages to a given object that is considered important by the programmer (e.g.: the world manager) or to address message to self, broadcast message etc. The decoding of the messagee type occurs in the dispatchMessage function which sends copy of the message to the different MessageableObjectBase instances.

typedef unsigned long messagee_id; #define NUM_MESSAGER_GROUPS 2 #define MAX_MNEMONICS 256 #define INVALID_MESSAGEE 0 #define MESSAGEE_MNEMONICS_BASE ((messagee_id)0xA0000000) #define MESSAGEE_OBJECTS_BASE ((messagee_id)0xB0000000) #define MESSAGEE_SIGNATURE_ZONE ((messagee_id)0xF0000000) #define MSG_SELF 1 #define MSG_BROADCAST 2 #define MSG_BROADCAST_NOT_ME 3 #define MSG_WORLD (MESSAGEE_MNEMONICS_BASE + 0) /* MessageManager manages messages and message types */ class MessageManager { friend class MessageableObjectBase; public: MessageManager(void) { for(int i=0;i<MAX_MNEMONICS;i++) this->m_pMnemonics[i] = INVALID_MESSAGEE; } ~MessageManager(void) { } private: /* register an object as a new messagee */ messagee_id registerMessagee(MessageableObjectBase *pMessagee) { size_t n = this->m_ppMessagees.getSize(); for(size_t i=0;i<n;i++) if(this->m_ppMessagees[i].getSize() == 0) { addMessageeToList(pMessagee, this->m_ppMessagees[i]); return (messagee_id)i | MESSAGEE_OBJECTS_BASE; } this->; addMessageeToList(pMessagee, this->m_ppMessagees[n]); return (messagee_id)n | MESSAGEE_OBJECTS_BASE; } /* hook a messagee, interresting for entities/components systems */ messagee_id registerMessagee(MessageableObjectBase *pMessagee, messagee_id ulHook) { size_t i = getIndex(ulHook); addMessageeToList(pMessagee, this->m_ppMessagees[i]); return ulHook; } /* return true if the messagee id is a mnemonic such as MSG_WORLD etc. */ bool isMnemonic(messagee_id ulMesageeID) { return (ulMesageeID & MESSAGEE_SIGNATURE_ZONE) == MESSAGEE_MNEMONICS_BASE; } /* return true if the messagee id is an object */ bool isObject(messagee_id ulMesageeID) { return (ulMesageeID & MESSAGEE_SIGNATURE_ZONE) == MESSAGEE_OBJECTS_BASE; } /* return the mnemonic index for the messagee id */ size_t getMnemonicIndex(messagee_id ulMesageeID) { size_t i = ulMesageeID & (~MESSAGEE_SIGNATURE_ZONE); if(i >= MAX_MNEMONICS) throwException(InvalidMessageeMnemonicException(ulMesageeID)); return i; } /* return the object index for the messagee id if it is an object signature */ size_t getObjectIndex(messagee_id ulMesageeID) { size_t i = ulMesageeID & (~MESSAGEE_SIGNATURE_ZONE); if(i >= this->m_ppMessagees.getSize()) throwException(InvalidMessageeRecipientException(ulMesageeID)); return i; } /* return the object index for the messagee id, works with objects and mnemonics id */ size_t getIndex(messagee_id ulMesageeID) { if(isMnemonic(ulMesageeID)) ulMesageeID = this->m_pMnemonics[getMnemonicIndex(ulMesageeID)]; if(!isObject(ulMesageeID)) throwException(InvalidMessageeRecipientException(ulMesageeID)); return getObjectIndex(ulMesageeID); } /* * Dispatch the message to the destination messagees. * Defined private with friend MessageableObjectBase such that only MessageableObjectBase can disptach message. * MessageableObjectBase send function is made such that it is not possible for a derived object to fake the sender id. */ bool dispatchMessage(messagee_id ulSenderID, messagee_id ulRecipientID, IMessage &rMessage) { messagee_id _ulRecipientID = ulRecipientID; /* cannot send to invalid messagee dest */ if(ulRecipientID == INVALID_MESSAGEE) return false; /* if MSG_SELF then recipient is the sender */ else if(ulRecipientID == MSG_SELF) ulRecipientID = ulSenderID; /* broadcast message to all registred messagees */ else if(ulRecipientID == MSG_BROADCAST || ulRecipientID == MSG_BROADCAST_NOT_ME) { size_t n = this->m_ppMessagees.getSize(); bool bRet = false; for(size_t i=0;i<n;i++) { messagee_id uIndex = (messagee_id)i | MESSAGEE_OBJECTS_BASE; if(uIndex != ulSenderID || ulRecipientID == MSG_BROADCAST) bRet |= dispatchMessage(ulSenderID, uIndex, rMessage); } return bRet; } /* decode mnemonic if it is one */ else if(isMnemonic(ulRecipientID)) { size_t i = getMnemonicIndex(ulRecipientID); _ulRecipientID = ulRecipientID = this->m_pMnemonics[i]; } /* now we must have an object or the message is corrupted */ if(!isObject(ulRecipientID)) throwException(InvalidMessageeRecipientException(ulRecipientID)); size_t i = getObjectIndex(ulRecipientID); /* change sender id to MSG_SELF if sender is recipient */ if(ulSenderID == _ulRecipientID) ulSenderID = MSG_SELF; bool bRet = false; /* send to all objects recorded as the messagee */ Array<MessageableObjectBase*> &list = this->m_ppMessagees[i]; size_t n = list.getSize(); for(size_t i=0;i<n;i++) if(list[i] != null) bRet |= list[i]->dispatchMessage(ulSenderID, rMessage); return bRet; } /* register object as a mnemonic */ bool registerMnemonic(messagee_id ulMnemonic, messagee_id ulMessageeID) { if(!isMnemonic(ulMnemonic)) throwException(InvalidMessageeMnemonicException(ulMnemonic)); size_t i = getMnemonicIndex(ulMnemonic); if(this->m_pMnemonics[i] != INVALID_MESSAGEE) throwException(InvalidMessageeMnemonicException(ulMnemonic)); this->m_pMnemonics[i] = ulMessageeID; return true; } /* release mnemonic */ bool unregisterMnemonic(messagee_id ulMnemonic, messagee_id ulMessageeID) { if(!isMnemonic(ulMnemonic)) throwException(InvalidMessageeMnemonicException(ulMnemonic)); size_t i = getMnemonicIndex(ulMnemonic); if(this->m_pMnemonics[i] != ulMessageeID) throwException(InvalidMnemonicWrongAuthException(ulMnemonic, ulMessageeID)); this->m_pMnemonics[i] = INVALID_MESSAGEE; return true; } /* unregister a messagee from the list; called when the object is destroyed */ void unregisterMessagee(MessageableObjectBase *pMessagee, messagee_id ulMessageeID) { if(!isObject(ulMessageeID)) throwException(InvalidMessageeRecipientException(ulMessageeID)); size_t i = getObjectIndex(ulMessageeID); if(this->m_ppMessagees[i].getSize() == 0) return; // TODO : why does clear make program crash? if(this->m_ppMessagees[i][0] == pMessagee) { size_t n = this->m_ppMessagees[i].getSize(); for(size_t j=0;j<n;j++) this->m_ppMessagees[i][j] = null; } else { size_t n = this->m_ppMessagees[i].getSize(); for(size_t j=0;j<n;j++) if(this->m_ppMessagees[i][j] == pMessagee) { this->m_ppMessagees[i][j] = null; return; } } } /* add message to list */ void addMessageeToList(MessageableObjectBase *pMessagee, Array<MessageableObjectBase*> &rList) { size_t n = rList.getSize();; rList[n] = pMessagee; } Array< Array<MessageableObjectBase*> > m_ppMessagees; messagee_id m_pMnemonics[MAX_MNEMONICS]; };

And that’s all! You can obviously adapt the code to suit your needs. For example, you can make the MessageManager class into a singleton which will make you the life easier in terms of code management. The reason I removed the singleton pattern for that class is that it was easier to manage multiple MessageManager at the same time. Not only to have a message manager for the hardware and UI as stated previously (because you can have a singleton triplet then) but also to have several worlds, each of them with their own message manager. Only one world would be active (be the foreground task or foreground world), the others being inactive in the background but ready to be swapped as the foreground world. The goal would be to stream worlds in and out in a separate loading thread such that when reaching the border of one map (one world), the following one would already be in cache and ready to be drawn. This is nothing but a way of reducing loading issues by splitting the actual whole game into several worlds/maps and having the next maps ready in the cache as we reach doors/trigger point. But that is a complete different story and I will leave that for later :-)

[⇈] Top of Page

You may also like:

[»] Implementing Object Persistence in C++

[»] Event Monitoring in C++ Using EVEMON.EXE

[»] Hierarchical Collision Detection

[»] A concurrent job system in C++ with dependencies

[»] Data Communication With a PIC Using RS232