13
\$\begingroup\$

I recently read this article on Game Loops: http://www.koonsolo.com/news/dewitters-gameloop/

And the recommended last implementation is confusing me deeply. I don't understand how it works, and it looks like a complete mess.

I understand the principle: Update the game at a constant speed, with whatever is left render the game as many times as possible.

I take it you cannot use a:

  • Get input for 25 ticks
  • Render game for 975 ticks

Approach since you would be getting input for the first portion of the second and this would feel weird? Or is that what is going on in the article?


Essentially:

while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP)

How is that even valid?

Let's assume his values.

MAX_FRAMESKIP = 5

Let's assume next_game_tick, which was assigned moments after initialization, before the main game loop is say... 500.

Finally, as I am using SDL and OpenGL for my game, with OpenGL being used for rendering only, let's assume that GetTickCount() returns the time since SDL_Init was called, which it does.

SDL_GetTicks -- Get the number of milliseconds since the SDL library initialization.

Source: http://www.libsdl.org/docs/html/sdlgetticks.html

The author also assumes this:

DWORD next_game_tick = GetTickCount();
// GetTickCount() returns the current number of milliseconds
// that have elapsed since the system was started

If we expand the while statement we get:

while( ( 750 > 500 ) && ( 0 < 5 ) )

750 because time has passed since next_game_tick was assigned. loops is zero as you can see in the article.

So we have entered the while loop, let's do some logic and accept some input.

Yadayadayada.

At the end of the while loop, which I remind you is inside our main game loop is:

next_game_tick += SKIP_TICKS;
loops++;

Let's update what the next iteration of the while code looks like

while( ( 1000 > 540 ) && ( 1 < 5 ) )

1000 because time has elapsed getting input and doing stuff before we reached the next ineteration of the loop, where GetTickCount() is recalled.

540 because, in the code 1000 / 25 = 40, therefore, 500 + 40 = 540

1 because our loop has iterated once

5, you know why.


So, since this While loop is CLEARLY depedent on MAX_FRAMESKIP and not the intended TICKS_PER_SECOND = 25; how is the game supposed to even run correctly?

It was no surprise to me that when I implemented this into my code, correctly I might add as I simply renamed my functions to handle user input and draw the game to what the author of the article has in his example code, the game did nothing.

I placed an fprintf( stderr, "Test\n" ); inside the while loop which doesn't get printed until the game ends.

How is this game loop running 25 times a second, guaranteed, while rendering as fast as it can?

To me, unless I am missing something HUGE, it looks like... nothing.

And isn't this structure, of this while loop, supposedly running 25 times a second and then updating the game exactly what I mentioned beforehand at the start of the article?

If that is the case why couldn't we do something simple like:

while( loops < 25 )
{
    getInput();
    performLogic();

    loops++;
}

drawGame();

And count for interpolation some other way.

Forgive my extremely long question, but this article has done more harm than good to me. I am severely confused now - and have no idea how to implement a proper game loop because of all these arisen questions.

\$\endgroup\$
7
  • 1
    \$\begingroup\$ Rants are better directed toward the article's author. Which part is your objective question? \$\endgroup\$
    – Anko
    Commented Feb 18, 2013 at 13:44
  • 4
    \$\begingroup\$ Is this game loop even valid, someone explain. From my tests it doesn't have the correct structure to run 25 times a second. Explain to my why it does. Also this isn't a rant, this is a series of questions. Must I use emoticons, do I seem angry? \$\endgroup\$
    – tsujp
    Commented Feb 18, 2013 at 13:45
  • 2
    \$\begingroup\$ Since your question boils down to "What am I not understanding about this game loop", and you have lots of bold-font words, it comes off as at least exasperated. \$\endgroup\$
    – Kirbinator
    Commented Feb 18, 2013 at 15:46
  • \$\begingroup\$ @Kirbinator I can appreciate that, but I was trying to ground all that I find unusual in this article so it isn't a shallow and empty question. I don't think it is quintessential that anyway, I'd like to think I have some valid points - after all that is an article trying to teach, but not doing such a good job. \$\endgroup\$
    – tsujp
    Commented Feb 18, 2013 at 15:48
  • 8
    \$\begingroup\$ Don't get me wrong, it's a good question, it just could be 80% shorter. \$\endgroup\$
    – Kirbinator
    Commented Feb 18, 2013 at 16:15

3 Answers 3

9
\$\begingroup\$

I think the author made a tiny error:

while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP)

should be

while( GetTickCount() < next_game_tick && loops < MAX_FRAMESKIP)

That is: as long as it is not yet time to draw our next frame and while we have not skipped as much frames as MAX_FRAMESKIP we should wait.

I also don't understand why he updates next_game_tick in the loop and I assume it is another error. Since at the start of a frame you can determine when the next frame should be (when using a fixed frame rate). The next game tick does not depend on how much time we have left over after updating and rendering.

The author also makes another common error

with whatever is left render the game as many times as possible.

This means rendering the same frame multiple times. The author is even aware of that:

The game will be updated at a steady 50 times per second, and rendering is done as fast as possible. Remark that when rendering is done more than 50 times per second, some subsequent frames will be the same, so actual visual frames will be displayed at a maximum of 50 frames per second.

This only causes the GPU to do unnecessary work and if rendering takes longer than expected it could cause you to start working on your next frame later than intended so it is better to yield to the OS and wait.

\$\endgroup\$
11
  • \$\begingroup\$ +Vote. Mmmm. This indeed leaves me puzzled on what to do then. I thank you for your insight. I shall probably have a play around, the problem is really knowledge on limiting FPS or having FPS dynamically set, and how to do that. In my mind fixed input handling is required so the game runs at the same pace for everyone. This is only a simple 2D Platformer MMO (in the very long run) \$\endgroup\$
    – tsujp
    Commented Feb 18, 2013 at 15:47
  • \$\begingroup\$ While loop seems correct and incrementing next_game_tick is there for that. It is there to keep simulation running at constant speed on slower and faster hardware. Running the rendering code "as fast as possible" (or faster than physics anyway) makes sense only when there is interpolation in rendering code to make it more smooth for fast objects etc (end of the article), but that is just wasted energy if it is rendering more than what whatever output device (screen) is able to show. \$\endgroup\$
    – Dotti
    Commented Feb 18, 2013 at 16:11
  • \$\begingroup\$ So his code is a valid implementation, now that is. And we just have to live with this waste? Or are there methods I can look up for this. \$\endgroup\$
    – tsujp
    Commented Feb 18, 2013 at 16:40
  • \$\begingroup\$ Yielding to the OS and waiting is only a good idea if you have a good idea of when the OS will return control to you - which may not be as soon as you'd like. \$\endgroup\$
    – Kylotan
    Commented Feb 18, 2013 at 17:59
  • 1
    \$\begingroup\$ This is incorrect, the correct code was indeed the > next_game_tick. Well, technically it should be >=. Basically, if the current time is >= the next time you want to do logic (physics, AI, input, etc.) and you have done this logic step 4 times or less, then do logic. Otherwise, just render. You are only incorrect because you missunderstood the point of next_game_tick, it is not when you render the next frame, it is when you do logical events. \$\endgroup\$
    – TorbenC
    Commented Aug 4, 2013 at 5:47
4
\$\begingroup\$

Maybe best if I simplify it a bit:

while( game_is_running ) {

    current = GetTickCount();
    while(current > next_game_tick) {
        update_game();

        next_game_tick += SKIP_TICKS;
    }
    display_game();
}

while loop inside mainloop is used to run simulation steps from where ever it was, to where it should be now. update_game() function should always assume that only SKIP_TICKS amount of time has passed since last call. This will keep game physics running at constant speed on slow and fast hardware.

Incrementing next_game_tick by amount of SKIP_TICKS moves it closer to current time. When this gets bigger than current time is, it breaks (current > next_game_tick) and mainloop continues to render current frame.

After rendering, next call to GetTickCount() would return new current time. If this time is higher than next_game_tick it means that we are already behind 1-N steps in simulation and we should catch up, running every step in simulation at same constant speed. In this case if it is lower, it would just render same frame again (unless there is interpolation).

Original code had limited the number of loops if we are left too far away (MAX_FRAMESKIP). This only makes it actually show something and not looking like being locked up if for example resuming from suspend or game being paused in debugger for long time (assuming GetTickCount() does not stop during that time) until it has catched up with time.

To get rid of useless same frame rendering if you are not using interpolation inside display_game(), you could wrap it inside if statement like:

while (game_is_running) {
    current = GetTickCount();
    if (current > next_game_tick) {
        while(current > next_game_tick) {
            update_game();

            next_game_tick += SKIP_TICKS;
        }
    display_game();
    }
    else {
    // could even sleep here
    }
}

This is also a good article about this: http://gafferongames.com/game-physics/fix-your-timestep/

Also, maybe reason why your fprintf outputs when your game ends might just be that it did not get flushed.

Sorry about my english.

\$\endgroup\$
4
\$\begingroup\$

His code looks entirely valid.

Consider the while loop of the last set:

// JS / pseudocode
var current_time = function () { return Date.now(); }, // in ms
    framerate = 1000/30, // 30fps
    next_frame = current_time(),

    max_updates_per_draw = 5,

    iterations;

while (game_running) {

    iterations = 0;

    while (current_time() > next_frame && iterations < max_updates_per_draw) {
        update_game(); // input, physics, audio, etc

        next_frame += framerate;
        iterations += 1;
    }

    draw();
}

I've got a system in place which says "while the game is running, check the current time - if it's greater than our running framerate tally, and we've skipped drawing fewer than 5 frames, then skip drawing and just update input and physics: else draw the scene and start the next update iteration"

When each update happens, you increment the "next_frame" time by your ideal framerate. Then you check your time again. If your current time is now less than when the next_frame should be updated, then you skip over the update and draw what you've got.

If your current_time is greater (imagine that the last draw process took a really long time, because there was some hiccup somewhere, or a bunch of garbage-collection in a managed language, or a managed-memory implementation in C++ or whatever), then draw gets skipped and the next_frame gets updated with another extra frame worth of time, until either the updates catch up to where we should be on the clock, or we've skipped drawing enough frames that we absolutely MUST draw one, so the player can see what they're doing.

If your machine is super-fast or your game is super-simple, then current_time might be less than next_frame frequently, which means that you are not updating during those points.

That's where the interpolation comes in. Likewise, you could have a separate bool, declared outside of the loops, to declare "dirty" space.

Inside of the update loop, you'd set dirty = true, signifying that you've actually performed an update.

Then, instead of just calling draw(), you'd say:

if (is_dirty) {
    draw(); 
    is_dirty = false;
}

Then you're missing out on any interpolation for smooth motion, but you're ensuring that you're only updating when you've got updates actually happening (rather than interpolations between states).

If you're brave, there's an article called "Fix Your Timestep!" by GafferOnGames.
It approaches the problem slightly differently, but I consider it a prettier solution which does mostly the same thing (depending on your language's features, and how much you care about your game's physics calculations).

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .