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 :)
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( ); }
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
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.
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?
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.
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.
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:
Apart from a performance reason, I don't really see a reason to simulate at 30Hz.
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.
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?
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.