The first step of implementing any application is writing the foundational code that keeps the program running until we or the user decide it’s time to quit. This code is often called the application loop, or game loop, when we’re making games.
Typically, every iteration of the loop does three things:
Each iteration of this loop happens extremely quickly.
The faster each iteration of this loop runs, the higher our application’s frame rate will be. Higher frame rates make our application feel more responsive to user input.
As a frame of reference, the minimum frame rate that is typically considered acceptable is 30 frames per second. To hit that, we need to be completing each iteration of our loop within 33 milliseconds. If we want 60 frames per second, we only have 16 milliseconds per frame.
In this lesson and the next, we’ll focus on the “process any events” part of the application loop. We’ll cover updating and rendering in the following two lessons.
It’s worth exploring this event-handling aspect a little deeper. SDL uses an extremely common way of managing events, called an event queue.
An event queue is a data structure, similar to an array, but designed in such a way that objects get added to the structure at one end, and removed at the other.
These actions are typically called “pushing” and “popping”, respectively
Any time an event occurs, an object representing that event gets added to the end of this queue.
To make our application react to the events that are happening, we constantly look at the start of the queue. If there’s an event there, we remove it from the queue, react accordingly to it, then delete it.
The code we write that constantly checks the front of the queue for events is an event loop.
Let's take a look at what a typical application loop might look like when we’re using SDL2:
#include <SDL.h>
#include "Window.h"
int main() {
Window GameWindow;
SDL_Event Event;
// Application Loop
while(true) {
// Event Loop
while(SDL_PollEvent(&Event)) {
// 1. Handle Events
}
// 2. Update Everything
// 3. Render Frame
}
}
The two new things to note here are the SDL_Event
object and the SDL_PollEvent
function call. These two things are how we interact with SDL’s event queue.
SDL_Event
is the base class for all of SDL’s events. It is the type of object that are on the event queue.
SDL_PollEvent
is the method whereby we remove the next element in the queue, so we can process it.
The implementation of SDL_PollEvent
requires us to create an SDL_Event
object ahead of time, and then pass its pointer into SDL_PollEvent
.
SDL will then update our object with the details of the next event we need to handle, and remove that event from the queue.
When there are no more events to process, SDL_PollEvent
will return 0
. So, we can use that return value to determine when our event loop ends for this frame.
The official documentation for SDL_Event
is available here.
At first glance, it seems we could simplify our application loop to be this:
while(SDL_PollEvent(&Event)) {
// 1. Handle Event
// 2. Update Everything
// 3. Render Frame
}
If we tried it, it would even seem to work. Many applications are even using setups like this. But, it forces the frame rate and event loop to be in lockstep. This is problematic in two scenarios.
Firstly, when relatively few events are happening, the application will update slower. This is generally undesirable - we want to do things like update animations at a pretty consistent frame rate.
We could fix that problem by changing their application loop to be something like this:
while(true) {
SDL_PollEvent(&Event);
// 1. Handle Event
// 2. Update Everything
// 3. Render Frame
}
The application will now output frames as fast as possible. But, the second problem is still there: a maximum of one event can be processed per frame.
When lots of events are happening, it can take several frames before user input is reflected on the screen. This can make our applications feel unresponsive.
So, modern application loops treat event handling and frame rendering as two unlinked processes, to the extent that is possible.
Once SDL_PollEvent
has updated our event object, it’s time for us to examine that object. We can then determine what action, if any, we need to take.
Typically, the first thing we need to do is determine what type of event it is. Events have a type
property for this.
while (SDL_PollEvent(&Event)) {
if (Event.type == /* ?? */) {
// ...
} else if (Event.type == /* ?? */) {
// ...
} else if (Event.type == /* ?? */) {
// ...
}
}
The type
member on the SDL_Event
class is an integer. However, SDL provides some helpers that maps those integers to meaningful names, so our code is easier to read.
Lets see an example.
SDL_QUIT
EventTypically, the first event we’ll want to handle is SDL_QUIT
. When we encounter this event, it means something has requested our application close.
If our event loop is being run in the main
function, we can honor this request with a simple return
 statement:
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_QUIT) [[unlikely]] {
return 0;
} else if (Event.type == /* ?? */) {
// ...
} else if (Event.type == /* ?? */) {
// ...
}
}
We’ve added the [[unlikely]]
attribute here. This is because the event loop is somewhere we really want to be conscious of performance. It’s unlikely any given event is the quit event because, almost by definition, that only happens once.
Finally, we can now close our application without having to force it!
SDL_Quit()
before ClosingIt’s generally recommended to inform SDL we’re about to quit our application.
We do that by calling SDL_Quit()
right before our application ends. This allows SDL to do any necessary cleanup. For example, it might need to revert any system settings that we changed from our application.
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_QUIT) [[unlikely]] {
SDL_Quit();
return 0;
}
//...
}
The official SDL documentation for SDL_Quit
is available here.
SDL_QUIT
vs SDL_Quit
There may be some confusion here, as we've used two identifiers with very similar names in this lesson. C++ is case sensitive, so the difference in their capitalisation matters.
SDL_QUIT
is an integer that matches an event type. SDL_Quit
is a similarly-named, but unrelated function that is used to request SDL clean up and shut itself down.
We’re not limited to just reading events from the SDL event queue. We can also add events to it, using SDL_PushEvent
.
The SDL_Event
class has several constructors we can use. The most simple one simply accepts a single argument - the type of event we want to create, such as SDL_QUIT
.
Here’s an example of how we can push an SDL_Quit
event onto the queue:
SDL_Event QuitEvent { SDL_QUIT };
SDL_PushEvent(&QuitEvent);
We can push an event like this from anywhere in our application. Then, once our main event loop sees it, it will react accordingly.
The documentation for SDL_PushEvent
is available here.
We can also use the SDL event queue for custom event types, specific to our application. That goes beyond the scope of this lesson, but if it’s of interest, SDL_UserEvent
and SDL_RegisterEvents
are good starting points to investigate.
In the next lesson, we’ll see examples of how we can capture and react to user input - keyboard and mouse events.
Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games