#6 Framework 2 – Messaging

The last tutorial was very long, so instead I’m going to break the next few bits down. Unfortunately that means that this time will be purely infrastructure with nothing new being added to the game as such.

So, the name of the game today is message passing. The issue is that, if there are things around your world, you might need to know about things which are happening, but you don’t really want to check every frame. Nor is it realistic to have things knowing about what needs to know (did that make sense?) Basically, the classes that should know about what information is needed is those that require the information, but the classes that know when they need to know is the opposite. So, what do we do? We look for a design pattern which might fit. Enter the Observer pattern.

As we need to keep things moving, we’re just going to be doing something very very simple, but we’re going to try and leave it open for extension. Let’s get started. For this tutorial, as the classes are quite small, we’ll put them all in MessageSystem.h and MessageSystem.cpp

MessageSystem.h
#ifndef __MESSAGE_SYSTEM_H__
#define __MESSAGE_SYSTEM_H__

typedef unsigned int Message;

//----------------------------------------
// Message System
//
// Currently just a simple wrapper for an
// ID. Can be extended to a more complete
// message system
//----------------------------------------
class MessageSystem
{
public:
	MessageSystem();
	Message RegisterMessage(const char* message);
private:
	Message m_CurrID;
};

// RegisterMessage
// quick accessor for the message system
Message RegisterMessage(const char* message);

As you can see, there’s no end to the include guards here, so we’ve only got the first part of the file. Basically, what we want is a message system that we can register messages in. This should do that. The RegisterMessage function is exposed outside of the class as MessageSystem will be within our TutorialGame, so it’s a convenient wrapper for getting to that. There’s very little here though.

MessageSystem.h
// DECLARE_MESSAGE
// this macro should be used in header files to
// expose messages you wish to be sent externally
#define DECLARE_MESSAGE(msg) 
	extern Message msg;

