Game loop


Every game needs a game loop. It’s just that piece of code that loop like crazy until the user closes the game. It’s what drives the game and it’s a very important part of each game.

When a user starts a game/program on a computer, the CPU executes the instructions of the program until it finishes. But what happens when the way to finish a program is by the user instruction, for instance, the user clicking on the upper right button of the window, or hit Alt-F4. You need to keep the program running until the user clicks that button. So the program needs to wait and wait and wait… until the end of times if necessary. The way to do that is with a loop.

// initialization statements
while (user don’t finishes the program)
{
    // do program relevant stuff
}
// cleanup statements (sometimes this is not needed)

So a game does that as its main block of code, and it’s common to see it like this:

// initialization statements
while (user don’t finishes the program)
{
    input();
    update();
    render()
}
// cleanup statements (sometimes these are not needed)

In that incessant loop, the game just collects input from the player, updates the game state and then renders it. That’s roughly the game loop that drives War 1. I just divide the render into two calls, render and present. The first call renders the content of the game, and the other one just put the rendered content to the screen.

Frame rate

One decision that needs to be made in each game is: what’s the target frame rate? Common “choices” are 30 fps and 60 fps, but in reality, the game can run at any “chosen” frame rate. And when I say the game can run at any frame rate I mean it can be made it so it runs at that frame rate. It can also be “chosen” not to be capped and the game will run at max frame rate depending on the computer that is running it.

The reason I write “chosen” like that, is because there is another factor that needs consideration, and that’s vertical synchronization or vsync of the monitor (a.k.a.the thing showing colors in weird patterns to the player). Apart from the actual frame rate of the game, the monitor has its frame rate (that’s is how many times it refreshes the pattern of color per second), and common ones are 60 fps (or hertz like you probably has seen previously), 75 fps and 144 fps.

So if the actual game runs at 30 fps in a monitor with a 60hz refresh rate, the game will show the same frame for two consecutive monitor frames in a row. If the game runs at 60 fps in that same monitor and it’s synchronized with vsync, the game will show each frame once per monitor frame. And the real conclusion here is that if the game runs at more than 60 fps in that same monitor, it doesn’t matter how many frames the game put into in a second, only 60 of those will be shown to the player. That desync can cause what is called screen tearing, you can read it here: https://en.wikipedia.org/wiki/Screen_tearing, or you know, google it.

All of this vague description is to say that I “choose” to run the game at 60 fps and to show what I have in that present function. I’m not taking into account vsync at the moment but it’s valid to say that I’m still improving that part of the game. I can show what I have at this moment and later update this article, or make another one.

void present()
{
    glfwSwapBuffers(context->window);
    glfwPollEvents();
    
    f32 currentTime = glfwGetTime(); 
    context->deltaTime = (currentTime - context->time);
    
    while (context->deltaTime <= SECONDS_PER_FRAME)
    {
        currentTime = (f32)glfwGetTime();
        context->deltaTime = (currentTime - context->time);
    }
    
    context->time = currentTime;
    context->fps = (u32)(1.0f / context->deltaTime);
}

Running at 60 fps means that each frame needs to be completed in ~0.016 seconds, or 16 milliseconds. Right now in a modern PC, the game finishes to compute the frame in like 7 or 8 milliseconds (that’s not true in the Raspberry Pi in which I’m developing the game and in which the frame take the whole 16 milliseconds), so by the time this function is execute each frame it has like 8 or 9 milliseconds to spare.

The first two lines in the function swap the buffers to present the newly created frame and process all windows events (needed to not freeze the program). Then I calculate the time it took for the frame to finish and proceed to wait, yes, just wait until the 16 milliseconds of the frame is reached. That’s what the while is about.

There is a problem with that though. The while is burning CPU time like crazy and it’s doing nothing at all. It’s just waiting. So, I ask myself: “are there other ways to wait that doesn’t involve wasting CPU?” And I answer myself: “Of course you dummy, just sleep the thread for the remaining time of the frame and that’s it” And I’m like: O_O.

So I tried:

void present()
{
    glfwSwapBuffers(context->window);
    glfwPollEvents();
    
    f32 currentTime = glfwGetTime(); 
    context->deltaTime = (currentTime - context->time);
    msleep((s32)((SECONDS_PER_FRAME - context->deltaTime) * 1000));
    context->time = currentTime;
    context->fps = (u32)(1.0f / context->deltaTime);
}

Easy right? Yeah, that doesn’t work quite well. In theory, sounds good, but in practice, the sleep function is not stable across operating systems. It turns out that the sleep functions (Sleep in Win32 and nanosleep in Linux) can finish either before (by interruptions on Linux for example) or after the time its supposed to be sleeping. It depends on the kernel scheduler (the thing managing CPU time for all running processes) sleep resolution and other factors.

So I can’t use this for now, and until I read more about this topic and have a way consistently run each frame in 16 milliseconds, I put the game back with the crazy while which is the stable solution I’ve now.

Files

war1-win64.zip 2 MB
Sep 17, 2019
war1-win32.zip 2 MB
Sep 17, 2019

Get War 1

Download NowName your own price

Leave a comment

Log in with itch.io to leave a comment.