Handmade Hero»Forums»Code
100 posts
A bit confused about main loop

Hi, at this point I'm not sure there is a sort of main loop, but according to the initial vids, the win32 file had a clock that would consume leftover time in delay.

With this logic, do all mechanics and rendering, measure and then delay 16.67ms minus measurement, right? You are always aiming at keeping a fixed mechanics step, so if something goes slow next frame, the worse that can happen is simulation slows down for a moment generating some barely perceptible flick, while you keep away from tunneling (if other constraints are properly chosen etc)

If that's correct, then what if you do not want to get user input so often? For example, you may not want to record position changes with 60hz, but 30hz because for a game like this it is enough?

You would just need to split the plyer mov in two, and draw two times.

What would be wrong with this?

As an extra here, I always assume jon blow did something like this with braid rewind to help compress state/pos, but to my surprise he said somewhere he recorded everything in 60hz!

So what is broken with such apparently simple pattern?

(sorry this Q looks a bit all over the place :)

Simon Anciaux
1337 posts
A bit confused about main loop

I'm not sure I understand your question, but a common way (as far as I know) games works is by rendering at an interval, and do physics (e.g. movement, collisions) at a smaller fixed interval several times per frame (using a fixed interval for physics is important to get consistent result).

while ( running ) {
    
    update_input( );

    delta_time = get_delta_time( );

    physics_step = 0.005f;
    physics_total = 0;

    while ( physics_total + physics_step < delta_time ) {
        simulate_physics( physics_step );
        physics_total += physics_step;
    }

    /* physics_total has some time left that you might need to handle. */
   
    render( );

    swap_buffer( );
}
Mārtiņš Možeiko
2559 posts / 2 projects
A bit confused about main loop
Edited by Mārtiņš Možeiko on

Nobody really is making software rendered games for anything serious. So doing sleeping & timings manually in main loop does not really apply the same way. When Casey wrote that initial win32 code, it maybe made sense ~10-15 years back. But nowadays on modern Windows the situation is more complex.

If you want to minimize latency/waiting time from input to output rendering and get smooth frame rate, read these articles about "frame pacing"

The Elusive Frame Timing
The Elusive Frame Timing (GDC video)
The Elusive Frame Timing (GDC pdf)
Myths and Misconceptions of Frame Pacing
Swapchains and frame pacing
LatencyFleX: A new approach to game latency reduction
Frame Pacing Library
NVIDIA Reflex SDK

100 posts
A bit confused about main loop
Replying to mmozeiko (#25767)

Thanks for the references, but I don't get what you mean by "software". In my toy I'm using sdl in particular but it is call to GPU.

What does hmh do now if not the loop timing?

100 posts
A bit confused about main loop
Replying to mrmixer (#25766)

Why would you call physics several times instead of only once after time accumulates >= threshold?

Mārtiņš Možeiko
2559 posts / 2 projects
A bit confused about main loop
Edited by Mārtiņš Možeiko on
Replying to da447m (#25769)

In later code HH enables vsync and measures time between frames to use for delta time. There's large TODO commented out code to fix that.

You do physics step multiple times to use exactly same timestep for its delta time. Because frame time is varying. And varying physics timestep is bad - gives nondeterministic results, often exploads in various corner cases.

You really really want physics step to be with fixed time delta. That's why you figure how much time you need to step, and then step in fixed increments to simulate physics for needed time interval.

100 posts
A bit confused about main loop
Replying to mmozeiko (#25771)

And varying physics timestep is bad - gives nondeterministic results, often exploads in various corner cases.

I got this I believe, my question is really more simple, why calling in small dt that moves player a fixed world metric "s" each time instead of calling only once?

Example: suppose you want 60hz for mechanics, then sum_dt = 16.67ms, so if your step is 3.34f you will call mechanics [up to] 5 times and bail. Mechanics step fixed, moves player total = 3.34*s, no tunneling etc.

But Leftover for next frames anyway, which means you wont sync moves perfectly because there is really no way, but you call whole physics up to 5 times during each 16.67ms.

Now if you just wait

target_time = 16.67;
while ( running ) {
    
    update_input( );

    delta_time = get_delta_time( );

    accumulated_time += delta_time;

    if( accumulated_time > target_time ) {
        simulate_physics( /* fixed whatever step */);
        accumulated_time -= target_time;
    }

    /* physics_total has some time left that you might need to handle. */
   
    render( );

    swap_buffer( );
}

you call only once and move those 3.34*s world units all at once.

Isn't this less granular but more economical? You collide everything hopefully only once?

Simon Anciaux
1337 posts
A bit confused about main loop
Edited by Simon Anciaux on
Replying to da447m (#25779)

I only have limited physics programming experience, so don't take what I say as "the way" to do things.

I'm not sure I understand what you're saying (sorry). Based on your example code, since you'd render in each loop iteration, you'd wait for vsync each time. Meaning at most you'd update physics every 16.6ms (assuming 60Hz monitor), and I don't think it'd be a good idea because that means that you'd set your physics fixed timestep at something like 16ms. But if there is a frame that for some reason takes 30ms, you'd then do physics for only 16ms and that would probably feel wrong.

The reason I used multiple physics steps in a single frame in the past is for precision. If you think about it, the smaller the physics timestep, the more refined movement and collision becomes. It also makes the left over time at the end of the frame smaller. But since we have timing and other constraints (e.g. power consumption) we need find the balance to meet those constraints and still get good enough precision.

If doing physics only once per frame with a "big" physics steps is good enough for your game, than do that. There is no need to do more complex things if they aren't necessary. Just be aware that there are other way if the need arise.

100 posts
A bit confused about main loop
Edited by da447m on Reason: OOPS I deleted a partial comment sent by mistake sry
Replying to mrmixer (#25782)

But if there is a frame that for some reason takes 30ms, you'd then do physics for only 16ms and that would probably feel wrong.

This is even worse with the while loop because you perform physics until the whole delta_time is consumed. You slow down even more in that example, so you need another checks to bail out. You will also move more than intended.

If you think about it, the smaller the physics timestep, the more refined movement and collision becomes.

Yes this scheme gives more granularity, let's say. But if your world is built in such a way you won't get tunneling in a single frame, then you don't need this, right?

you'd set your physics fixed timestep at something like 16ms.

Yes, but each excess of time stays in the accumulator so next frame you get things using less loops.

My original question was really how to properly decouple the input tic from rendering tic, but maybe that is not such a good idea.

Simon Anciaux
1337 posts
A bit confused about main loop
Edited by Simon Anciaux on
Replying to da447m (#25784)

This is even worse with the while loop because you perform physics until the whole delta_time is consumed. You slow down even more in that example, so you need another checks to bail out. You will also move more than intended.

The amount of time you simulate isn't the same as the time spent in the simulation. What I mean is that the whole simulation loop can take 1ms or 1day (real time) to simulate 16ms (in the game) depending on the complexity of the simulation.

If a frame takes 33ms for any reason (OS takes over, rendering is expensive ...) you may (or not) want to simulate those 33ms so that if you started at some position and 33ms elapsed, you always end up at the same location independently of how many frame where displayed. Again this is not a truth, it's a way to do things but there are probably other ways.

But if your world is built in such a way you won't get tunneling in a single frame, then you don't need this, right?

Yes. Do what is necessary for your game. That's all.

Yes, but each excess of time stays in the accumulator so next frame you get things using less loops.

In the code you showed, if the accumulator starts at 0:

acc = 0;
// frame 1 : 16ms
acc += 16
if( acc >= 16 ) acc -= 16
// frame 2 : 32ms
acc += 32
if ( acc >= 16 ) acc -= 16
// frame 3 : 16ms
acc += 16 => (16 + 16 = 32)
if ( acc >= 16 ) acc -= 16
// frame 4 : 32ms
acc += 32 => (16 + 32 = 48)
if ( acc >= 16 ) acc -= 16

acc => 32
// acc will always grow because you can't have a frame < 16ms with vsync.

My original question was really how to properly decouple the input tic from rendering tic, but maybe that is not such a good idea.

Maybe more concrete information about what you're trying to do would help us understand. After reading again your first post, it seems that you want to:

  • Render at 60Hz
  • Update the game at 30Hz

Apart from a performance reason, I don't really see a reason to simulate at 30Hz.

100 posts
A bit confused about main loop
Edited by da447m on
Replying to mrmixer (#25785)

You are right, tbh my snippet was messed up, it should've been

target_time = 16.67;
while ( running ) {
    delta_time = get_delta_time( );
    accumulated_time += delta_time;

    if(accumulated_time > target_time) {
        update_input( );
        simulate_physics( /* fixed whatever step */);
        render( ); // no vsync

        accumulated_time -= target_time;
    } 
}

I think you won't get delayed, because once you miss the first refresh by some time t < 16.67ms (let's just assume it is 5ms whatever), then provided your machine takes that in average, then you will be 5 ms off but still in the next consecutive refreshes (since those 5 ms will be shaved off).

Of course, not if you use vsync because that will hold your loop. But I mean that's just how I myself simplified my mess :).

My question was how to decouple the mechanics, something like below if we assume 30hz mechanics

target_time_render = 16.67;
target_time_physics = 2*target_time_render;
while ( running ) {
    delta_time = get_delta_time();
    acc_time_physics  += delta_time;
    acc_time_render   += delta_time;

    if(acc_time_physics > target_time_physics) {
        update_input();
        simulate_physics( /* fixed step */);

        acc_time_physics -= target_time_physics;
    }
    
    if(acc_time_render > target_time_render) {
        render(); // no vsync

        acc_time_render -= target_time_render;
    }
}

You would render half movements in each render call.

Mārtiņš Možeiko
2559 posts / 2 projects
A bit confused about main loop
Edited by Mārtiņš Možeiko on
Replying to da447m (#25787)

It will update physics every second frame. It won't do "half movements" every frame. Every second frame will be identical to previous one. Why would you want that? At that point why not simply render at 30hz too?

100 posts
A bit confused about main loop
Replying to mmozeiko (#25789)

If you collected all movs during physics, put them in a queue split in halves and draw the queued movs inside render(). If player moved 10 units, then draw 5 once, 5 next.

Mārtiņš Možeiko
2559 posts / 2 projects
A bit confused about main loop
Replying to da447m (#25790)

The problem is that it won't be so simple to split it like that. If you all movements are linear - then yeah, sure you can split it like that. But that's not real physics. In more realistic physics if something hits in middle of those "10" unit simulation and changes direction/rotation/speed/etc... then you need to have actual physics simulation in middle of that 5 units to know what is actual next state - and that is equivalent of calling simulate_physics(5) twice anyway. So you cannot do simulate_physics(10) at all.

100 posts
A bit confused about main loop
Replying to mmozeiko (#25791)

True, but for realistic sim we probably cannot do rewind anyway due to colossal amount of data.

In 2D I personally don't see much fun in those realistic stuff like Box 2D, it is nicer if movs are cartoon-ish even? :)