An Event-Queue
Decouple when a message or event is sent from when it is processed.
– R. Nystrom
A game is usually made of many different entities that interact with each other, as well as different components that must be able to communicate with each other.
In this tutorial, we will learn how to use an event queue to unify all interactions between game characters, such as the player and NPCs, and application components, such as user input and audio. An event queue stores messages in a first-in, first-out order (fifo). The technical term for adding a message to the queue is enqueue. Messages are dequeued from the queue and dispatched to whatever entity was the designated receiver, so a message queue, or event queue, decouples the sender and receiver of a message both locally and in time. But enough for the theory, let us see some code.
A Thread-Safe Queue
To implement an event queue, we will need a queue (sic!). Later on, we will learn about using more than one kernel, thus our queue should be thread-safe, apart from that, the implementation is straightforward:
Postal System
The basic idea behind the event queue is to imitate the centuries old system of sending messages via letters. Just like in reality, the content of each message can vary, but the way messages are send and received is always the same: the sender puts the message in a box (our event queue) and the postal service (our main application class) delivers it to the desired destination, specified by the address on the envelope.
The idea of a postal service can easily be applied to a game or an application. If an entity or a game component wants to interact with another, it can simply send a message. The receiver can then act upon that message accordingly.
Envelope
The first thing to do is to design the envelope:
.
As you can see, the envelope always specifies the sender and the destination of a message, such that both the receiver and the sender of a message always know where a message is going to and where it came from.
Inside the envelope we find the actual message. But there is one thing missing from this picture: to properly handle messages, we also want to know what type of message was sent or received. In the above case, maybe, if the receiver had known that the message contained rather unpleasant news, potentially he would have had a drink first before reading the actual message.
To summarize, an envelope contains the address of the sender and receiver of the message, the type of the message inside the envelope and the actual message.
For a more game related example, imagine a dog guarding its garden from noisy cats. It might be a bit unrealistic, but the dog sends a message to the cat that it is on pursuit. The message type would be pursue and the data could be empty, or contain the velocity of the dog. Anyway, upon reading the message, and verifying that it was indeed sent by the dog, the cat flees away from the dog! In the unlucky case that the dog catches the cat, the dog sends a message with the type damage (ouch) to the cat, and the data might contain the amount of damage dealt, say, two, for example. The cat, upon reading the dreadful news, would lose two of its seven lives.
So far, so good, let us translate the theory into a practical example. Behold the Depesche (German for Dispatch, from the French word for to hurry) class:
As you can see, we have an enum specifying the different message types, and the structure of the Depesche mirrors the real-life example we described above. Notice that the sender of a message doesn’t have to do anything special, besides putting its message in an envelope and bringing it to the post office. The receiver, however, needs to act upon the messages he receives by invoking the onMessage method. We will see some examples soon.
Post Office
The core DirectXApp class will take the function of the post office. It will receive and store messages in the thread-safe queue and dispatch messages to the desired receivers:
The addMessage method simply takes a Depesche and puts it on the queue. The dispatchMessages method simply pulls all the messages from the queue and dispatches them to the correct destination.
Reading Messages
To read the messages it got, a class inherits from the DepescheDestination class and implements the onMessage method:
The message queue will invoke the onMessage method of each entity or game component that is destined to receive a message.
User Input
As a first example of the new event-queue system, we will change the InputHandler class to send a message each time a key map becomes active or whenever the state of the gamepad changes:
Adding a message to the queue is rather straightforward. In this example, we simply set the InputHandler class as the sender and a game state as the destination of the message. The type of the message is UserInput, meaning that a defined key map was activated. The actual message is set to false, to indicate that the InputHandler was not listening for a redefined key mapping (ignore this if you have not read the previous tutorials).
Another message is sent when the state of the gamepad changes, namely a Gamepad message:
The InputHandler class can also receive messages, for example, when a game component decides that it is time to vibrate the gamepad:
As a last example for user input, let us see how the PlayState handles messages:
If a gamepad message is received, the game simply stores the position of the left thumb stick to later move the game entity in the desired direction. If a key map was activated, we let the user interface, in this case, the HUD, handle it.
Of Cats and Dogs
Another, probably even more important, usage of the event queue is to allow game entities to interact with each other. To showcase that situation, I created a little demo of a dog (Cosmo) chasing cats away from his garden.
Here is how a game entity is defined:
Now the NPCs (the cats) will have their own class derived from the above entity class:
As you can see, the cats can both send and receive message. We have already seen that the input handler handles messages about vibrating the gamepad, well, the cats actually send those messages whenever the dog is too close:
Depending on its health, the cat sends a message to the input handler, asking for the gamepad to be vibrated with different speeds.
The dog, or the player, has its own class as well:
The dog does not receive messages: its position is updated when the PlayState receives a gamepad message. It can send messages, however. How does the cat know about the dog’s position? Well, the dog is not a perfect poacher and simply sends a message to each cat, barking its position:
Once the dog has updated its position, it sends a message to each cat, warning the cat to flee or to take damage! The cats simply lose one of their seven lives each time the dog gets too close.
Here is a demo to showcase the usage of the event queue for user input and interactions between game entities, as described above:
You can download the source code from here.
During the upcoming tutorials we will learn how to add music to our application, using XAudio2, FMOD or WWise.
References
Literature
(in alphabetic order)
- Game Programming Algorithms, by Sanjay Madhav
- Game Programming Patterns, by Robert Nystrom
- Microsoft Developer Network (MSDN)
- Tricks of the Windows Game Programming Gurus, by André LaMothe
- Wikipedia