thoughts on fix-your-timestep and the spiral of death

posted in Gamedev info
Published February 03, 2017
Advertisement

thoughts on fix-your-timestep and the spiral of death:

in the fix-your timestep algo its generally assumed that on average ET will be < DT.

with no ET cap, when accumulator >= 2*DT, fix-your-timestep will update twice before rendering (IE it drops a frame).

while this is only an issue with slow software, it can be a game-breaker.

the typical approach is to cap ET to some max value. This guarantees that update will run no more than N times in a row before the next render, where N = (accumulator+ET) / DT (rounded down).

but accumulator can be any value from 0 to DT. So it would probably be more accurate to cap accumulator, not ET.

But what cap value?

It depends on how many frames you're willing to drop.

To never drop frames, the cap is DT.

this would lead to a slightly different update:

instead of:accumulator+=ETwhile (accumulator>=DT) { accumulator-=DT update_all }
you get something like:accumulator+-ETif (accumulator >=DT) { accumulator-=DT update_all if (accumulator>=DT) { // we've hit spiral of death and are updating more than once per render! } }
But now, what do you do if you hit the spiral of death?


Well, we don't want to drop a frame. From the point of view of the player, when we drop a frame, update starts running faster at the same time that render slows down. This makes it hard for the player to react in time.

So we don't want to call update again, we want to render again first. Yes, this puts render in lockstep with update temporarily - same as any ET cap.

But what do we do with accumulator? Leave it the way it is? It's already full or overflowing. and next update, we'll just add more ET to it, making it even more over-full. So that won't work. Setting it to DT (just full) will guarantee an update after the next render. But if you just set it to zero, at worst, render suddenly gets its act back together and ET drops back below DT, and you render a few frames before the next update_all. So setting it to zero seems pretty safe.

This leads to the following:accumulator+=ETif (accumulator >= DT) { accumulator-=DT update_all if (accumulator>=DT) { // accumulator>=DT*2 spiral of death / frame drop! // clear accumulator, and don't call update_all. accumulator=0 } }
This algo implements fix-your-timestep with an accumulator cap of DT, and will never drop a frame. When ET goes high, it reverts to lockstep behavior of one render, one input, one update per cycle - which degrades gracefully under heavy load - just what you need when ET is high. Note that by comparison, your typical ET cap will only revert to lockstep behavior of the form: one render, one input, N updates per cycle, where N = (accumulator+ET) / DT (rounded down). With a low enough ET cap (specifically: capping accumulator to DT, or ET to DT-accumulator... its the same thing), similar behavior can be obtained with an ET cap and the more common "while (accumulator>=DT)" form of the algo.


Happy coding! :)

0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement