Implementing an Application Loop

Step-by-step guide on creating the SDL2 application and event loops for interactive games

Ryan McCombe
Updated

This lesson explains the fundamental concept of the application loop - the engine that drives real-time programs. You'll learn how SDL manages events, how to poll the event queue to react to user input, and how to correctly structure your loop to handle window closing requests.

Starting Point

We'll continue working on our main.cpp and Window.h files from earlier. Their current state is provided below.

The Application Loop

From a high level, we can imagine our main function has three main areas. An area where we initialize things and create our objects, followed by a loop, followed by an area where we shut things down.

int main(int argc, char** argv) {
  // Initialization
  // ...
  
  // Loop
  while (true) {
    // ...
  }
  
  // Shutdown
  // ...
  return 0;
}

These three components are the standard, high-level structure of all desktop applications, mobile apps, games, and any other type of program that is designed to continue running until the user asks it to close.

In this lesson, we'll focus on the loop part. In this high level design, the loop that keeps our program running is often called the main loop, the application loop, or, if our program is a game, the game loop.

Within each iteration of the main loop, we perform three actions in order:

  1. We react to any events that happened, such as the user pressing keyboard buttons and clicking on things
  2. We update the objects that our program is managing
  3. We render some visual output to the screen so the user can see the changes
int main(int argc, char** argv) {
  // Initialization
  // ...
  
  while (true) {
    // 1. Process Events
    // 2. Update Objects
    // 3. Render Changes
  }
  
  // Shutdown
  // ...
  return 0;
}

If we design our application well and optimize the performance of its various components, our program can complete dozens or even hundreds of iteration of this loop every second.

This means that the player can perform some action and, within a few milliseconds, the effect of that action is visible on the screen. From a player's perspective, a few milliseconds isn't noticable - it may as well be instantenous, so we've created the illusion that they're interacting with our program in "real time".

Events

In this lesson, we'll focus on the first part of the application loop - processing events. SDL uses an extremely common way of managing events, called an event queue.

An queue is a data structure, similar to an array, but designed in such a way that objects get added to the structure at one side, and removed at the other. Conceptually, an "event" is something that happened in our program, but in code, it's just an object.

Like any object, it contains some member variables that help us understand what happened. SDL's type for representing events is an SDL_Event, which we'll work with soon.

Adding an object to a queue is typically called "pushing" it, whilst removing and processing an event is often called "popping" it. We push events to the back of the queue, and pop them from the front:

To make our application react to the events that are happening, we repeatedly look at the front of the queue. If there's an event there, we pop it from the queue, react accordingly to it, and then move on to the next event.

The code we write that repeatedly checks the front of the queue for events is an event loop. On every iteration, it pops an event, examines it, and reacts appropriately. It continues iterating until there are no more events in the queue.

Handling Events

Currently, the only thing in our application loop is a call to SDL_PumpEvents():

#include <SDL.h>
#include "Window.h"

int main(int argc, char** argv) {
  // Initialization
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;

  while (true) {
    // 1. Process Events
    SDL_PumpEvents();
    
    // 2. Update Objects
    // ...
    
    // 3. Render Changes
    // ...
  }
  
  // Shutdown
  SDL_Quit();
  return 0;
}

This invocation tells SDL "we're doing an iteration of our application loop right now - handle your events". Behind the scenes, SDL runs its event loop and processes all the events in its queue.

This is important - for SDL to work correctly, we must prompt it to process its events on every iteration of its application loop.

However, in most programs, we also want the opportunity to react to those events. If the user clicked their mouse somewhere in our window, that means they probably want our program to do something.

For visibility of events, we need to replace SDL_PumpEvents() with our own event loop.

Creating an Event Loop

If we want to see what events are happening in our program, we can use SDL_PollEvent(). This function pops the next event from SDL's event queue, and lets us examine it.

To use SDL_PollEvent(), we need to create an SDL_Event object beforehand, and then pass it's pointer to SDL_PollEvent():

