[C++] Simple Message Bus
This is about messaging system between components. I mean that components are like sub-systems. You can split systems into UI, Sound, Rendering, InGame, etc. Or it can be more contents like Control, Inventory, Costume, Shop system, etc. Whatever they are, they need to communicate each other by user inputs or any other events.
I have seen many times calling each system’s functions directly by Singleton patterns or instance variables. At first, it is very efficient and simple. But as systems grow, it can be hell to maintain them.
Figure 1. shows complicate dependency between components and it will have more arrows as systems need more functionalities.
Figure 2. introduces a bus which can deliver messages to each other systems. If there is a bus like below, dependencies can be predicted even though systems grow.
Let’s see the code. This is not a new concept or new things. Upon the Observer pattern, I added some functions and tried to be simple.
First of all, I defined what the message is.
class Message
{
public:
Message(uint from, uint to, string type, string msg) : _from(from), _to(to), _type(type), _msg(msg) {}
~Message() {}
private:
uint _from;
uint _to;
string _type;
string _msg;
public:
const uint& getFrom() { return _from; }
const uint& getTo() { return _to; }
const string& getType() { return _type; }
const string& getMsg() { return _msg; }
};
It simply includes ‘from’, ‘to’ ‘type’, ‘message’. ‘from’ will be a component id which will be assigned. ‘to’ is also a component id of a receiver. ‘type’ can be used freely for depending on various requirements. ‘message’ is a string to send.
Next, let’s define a bus which can deliver the message.
class SafeBus
{
private:
uint uniqueGuid;
public:
SafeBus();
~SafeBus() {}
private:
queue<shared_ptr<Message>> _msgQueue;
map<uint, MessageReceiver*> _mapReceiver;
public:
void AddReceiver(MessageReceiver* receiver); // Components register through this.
void TransferMessage(const shared_ptr<Message>& msg); //Add a message to a queue
void Notify(); // if message exists, notify to receivers
uint GenerateUniqueGUID() // component id is given when calling the AddReceiver' method.
{
return ++uniqueGuid;
}
};
‘_msgQueue’ is a queue that keeps messages to send. ‘_mapReceiver’ is a map that a key will be a component id, and a value is a component pointer. (will discuss later about MessageReceiver). The explanation about methods are decribed as a comment. That is, SafeBus class does work like generating a unique id, register a receiver, add a message to a queue, and notify to a receiver which is a component.
A component will inherit from two classes which are MessageReceiver and MessageSender.
class MessageReceiver
{
public:
MessageReceiver(SafeBus * bus);
~MessageReceiver() {}
public:
virtual void onReceive(const uint& from, const uint& to, const string& type, const string& msg) {}
public:
const uint GetReceiverId() { return _receiverId; }
private:
uint _receiverId;
SafeBus *mpSafeBus = nullptr;
};
class MessageSender
{
public:
MessageSender(SafeBus * bus) : mpSafeBus(bus) {}
~MessageSender() {}
public:
virtual void Send(const uint& from, const uint& to, const string& type, const string& msg);
private:
SafeBus *mpSafeBus = nullptr;
};
As a class name describes, MessageReceiver will receive a message when a Safebus notifies. And MessageReceiver also calls AddReceiver to register itself. MessageSender sends a message to Safebus. That’s all for MessageSender.
Ok. Now it’s ready to make a component as a example.
class ComponentA : public MessageReceiver, public MessageSender
{
public:
ComponentA(SafeBus * bus);
~ComponentA() {}
public:
void onReceive(const uint& from, const uint& to, const string& type, const string& msg) override;
void Send(const uint& from, const uint& to, const string& type, const string& msg) override;
};
I defined a ‘ComponentA‘ class which inherits MessageReceiver and MessageSender. If a component only receives not sending, then inherit MessageReceiver only.
When sending a message, you can broadcast a message to all components registered, or you can send to a specific component only.
const uint idCompA = compA.GetReceiverId();
const uint idCompB = compB.GetReceiverId();
compA.Send(idCompA, EM_DEF_ID::BROADCAST, "type2", "Hello world to ALL"); // broadcast
compA.Send(idCompA, idCompB, "type1", "Hello world"); // send to component B only
You can download a full source through github. (https://github.com/chams12/TB_SimpleMessageBus)
Class diagram is described below.