Skip to main content
GameDev.net gamedev.net
🔒 Locked

Designing a game loop.

Started by Derek911 May 19, 2021 at 9:12 AM 11 replies 19.6k views
Original Post
Derek911
Derek911

I'm developing a simple Win game. Here is my 2 similar implementations of the game loop (in c++):

approach #1:

   while (Msg.message != WM_QUIT) {
   
     if (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE) > 0) {
      TranslateMessage(&Msg);
      DispatchMessage(&Msg);
     }
     else {
               // Do update, rendering and all the real game loop stuff
           }
   }

approach #2:

   while (Msg.message != WM_QUIT) {
   
     if (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE) > 0) {
      TranslateMessage(&Msg);
      DispatchMessage(&Msg);
     }
     
           // Do update, rendering and all the real game loop stuff
           
   }}

My question is which one is better and which one should i choose? Is it better to do the update and rendering stuff in the else closure as in the #1 approach or just in the while loop block as in the #2 approach?

Tom Sloper
Tom Sloper

Moved from Game Design And Theory to For Beginners. Game Design And Theory is not a programming forum. Sorry I can't answer your question.

-- Tom Sloper    --      sloperama.com
Alberth
Alberth

Do you have hard evidence to support a decision?

I mean, how often does 0 events happen in an iteration, how often does 1 event happen, and 2, 3, …

You can have long discussions about “better”, but without evidence that more than 1 event in an iteration is common, your entire question could be purely academic. Nice as discussion topic, but not very efficient in spending time for making a game.

SyncViews
SyncViews

I don't think this really matters if you are running the loop at a reasonable frequency. The more interesting part would be how you handle update steps (fixed or variable), frame limiting, recovering from lag, etc. which you didn't show.

The only small thing that comes to mind is because of how you did while (Msg.message != WM_QUIT) it will try to run update/render one last time after exit in example #2, but even that isn't really a big deal unless you already deleted some stuff before calling PostQuitMessage.

Gnollrunner
Gnollrunner

Not sure which one is better but I use #1 for the sole reason I started with one of Frank Luna's books and that's how he did it. So far it hasn't been an issue. I actually now have 3 game loops. The main rendering/game loop, one that does LOD in the background and one that runs the universe since in my case I have planets and a sun.

kseh
kseh

I would probably go with the 2nd approach. Process the various Windows messages first (if any), do game logic next, draw the frame. With approach 1 it seems like it'd be possible processing Windows messages could result in your game not being updated in iterations through the loop. I'm picturing simply moving the mouse around over your window results in game logic and frame drawing never being done. Unless you're planning on using WM_TIMER and WM_PAINT messages to run your logic and draw functions, which is what I used to do when I was first learning but I wouldn't really recommend that technique now.

Shaarigan
Shaarigan

From a pure code view, the first one is the better one because of the else statement in there. This allows the CPU to handle branching on your loop more efficient and can propably speed things up while it is unpredictable for the second version.

I however would like to bring in a third version and replace the if statement of the second one with a while statement. From my experience, Windows events appear in groups most of the time, even if you don't handle all of those messages, there are coming multiple of them in usually. For example when resizing or moving a window.

However, the first one is my favorite way to go because you can react to a quit message more efficiently than in the other two

SuperVGA
SuperVGA

At this point, all that matters (besides everything working) is that you think your code is easy to understand, and preferably that others can read it too.

Consider optimisations some other time.

Oberon_Command
Oberon_Command

You may find this article useful, as it goes into detail on why the “standard” event loop that most tutorials use can be a problem for CPU usage.

Shaarigan
Shaarigan

If we start talking about “the standard loop” and games, then we should also talk about why it is recommendable to avoid putting everything into the loop on the main thread and instead let that loop do what it is supposed to do, catching OS events and nothing else. I've seen this in Godot for example, where the event loop is also performing calls into the game. This is wrong in so many points, first but foremost is it slowing down the framerate when there is something happening on the Desktop at the same time, which is the case for every newer game these days, starting in windowed mode. So every OS message interrupts the game for milliseconds. Also stop catching user input in the main loop!

Geri
Geri

void engine(){

particle_effects();

calculate_anim();

do_gravity();

for(int i=0;i<3;i++) do_collision_code(); // so you wont walk into walls

render_every_3d_object();

render_every_sprites_and_texts();

swapbuffers();

if((rendered_frames%1000)==999) toilet(); // free unused memory buffers, throw unused textures, etc

}

//your game:

while(1){

// input

#ifdef WIN32

STOP USING TOY OPERATING SYSTEMS

#else

x11_poll_keyboard_mouse_joystick();

#endif

DO YOUR GAME CODE HERE

// whenever your 3d engine needs to be activated or somethign has to be rendered

engine();

}

Oberon_Command
Oberon_Command

Shaarigan said:
If we start talking about “the standard loop” and games, then we should also talk about why it is recommendable to avoid putting everything into the loop on the main thread and instead let that loop do what it is supposed to do, catching OS events and nothing else. I've seen this in Godot for example, where the event loop is also performing calls into the game.

It worked fine back when every Windows game was single-threaded. What has changed?

I would remind you that Godot performs well enough that quite a few people ship games with it. ?

Shaarigan said:
So every OS message interrupts the game for milliseconds.

Milliseconds has not been my observation. How did you measure this?

Shaarigan said:
Also stop catching user input in the main loop!

Why? Recall that checking input state directly can lead to inputs being lost due to timing issues (eg. if the user holds a keyboard button down for less than a frame).

nikkoid
nikkoid

#2 is better.

1- you must test if &Msg exists before sending it to Windows. So you have no choice other than testing.

2- there is no reason to use else here, because you are not going to update windows OR your game, you are going to update windows AND your game, so the rendering must happen regardless. There is no reason to wait for the next loop, you are slowing down you game for nothing.

The real world sucks, go W.O.K https://wolfonlinekingdom.com
a light breeze
a light breeze

You definitely want to handle all pending events as soon as you get them, without stopping to update the game state or render. In other words, something like this:

while (running) {
  while (event pending) {
    process_event();
  }
  update_game_state();
  render();
}

This is roughly equivalent to your option 1, just structured differently. The reason for handling all events immediately is to prevent a backlog of unhandled events from forming.

Endurion
Endurion

The first one, the second leads to delayed message handling (try dragging your main window for example).

Always process all messages from the queue before running your update/render code.

Fruny: Ftagn! Ia! Ia! std::time_put_byname! Mglui naflftagn std::codecvt eY'ha-nthlei!,char,mbstate_t>

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.