SDL_Event Event;
SDL_PollEvent(&Event);

The SDL_PollEvent() call will update our Event object with details of the event that it popped off the queue. We can then examine that object and decide what action, if any, we need to take.

There are two additional things to note about SDL_PollEvent():

  • SDL_PollEvent() returns true if there was an event in the queue awaiting processing, and false if the queue was empty
  • Multiple events can happen within the same iteration of our loop, so we need to call SDL_PollEvent() repeatedly on every iteration until it returns false

This means that an application loop that includes SDL_PollEvent() will look something like this:

#include <SDL.h>
#include "Window.h"

int main(int argc, char** argv) {
  // Initialization
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  
  SDL_Event Event;
  while (true) {
    // 1. Process Events
    while (SDL_PollEvent(&Event)) {
      // Examine the Event object and react
    }
    
    // 2. Update Objects
    // ...
    
    // 3. Render Changes
    // ...
  }
  
  // Shutdown
  SDL_Quit();
  return 0;
}

That is, on every iteration of our application loop, we have an inner loop that continues until all of the outstanding events are processed - that is, until SDL_PollEvent() returns false.

On every iteration of this inner event loop, we have the opportunity to react to an individual event. That is, the Event object that we originally created, and that SDL_PollEvent() has just updated with the event data that it popped from the queue. We'll see how to react to events in the next section.

Comparing SDL_PollEvent() and SDL_PumpEvents()

If we decide we no longer need to handle events, we shouldn't just delete our event handling. Instead, we should go back to using SDL_PumpEvents().

For SDL to work correctly, we must prompt it to process its events at the appropriate time - that is, on every iteration of our application loop. If we don't care what the events are, we should call SDL_PumpEvents():

while(true) {
  // 1. Process all the events
  SDL_PumpEvents();

  // 2. Update Objects
  // 3. Render Changes
}

If we do care, we should call SDL_PollEvent() repeatedly until it returns false:

while(true) {
  // 1. Process all the events
  while(SDL_PollEvent(&Event)) {
    // ....
  }

  // 2. Update Objects
  // 3. Render Changes
}

Event Types

Now that we're getting information on every event flowing through the system, it's time to react to them. The first thing we need to understand is whether the event we're currently processing is something we care about.

Our application loop can get quite complex, so if we have a lot of different things we need to react to, we should consider offloading that to a new function:

#include <SDL.h>
#include "Window.h"

void HandleEvent(SDL_Event& E) {
  // ... 
}

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  
  SDL_Event Event;
  while (true) {
    while (SDL_PollEvent(&Event)) {
      HandleEvent(Event);
    }
  }
  
  SDL_Quit();
  return 0;
}

SDL_Event objects have a type field, that we can inspect to get an initial understanding of what type of event we're dealing with. SDL provides a range of helper values that we can compare against for specific event types we're interested in. For example:

#include <SDL.h>
#include "Window.h"

void HandleEvent(SDL_Event& E) {
  if (E.type == SDL_MOUSEMOTION) {
    std::cout << "Mouse moved\n";
  } else if (E.type == SDL_MOUSEBUTTONDOWN) {
    std::cout << "Mouse clicked\n";
  } else if (E.type == SDL_KEYDOWN) {
    std::cout << "Keyboard button pressed\n";
  }
}

int main(int argc, char** argv) {/*...*/}

If we move our mouse around and press some buttons, we should now see that activity being detected in our program:

Mouse moved
Mouse moved
Mouse clicked
Keyboard button pressed
Mouse moved

These various event types have further properties, letting us understand things like which button was pressed, or where the mouse moved to. We'll cover the most useful event types throughout this course, but a full list is also available on the official documentation.

Quitting the Application

We finally know everything we need to know to let users close our application. When the player requests our game close by, for example, clicking the "x" in the title bar, SDL pushes an event onto the queue. It has a type of SDL_QUIT:

