Gamedev Framework (gf)  0.3.0
A C++11 framework for 2D games
Game messages

Table of Contents

Introduction

In any game, you need to pass data around multiple parts of the code. The first choice is to make direct function calls. The main drawback of this approach is the tight coupling between the caller and the callee. The second choice is to use the observer pattern.

The purpose of gf::MessageManager is to provide a class that implements the observer pattern.

How to define and use messages?

In this section, we will see how to defined and use messages in your game.

Defining a message

A message is just a structure with your own fields. In gf, a message must derive from gf::Message and define a static unique type.

struct HeroPosition : public gf::Message {
static const gf::Id type = "HeroPosition"_id; // compile-time definition
gf::Vector2f position;
};

This piece of code defines a message HeroPosition with a single field: position. The type of the message is "HeroPosition"_id that is transformed at compile time in an integer of type gf::Id.

Sending a message

To send a message, you need a gf::MessageManager. Here, we suppose we have a global variable called messageManager of this type (see also Safe singletons).

gf::MessageManager messageManager;

Then, you only have to call gf::MessageManager::sendMessage() with an instance of your message.

class Hero {
public:
void update(float dt) {
// compute new position
m_position = computeNewPosition(m_position, dt);
// broadcast the new position
HeroPosition message;
message.position = m_position;
messageManager.sendMessage(&message);
}
private:
gf::Vector2f m_position;
};

Receiving a message

To receive a message, you have to register a message handler in the message manager. A message handler is a function that will be called when a message of a given type is sent. This function can be a free function, or a member function. gf::MessageManager provides a shortcut for this last (very comon) case.

class Enemy {
public:
Enemy() {
// register an handler: the onHeroPosition method
messageManager.registerHandler<HeroPosition>(&Enemy::onHeroPosition, this);
}
private:
gf::MessageStatus onHeroPosition(gf::Id id, gf::Message *msg) {
// verify that we have the right message type
assert(id == HeroPosition::type);
// we can now safely cast the message...
auto heroPosition = static_cast<HeroPosition*>(msg);
// and use its data to update the ennemy
m_target = heroPosition->position;
// we keep this handler for future messages
}
private:
gf::Vector2f m_target;
};

More about gf::MessageManager

Synchronous delivery

gf::MessageManager passes messages synchronously. This means that the message is sent immediately to handlers. An advantage is that there is no need for an allocation, the message can be put on the stack. A drawback is that care must be taken to avoid message loops. If handlers do not send messages, then the drawback disappears.

Removing handlers

Each message handler receives an handler id when it is registered. This handler id can then be used to remove the handler directly through the gf::MessageManager::removeHandler() method.

The other method to remove a handler is to return gf::MessageStatus::Die at the end of the handler.

A common error is to register an handler as a method of an object and to delete this object without removing the handler. As a consequence, the message manager will try to call the handler and that will result in an error as the object does not exist anymore. This type of error can be very hard to find because the effect of the error is often indirect.

A good way to prevent this type of error for short life objects is to keep the handler ids and to remove the handlers in the destructor of the class.

class ShortLife {
ShortLife() {
m_onFoo = messageManager.registerHandler<Foo>(&ShortLife::onFoo, this);
// register the same function for two message types: that makes two different handlers
m_onBar = messageManager.registerHandler<Bar>(&ShortLife::onBarOrBaz, this);
m_onBaz = messageManager.registerHandler<Baz>(&ShortLife::onBarOrBaz, this);
}
~ShortLife() {
messageManager.removeHandlers({ m_onFoo, m_onBar, m_onBaz });
}
private:
// do something useful
}
gf::MessageStatus onBarOrBaz(gf::Id id, gf::Message *msg) {
// do something useful
}
gf::MessageHandlerId m_onFoo, m_onBar, m_onBaz;
};