// REGISTER_MESSAGE
// this macro should go into the source file
#define REGISTER_MESSAGE(msg) 
	Message msg = RegisterMessage(#msg);

We shouldn’t really need to make things more easy than our wrapper function, but just in case we change the messaging system later on (we may just do that…) we’ve wrapped the whole thing in a few macros. Trust me, it will all make sense. Now, we don’t actually have anything to send messages between, we only have a few objects in our game, but we can pretend to register a message in the tutorial behaviour.

TutorialBehaviour.h
#ifndef __TUTORIAL_GAME_H__
#define __TUTORIAL_GAME_H__

#include "MEngine.h"

#include "GameClock.h"
#include "MessageSystem.h"

DECLARE_MESSAGE(MESSAGE_TEST);
TutorialBehaviour.cpp
#include "TutorialGame.h"

//----------------------------------------
// Messages
//----------------------------------------
REGISTER_MESSAGE(MESSAGE_TEST);

As you can see, that much is pretty simple. It probably won’t compile right now because you haven’t got the relevant stuff in the MessageSystem.cpp yet, so let’s do that.

MessageSystem.cpp
// Standard includes
#include "MessageSystem.h"

// Project includes
#include "TutorialGame.h"

// Engine includes
#include 

// System includes
#include  // std::find

MessageSystem::MessageSystem()
: m_CurrID(0)
{
}

Message MessageSystem::RegisterMessage(const char* message)
{
	// For now, we're just going to do something very quick
	// all we want is to have an ID that increases with
	// every message registered
	Message msg = m_CurrID;
	m_CurrID++;
	return msg;

	// in future this should be extended to allow for error
	// checking. This wouldn't be safe, for example, for a
	// multiplayer game, there is no guarantee that messages
	// get registered in the same order. A better solution
	// would be to use a hash of the message name, but then
	// in debug we'd want to do some tests to make sure that
	// we don't have any clashes. For now, this will work.
}

Message RegisterMessage(const char* message)
{
	// This should just wrap up the RegisterMessage function
	// inside the MessageSystem. Making the MessageSystem a
	// singleton would reduce the need for this, but that
	// would not allow us to be able to replace message
	// systems at a later date.
	MEngine* engine = MEngine::getInstance();
	if(MGame* game = engine->getGame())
	{
		TutorialGame* tutGame = (TutorialGame*)game;

		if(MessageSystem* sys = tutGame->GetMessageSystem())
		{
			return sys->RegisterMessage(message);
		}
	}

	// Do we have a sensible fallback behaviour for if we
	// don't have a message system? Nope. Probably best
	// actually doing a simple incremented ID here to be
	// safe.
	return 0;
}

So, there’s quite a few lines here, but it actually does very little. Currently, all that happens when we register a message is that we give back the next ID in a sequence. Much as 90% of the time, this will be sufficient, it won’t allow for things such as multiple definitions which can be quite nasty and potentially confusing. Also, if the game would ever become a network game, there is no standard for which order the messages get registered. Even if you recreated the system so that it would register messages centrally, so you could guarantee the order, you would require people to play with precisely the same build, otherwise a message could have been registered in the wrong order. What should, ideally, happen, is that we take the message name (passed as a const char*) and do some sort of magic to it. The normal thing would be to get a hash of the string. There are many hash functions available. It’s up to you which one you choose if you decide to go down this route. Might be useful to put it in Util.h/.cpp as it’s a generally useful thing for comparing strings quickly. Of course, on debug you probably want to have some special functionality, maybe you store the hash and the const char* in a map so you can find potential clashes and flag it up so they don’t affect the final build.

MessageSystem.h
//----------------------------------------
// Observer
//
// Currently just an interface for subject
// to send messages to
// Could be extended to automatically
// remove itself from subjects on destruction
//----------------------------------------
class Observer
{
public:
	virtual void OnMessage(Message message, int param) = 0;
};

This, at this stage, could be called IObserver as there’s nothing there but a virtual interface. I do, however, intend to add some more functionality to this class later, so I’ll just leave it as it is for now. Basically, it will provide an interface for any subject (we’re coming to that one) to send messages to. The subject therefore doesn’t need to know what’s listening to it, and the observer doesn’t need to constantly poll the subjects.

MessageSystem.h
#include 

//----------------------------------------
// Subject
//
// Base class for anything wanting to send
// messages
//----------------------------------------
class Subject
{
public:
	void AttachObserver(Observer* observer);
	void DetachObserver(Observer* observer);

protected:
	void SendMessage(Message message, int param = 0);

private:
	typedef std::vector	observerVec;
	typedef observerVec::iterator	observerVecIter;

	observerVec		m_Observers;
};

#endif /*__MESSAGE_SYSTEM_H__*/

And so, we come to the end of the MessageSystem.h. The subject isn’t anything special either. It’s got a very simple interface, we can attach observers, detach them, and we can send messages. We’ve made SendMessage protected as noone should be sending a message from outside really (well, maybe, but we’ll come to that when we revisit this system later maybe). The only member of this class, for now, is a vector of observers, other classes which are listening to us. Let’s move onto the code.

MessageSystem.cpp
void Subject::AttachObserver(Observer* observer)
{
	// if we're not already being observed by
	// the one being added - then add to the list
	if(std::find(   m_Observers.begin(),
					m_Observers.end(),
					observer) == m_Observers.end())
	{
		m_Observers.push_back(observer);
	}
}
void Subject::DetachObserver(Observer* observer)
{
	// find the correct observer to remove
	observerVecIter iObserver =
		std::find(	m_Observers.begin(),
					m_Observers.end(),
					observer);
	if(iObserver != m_Observers.end())
	{
		m_Observers.erase(iObserver);
	}
}
void Subject::SendMessage(Message message, int param)
{
	// for now, we just send everything to everyone
	// and let them deal with filtering. This isn't
	// an ideal solution, we probably want to extend
	// AttachObserver so we can subscribe to specific
	// messages.
	observerVecIter iObserver;
	for(iObserver = m_Observers.begin();
		iObserver != m_Observers.end();
		iObserver++)
	{
		(*iObserver)->OnMessage(message, param);
	}
}

At this stage, there really shouldn’t be anything surprising here. The only thing to mention is that, if we start using the message system heavily, potentially this could cause a slowdown as it would be calling OnMessage many times where it isn’t needed. Again, this is acceptable for now, I will give some improvement the next time we visit this class. So, should we put a quick test together for this? First of all, we will make our game a subject, so things can listen to us.

TutorialGame.h
<pre>TutorialGame.cpp
class TutorialBehaviour : public MBehavior, public ITimerAction, public Observer
{
public:
//...
	//----------------------------------------
	// Observer virtuals
	//----------------------------------------
	void OnMessage(Message message, int param1);
TutorialBehaviour.cpp
void TutorialBehaviour::Setup(float step, float delta, float range)
{
	// setup the movement information
	m_StepX = step;
	m_CurrDeltaX = delta;
	m_RangeX = range;
	m_StepZ = step;
	m_CurrDeltaZ = delta;
	m_RangeZ = range;

	if(MObject3d* parent = getParentObject())
		m_RootPosition = parent-&gt;getPosition();

	MEngine* engine = MEngine::getInstance();
	if(MGame* game = engine-&gt;getGame())
	{
		// make a new timer and start it ticking
		TutorialGame* tutGame = (TutorialGame*)game;
		if(GameClock* clock = tutGame-&gt;GetGameClock())
		{
			m_BounceTimer = clock-&gt;CreateTimer(eBounceTimer);
			m_BounceTimer-&gt;AddAction(this);
			m_BounceTimer-&gt;SetTimeMs(BOUNCE_TIMEOUT);
			m_BounceTimer-&gt;Start();
		}

		tutGame-&gt;AttachObserver(this);
	}
}
//----------------------------------------
void TutorialBehaviour::OnTimerComplete(int id)
{
	// we're now using the message from the game
	// to bounce, so don't listen to the timer
	// for now
	/*
	// timer has finished, just check which one
	switch(id)
	{
	case eBounceTimer:
		m_IsBouncing = true;
		break;
	};
	*/
}
//----------------------------------------
void TutorialBehaviour::OnMessage(Message message, int param1)
{
	if(message == MESSAGE_TEST)
	{
		m_IsBouncing = true;
	}
}

So, what have I done, I’ve removed the timer flagging the cube to bounce and I’ve made it listen to the timer which is in our game. Now, if you were particularly astute, you’ll remember that the timer in TutorialBehaviour fired off every 5000ms, but the one in our game only takes 500ms. This was intentional and not a typo on my part (honest!). So now, if you compile and test the behaviour again, you should be able to see the cube bouncing far more frequently.

What have we done then? Couldn’t we have done this without the message passing? Well, yes. But if you think about it, we now have one thing which is informing an object, potentially many objects. That means that many things could be synchronised to acting on this. In order to do that without a message system you would have to constantly poll which is slow and awkward.

<-Previous Next->

Discuss

Leave a Reply

Your email address will not be published. Required fields are marked *

Diary of a Slacker