#include <SDL.h>
#include "Window.h"

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  
  SDL_Event Event;
  while (true) {
    while (SDL_PollEvent(&Event)) {
      if (Event.type == SDL_QUIT) {
        // User wants to quit...
      }
    }
  }
  
  SDL_Quit();
  return 0;
}

To handle this, let's add a new shouldContinue boolean initialized to true, and update our application loop from while(true) to while(shouldContinue). When the player wants to quit, we'll set shouldContinue to false.

This causes our main loop to iterating, which means our main function will end:

#include <SDL.h>
#include "Window.h"

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  
  SDL_Event Event;
  bool shouldContinue{true};
  while (shouldContinue) {
    while (SDL_PollEvent(&Event)) {
      if (Event.type == SDL_QUIT) {
        shouldContinue = false;
      }
    }
  }
  
  SDL_Quit();
  return 0;
}

As with anything, there are multiple ways we could have handled this. We could alternatively call SDL_Quit() and return 0 from our application loop:

#include <SDL.h>
#include "Window.h"

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
  
  SDL_Event Event;
  bool shouldContinue{true};
  while (true) {
    while (SDL_PollEvent(&Event)) {
      if (Event.type == SDL_QUIT) {
        SDL_Quit();
        return 0;
      }
    }
  }
  
  SDL_Quit();
  return 0;
}

Complete Code

Our latest files, which we've updated with the event loop and SDL_QUIT handling, are below:

Summary

This lesson covered implementing the main application loop in SDL. We discussed the standard structure (initialize, loop, shutdown) and the loop's core tasks: handling events, updating game state, and rendering.

We focused on event handling, using SDL_PollEvent() within a nested loop to process all events from SDL's queue and specifically reacting to the SDL_QUIT event to enable application closure.

Key Takeaways:

  • Desktop applications rely on a continuous main loop.
  • The loop structure is typically: Process Events -> Update -> Render.
  • Events are managed in a queue; SDL_PollEvent() retrieves them.
  • Use a while loop with SDL_PollEvent() to empty the event queue each frame.
  • SDL_Event objects contain details about each event, including its type.
  • Handle SDL_QUIT to allow the application to close cleanly.
  • Remember to call SDL_PumpEvents() or SDL_PollEvent() in every iteration of your main loop.
Next Lesson
Lesson 20 of 128

Double Buffering

Learn the essentials of double buffering in C++ with practical examples and SDL2 specifics to improve your graphics projects

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

Consequences of Not Calling SDL_PollEvent() or SDL_PumpEvents() in SDL2
What happens if I don't call SDL_PollEvent() or SDL_PumpEvents() in my loop?
Understanding Application Loop Speed and CPU Usage in SDL2
How fast does the while(true) loop run? Can it use 100% CPU?
What SDL_PumpEvents() Does Internally in SDL2
What does SDL_PumpEvents() actually do behind the scenes?
Does SDL_PollEvent() Permanently Remove Events from the Queue in SDL2?
If SDL_PollEvent() pops an event, does that mean it's gone forever?
Does the SDL2 Application Loop Stop When the Window Loses Focus?
Does the application loop stop if the window loses focus?
Handling Specific Key Presses like Escape in SDL2
How do I handle specific key presses, like the Escape key, to quit?
Efficient Handling of Key Presses in SDL2
How can I efficiently handle multiple key presses in SDL2?
How to Improve Frame Rate in an SDL2 Application
What are the best practices for improving the frame rate in SDL2 applications?
Handling Window Resize Events in SDL2
How do I handle window resize events in SDL2 to adjust my rendering?
How to Use SDL Timers in Game Development
Can you explain how to use SDL timers to trigger events at regular intervals?
Dynamic Event Handling with SDL_PushEvent()
How can I dynamically trigger events in SDL2 using SDL_PushEvent()?
Or Ask your Own Question
Get an immediate answer to your specific question using our AI assistant