Author image

Event Queue, Message Bus and Dispatcher


NOTE: I will use the terms event and message interchangeably. In some contexts there may be a minor differentiation but for this discussion it will be ignored.

Event Queue or Message Bus


Decouple when a message or event is sent from when it is processed. - R. Nystrom

What's the easiest way to manage the connections between event listeners and event producers? You guessed it “Event Queues”.

When an event occurs, such as user input, or an entity sending an event to another entity, it needs to be stored somewhere such that it's not lost while in transit between the source that reported the event and when the program gets around to respond to it. That “somewhere” is a queue.

New events are added to a queue of unprocessed events. And at a later time when it's convenient in the application we pull off the events from the queue (aka dequeue) and respond to them by calling onEvent().


  • Create a queue of Events
  • Create an Event Class, which contains everything a 'message envelope' contains
  • The head of the queue is where requests are read from. The head is the oldest pending event to be received (remember FIFO).
  • The queue's tail is where we add, or enqueue, new events.
  • a source adds events to the queue
  • to read events, a class inherits from IEventListener and implements onEvent() (or onMessage() style functions)

Event Dispatcher

You can think of an event dispatcher as the conduit through which any module or any subsystem can speak to any other within a larger system of components, without any two components having to know each other.

In reference to our video game example, there are various different entities that interact with each other. Those interactions tend to be very dynamic and deeply connected with gameplay. This tutorial covers the concept and implementation of a message queue system that can unify the interactions of entities, making your code manageable and easy to maintain as it grows in complexity.

In this approach, communication among game entities becomes unified. All entities can send and receive messages. No matter how complex or peculiar the interaction or message is, the communication channel always remains the same. On the other hand, the destination will deal with or react to the message based on its content and on who the sender is.

Basically the event dispatcher is the directory of the event queue. It manages the dispatch of events. In our code, the MessageDispatcher owns the MessageBus and it's the only one that can call its functions.

What will the Message class contain?

We must allow the entities to pass different kinds of data between them, so the messaging system should be able to support messages with any type of payload. The payload data could be relatively large, so it shouldn't be copied around. Only its ownership should be passed from sender to the queue, and from the queue to the receiver.

I concluded that in the general case the following items should be sufficient:

  1. pointer or id to source object
  2. vector of pointers or ids to destination objects
  3. message type - a flag that identifies the general purpose of the event
  4. a boolean flag indicating whether the message has been received and handled by the recipient(s)
  5. message payload - a callback function MessageCall or data struct MessageData

Experiments: - you may want to “double buffer” your queue. That is add two of them, one for receiving events, one for processing. When the processing queue is processed switch the two queues around (pointer switch) and unlock. Reports indicate there's a shorter lock time. - Instead of a (single or 2) large queue of events you may want to keep a map of event types to queues of events, ie. instead of queue<Event<T>> q; use map<EventType, queue<Event<T>> mq; or map<Event<T>::Destination, queue<Event<T>> mq;.

When should you use it?

When the amount of modules & components of your codebase grows, then it gets hard to reason about all of them thus resorting to an EventQueue/MessageBus would be an excellent choice.

Comparison with other event driven design patterns

  • In case you only want to decouple who receives an event from its sender, patterns like Observer and Command will take care of this with less complexity. You need an EventQueue when you want to decouple something in time.
  • Message queues give control to the code that pulls from it?-?the receiver can delay processing, aggregate requests, or discard them entirely. But queues do this by taking control away from the sender. All the sender can do is throw a request on the queue and hope for the best. This makes queues a poor fit when the sender needs a response.

Some have criticized event queue systems, because of the global nature of the Queue instance. I resent this accusation, because I believe in a complex system you'll always have at least a little bit of domain-crossing, there's no way around it. As long as you have clearly and nicely defined the queue interface you should be perfectly fine.

I believe that the biggest advantage of an Event - Queue + Dispatcher system is that it allows a higher level of abstraction between any kind of modules. And hey, if it's used all the time by the OS, for interprocess communication, for network comms etc. then it can certainly find usability in your own usecase!


This project contains a bit more than just the EventQueue and dispatcher. I actually intended to form the foundation for a gameplay interaction system, allowing the ability of any types of entities to communicate among each other, no matter the kind of modules they're in. As a result you will some some obscure stuff in the code so I will explain the most important of those here.

I. DelayedFunc

You will find and I'm sure you'll be wondering of the DelayedFunction.

This will be used as the basis for a nice Message class, which I call MessageCall. It's basically a more advanced envelope, which allows one to send messages/events that perform arbitrary logic. That should be the goal right? The way this is implemented requires understanding of some advanced C++ concepts. I will lay out the basics right below.

DelayedFunc represents a ready-made callable object relete with the function signature and its arguments, packaged & owned by an std::unique_ptr, which allows it to be easily inserted and manipulated in any std container, or used in other manner. Under the hood DelayedFunc utilizes std::function<void> type erasure to be able to call function of any signature. We create that function using std::bind for argument packing and hand it over to std::function<void>.

Using std::function seems to be the only way to wrap std::bind functions and store them in a container.

That was a mouthful I know, but remember to take everything one step at a time!

II. Lifetime of Objects in the Queue

  1. Pass ownership: This is the traditional way to do things when managing memory manually. When an event gets queued, the queue claims it and the sender no longer owns it. When it gets processed, the receiver takes ownership and is responsible for deallocating it. This is what we do in this tutorial and code.
  2. Share ownership: These days, now that even C++ programmers are more comfortable with garbage collection, shared ownership is more acceptable. With this, the event sticks around as long as anything has a reference to it and is automatically freed when forgotten.
  3. The queue owns it: Another option is to have messages always live on the queue. Instead of allocating the event itself, the sender requests a 'fresh' one from the queue. The queue returns a reference to an event already in memory inside the queue and the sender fills it in. When the event gets processed, the receiver refers to the same event in the queue.

III. MessageBus / EventQueue thread safety measures

The careful reader has noticed from the code that MessageBus is thread safe. The way this is done can be summed up by the following points: - unique_lock in dequeue because while you wait using a unique_lock you can relinquish control to any other threads trying to access a critical section using m_mu. - otherwise using a lock_guard somebody wouldn't be able to enqueue and the program would deadlock - dequeue forces the thread to wait/sleep, using the condition variable, while there's no messages on the Queue

I used Windows 8.1 x86_64, Visual Studio 2017, Modern C++17 to build the project. It should work on other platforms as well.


Github repository link.


Event Queue by Robert Nystrom.

C++ Virtual Construction

SO answer.