Implementing an Application Loop

Step-by-step guide on creating the SDL2 application and event loops for interactive games
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Get Started for Free
Abstract art representing computer programming
Ryan McCombe
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.

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

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

  while (true) {
    SDL_PumpEvents();
  }

  SDL_Quit();
  return 0;
}
// Window.h
#pragma once
#include <iostream>
#include <SDL.h>

class Window {
public:
  Window(){
    SDLWindow = SDL_CreateWindow(
      "Hello Window",
      SDL_WINDOWPOS_UNDEFINED,
      SDL_WINDOWPOS_UNDEFINED,
      800, 300, 0
    );

    SDL_FillRect(
      GetSurface(),
      nullptr,
      SDL_MapRGB(
        GetSurface()->format, 50, 50, 50
      )
    );

    SDL_UpdateWindowSurface(SDLWindow);
    
  }

  SDL_Surface* GetSurface() {
    return SDL_GetWindowSurface(SDLWindow);
  }
  
  Window(const Window&) = delete;
  Window& operator=(const Window&) = delete;
  
  ~Window() {
    if (SDLWindow && SDL_WasInit(SDL_INIT_VIDEO)) {
      SDL_DestroyWindow(SDLWindow);
    }
  }
  
private:
  SDL_Window* SDLWindow{nullptr};
};

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", respectively. We push events to the back of the queue, and pop them from the front:

Diagram showing an event queue

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.

Diagram showing an event loop interacting with an event 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.

Output Parameters

The design pattern where a function communicates with its caller by modifying an argument it provided is used very frequently in SDL’s API. This design is sometimes referred to as an output parameter, or out parameter. In C++, output parameters are implemented as non-const references or, in the case of SDL_PollEvent(), a pointer to a non-const object.

When designing our own programs, output parameters are generally something to avoid. It’s more intuitive to have a function communicate with it’s caller by returning a value.

The main benefit of output parameters is performance - updating an existing object is often faster than creating a new one. So, we may want to consider using an output parameter design in scenarios where our function is updating some large object that the caller provides, or when our function is being called very frequently.

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 it popped from the queue. We’ll see how to react to events in the next section.

Application Loops: Common Mistakes

It’s fairly common for our initial attempts at application loops to be flawed. We may also not notice that something is wrong as, when our program is simple, a flawed application loop may seem to be working correctly.

Let’s take a look at two of the most common mistakes. First, we have this:

while(true) {
  while(SDL_PollEvent(&Event)) {
    // 1. Process Event
    // 2. Update Objects
    // 3. Render Changes
  }
}

This loop forces the application loop and event loop to be in lockstep. This is problematic because, when no events are happening, our objects will not update. Many objects, such as those playing animations, want to be updating even when no events are happening.

Another example of a flawed application loop might look something like this:

while(true) {
  SDL_PollEvent(&Event);
  // 1. Process Event
  // 2. Update Objects
  // 3. Render Changes
}

The application fixes the previous problem - it will now update even when the event queue is empty. However, in this case, a maximum of one event can be processed on every iteration of our main loop.

When lots of events are happening, this means there can be a significant delay between each event and our application’s reaction to it. These delays make our program less responsive.

We should make sure our application loop follows the nested loop pattern we introduced in this lesson:

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

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

This ensures our application loop will iterate even when no events are happening and, when multiple events occur in quick succession, all of them can be handled within the same iteration of our application loop.

SDL_PollEvent() vs 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 let’s us 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, causing the loop to 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;
}

SDL_QUIT vs SDL_Quit

There may be some confusion here, as we're using two identifiers with very similar names in this lesson. C++ is case-sensitive, so the difference in their capitalization matters.

SDL_QUIT is an event type, whilst SDL_Quit is a similarly-named, but unrelated function that is used to request SDL clean up and shut itself down.

Preview: Pushing Events and Custom Event Types

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 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. For example, we might have a custom quit button in our UI that could push this event. Then, once our event loop sees it, it will terminate the main loop and shut our program down.

We can also use the SDL event queue for custom event types, specific to our application. This might include events for when the player completes a level or gains a powerup.

We’ll be using this in our later projects as, whenever we’re managing a large collection of objects, an event queue is a useful pattern for letting disparate parts of our application communicate with each other.

Complete Code

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

#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) {
        SDL_Quit();
        return 0;
      }
    }
  }

  return 0;
}
#pragma once
#include <iostream>
#include <SDL.h>

class Window {
public:
  Window(){
    SDLWindow = SDL_CreateWindow(
      "Hello Window",
      SDL_WINDOWPOS_UNDEFINED,
      SDL_WINDOWPOS_UNDEFINED,
      800, 300, 0
    );

    SDL_FillRect(
      GetSurface(),
      nullptr,
      SDL_MapRGB(
        GetSurface()->format, 50, 50, 50
      )
    );

    SDL_UpdateWindowSurface(SDLWindow);
  }

  SDL_Surface* GetSurface() {
    return SDL_GetWindowSurface(SDLWindow);
  }
  
  Window(const Window&) = delete;
  Window& operator=(const Window&) = delete;
  
  ~Window() {
    if (SDLWindow && SDL_WasInit(SDL_INIT_VIDEO)) {
      SDL_DestroyWindow(SDLWindow);
    }
  }
  
private:
  SDL_Window* SDLWindow{nullptr};
};

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.
Free and Unlimited Access

Professional C++

Unlock the true power of C++ by mastering complex features, optimizing performance, and learning expert workflows used in professional development

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Ryan McCombe
Ryan McCombe
Updated
sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Get Started for Free
Creating an SDL Application
sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

This course includes:

  • 108 Lessons
  • 92% Positive Reviews
  • Regularly Updated
  • Help and FAQs
Free and Unlimited Access

Professional C++

Unlock the true power of C++ by mastering complex features, optimizing performance, and learning expert workflows used in professional development

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2025 - All Rights Reserved