More thoughts on fixed vs variable timesteps

posted in Gamedev info
Published January 05, 2015
Advertisement
More thoughts on fixed vs variable time steps

For some reason, this topic keeps popping into my mind. I suspect its because I wasn't convinced it was the best approach - even though i couldn't quite say why.

After my last round of thinking about it, i had come to the conclusion that the "fix-your-timestep" algo (f.y.t. for short) was actually a very clever way to render as fast as possible - irregardless of other considerations such as the slight temporal aliasing caused by the tween from previous to current state, or dropping frames to keep up when render time + remainder > ET_chunksize*2, and render times are not capped. and it was my understanding that the reason to do this was to get smoother animation.

As the Caveman 3.0 project pulls into the final stretch, one of the things on the todo list that's finally popping up is "decouple render from input and update". So i've been playing around with loop structures that decouple render.

There are a few things i don't like about f-y-t:

1. with no frame time cap, it can drop frames when render times spike, which can have a negative impact on gameplay in extreme cases. this is of particular concern to me as the types of games i tend to make (in-depth vehicle combat sims) tend to exhibit spiky rendertime behavior - especially in heavy combat - the worst time to start droping frames on the player. but using a frametime cap to avoid dropping frames can fix this. so this really isn't an issue, unless the developers forget the frametime cap - like Dynamix in Aces of the Deep. i believe silent hunter 4 has the same issue as well sometimes.

2. render is temporally aliased by up to ET_chunksize milliseconds from the current state. again, not a biggie. the difference between state at t=t0 and t=t1=t0+ET_chunksize is minimal. if physics runs at 30 fps, ET_chunksize=33ms. at a walk speed of 2 m/s, the difference between t0 and t1 is only 6.7cm.

3. its more work. hey, what can i say? i'm lazy. you need to make all of update use ET as a parameter. maybe not such a big deal for some games, but what if your update looks like this?


[color=#0000ff][size=1]void do_global_frame2()
{
int a;
BMupdate_all();
model_missiles(); // model missile movement
move_animals(); // move animals
if (last_active_animal >= 0) // model animal attacks
{
for (a=0; a <= last_active_animal; a++)
{
if (!animal[a].active) continue;
if (!animal[a].alive) continue;
if (!animal[a].attacking) continue;
animal[a].attack_counter++;
if (animal[a].attack_counter>=30) { animal[a].attacking=0; animal[a].xr=0.0f; animal[a].attack_counter=0; continue; }
if (animal[a].attack_counter==15) new_resolve_animal_attack(a);
}
}
if (last_active_animal >= 0) // model animal suprise
{
for (a=0; a <= last_active_animal; a++)
{
if (!animal[a].active) continue;
if (!animal[a].alive) continue;
if (!animal[a].suprised) continue;
animal[a].suprise_counter++;
if (animal[a].suprise_counter>60) animal[a].suprised=0;
}
}
move_rafts2();
model_falling2();
//if (gamespeed!=SPEED6)
update_clouds();
}


void do_global_second2()
{
remove_animals();
if (cm[cm0].location==INCAVERN) check_cavern_encounter_points();
model_detangling();
if (tutor_on) run_tutor();
play_ambient_audio();
}


void do_global_minute2()
{
int a;
#ifdef BETA_ANTI_CRACK
checksum_ram2();
#endif
model_fires();
remove_distant_dropped_objects();
for (a=0; a{
if (!animal[a].active) continue;
if (!animal[a].alive) continue;
if ( (animal[a].state != TAMED) && (animal[a].state != WARRIOR) && (animal[a].state != COMPANION) ) continue;
if (animal[a].dmg < 1) continue;
if (dice(10000) > 2) continue;
animal[a].dmg--;
}
remove_non_hostiles();
#ifdef BETA_ANTI_CRACK
checksum_ram3();
#endif
}


void do_global_hour2()
{
model_dropped_food_spoilage();
model_storepit_raids();
model_weather();
model_watertable();
model_natural_disasters();
if ((map[cm[cm0].mx][cm[cm0].mz].volcano) && (dice(10000)<=14)) eruption();
model_captured_animals_escaping();
if (hour==8) pay_warriors();
model_CRH_raids2();
model_dry_creeks();
model_start_wildfires();
.c++
c model_gift_encounters
c model_call4aid
#ifdef EXE_ANTI_CRACK
== data1[6] 1 // if failed exe checsum, bomb!
#ifdef DRM_MSGS_ON
c msg3 "error a3"
#endif
= cm[1000000]._hp 0
.
#endif
c++
}


void do_global_day2()
{

.c++

#ifdef EXE_ANTI_CRACK
== data1[6] 1 // if failed exe checsum, bomb!
#ifdef DRM_MSGS_ON
c msg3 "error a4"
#endif // drm_msgs_on
= cm[1000000]._hp 0
.
#endif // exe anti crack

c model_temp_shelter_weathering

c++

model_storepit_weathering();
model_cave_age();
model_raft_weathering();
change_the_map();
ZeroMemory(numconvos,sizeof(int)*maxcavemen*maxnpcs); // reset conversation counters
CRH_reduce_resources();
change_npc_stuff(); // chance to change what npc's have on them to trade
change_crhstufflists();
model_crh_relations();
model_abandoned_hut_weathering();
#ifndef NOSAVE
autosave();
#endif // nosave
}


fn v BMupdate_each i a
' a is bm #
' do update stuff here - do global frame
c BMdecrease_fatigue a
c BMmodel_attack a
c BMmodel_surprise a
c BMmodel_full_fatigue a
c B2_run_BM_AI a
c BMmodel_paddling_fatigue a
c BMmodel_falling a
c BMmodel_sneak_detection a
!= frame 0
ret
.
' do global second
c BMmodel_fatigue_dueto_damage a
c BMmodel_fatigue_dueto_encumbrance a
#ifndef demo
c BMrun_quests a
#endif
c BMclear_rockshelter_encounter_flags a
c BMcheck_rockshelter_encounters a
c BMclear_cave_encounter_flags a
c BMcheck_cave_encounters a
c BMclear_hut_encouter_flags a
c BMcheck_hut_encounters a
c BMcheck_hut_takeover a
c BMcheck_climbing_animals a
!= second 0
ret
.
' do global minute
c BMcheck_animal_encounters a
c BMcheck_caveman_encounters a
c BMmodel_intox a
c BMextinguish_torches a
== minute%7 0
c BMreduce_hygiene_dueto_movement a
.
== minute%10 0
c BMreduce_water a
c BMreduce_sleep a
.
== minute%15 0
c BMreduce_food a
c BMaffect_mood a
.
!= minute 0
ret
.
' do global hour
c BMreduce_hygiene a
c BMmodel_dehydration a
c BMmodel_food_spoilage a
c BMmodel_exposure a
c BMmodel_heatstroke a
c BMmodel_drown_in_flood a
c BMmodel_wearNtear a
c BMmodel_perm_shelter_raids a
c BMmoodboost_nature_lover a
== hour%2 0
c BMmodel_damage_dueto_illness a
.
#ifndef demo
? ((hour>7)&&(hour<19))
c BMcheck_quest_encounters a
.
#endif
!= hour 0
ret
.
' do global day
c BMmodel_starvation a
c BMmodel_getting_sick a
c BMmodel_background_radiation a
c BMmodel_perm_shel_weathering a
c BMzero_god_relations a
c BMreduce_social a
c BMmodel_traps a
c BMmodel_skill_reduction a[/color]





not quite so easy eh?
and you have to save previous state (not a biggie), and tween everything (somewhat more of a biggie).





So i played around with the game loop in Caveman some, and actually got render decoupled, but wasn't doing the tween yet. I stopped at that point because the animation engine expected a fixed timestep, and i was too lazy at the time to do the mods required to continue experimenting. i've since figured out all i need is a set_animation_frame_no_increment() routine that doesn't increment the frame count automatically when it does a tween.

Today i revisited the topic yet again, taking a slightly different tack.
instead of a goal of "same speed on all pcs and render as fast as possible" (f-y-t's goal),

i set my goals as:
1. run at the same speed on all pc's
2. always render after each input or update cycle (IE don't drop frames)
3. only render after an input or update cycle (IE don't render if camera or simulation state has not changed - don't worry about tweening)
3. render as fast as possible.
4. don't render faster than refresh rate
this is more or less the same goal as f-y-t without tweening or dropping frames or rendering faster than the refresh rate.

i won't bore you with the details, but rendering after and only after input or update means render can't be decoupled. limiting render to refresh rate can be done via wait for vsync, or a framerate limiter.

this stared to lead me to the conclusion that f-y-t was the way to go for smoother animations under variable frametimes, else use a basic loop w/ vsync or framerate limit it to refresh rate. so i was writing up the pros and cons of each. then i started applying the normal fixes to remove the cons, such as setting frametime cap = ET_chunksize so f-y-t never drops frames. i got it down to smoother animation vs more work as the tradeoff between the two. but that still didn't quite seem right since a basic loop would update before every frame, so i started plugging in some numbers and discovered that as rendertimes vary, both loops exhibit the same degree of smoothness. this was comparing a f-y-t loop w/ frametime cap=ET_chunksze vs a basic loop w/ vsync or framerate limited to refresh rate. the two loops behave identically. so it would seem that f-y-t saves previous state, tweens, and makes update use ET - all for naught!?!?!?

obviously, the next question is, when would f-y-t behave differently? and is that a condition we even care about? if not, why use f-y-t?

that's as far along as i've gotten so far on this train of thought.

if anyone can find a flaw in my logic, please speak up. either i'm missing something, or perhaps this f-y-t stuff simply "does not compute". it wouldn't be the first time i found an error in the textbook...

until next time, i guess a couple good parting quotes might be:

"question authority."

- and -

"question conventional wisdom."
0 likes 4 comments

Comments

Aardvajk

I don't really understand your objections to the decoupled approach. You mention dropping frames but I'm not sure what you mean here. The fix your timestep most certainly does compute and has been used extensively in the industry for a long time so its unlikely to have a fundamental flaw, no offence.

What exactly is it you think is wrong with this approach, concisely? I really don't understand your explanations so far.

There is a lot more to this approach than smoother animations. It is a way to ensure that your physics can run in a deterministic way with a fixed timestep (important even for jump height in a classic Mario game, so not just for physics sims) while not requiring any kind of cap on the rendering and allowing the hardware to work in any setup, including a slower system that renders slowly.

The aliasing part is for smoothing out the animations and works very well.

Frame rate cap cannot be guaranteed by any method you employ. Users can set their graphics cards to ignore vsync even if it is turned on by the software for example. And if you end up on a PC that cannot render as fast as your cap, the whole thing falls apart.

I use the temporal aliasing approach in all my games and I find it pretty trivial to be honest. I have a class that stores the previous and current state of anything that needs to be aliased, and all my rendering methods take a blend parameter that is fed into these states to get the final value for the render frame. I don't understand what the problem is here either.

January 05, 2015 11:26 AM
JTippetts
Code tags are your amigo, amigo.
January 05, 2015 09:46 PM
Norman Barrows

>> What exactly is it you think is wrong with this approach, concisely? I really don't understand your explanations so far.

it may no longer be valid with the speed of todays pcs.

its been my experience in the past that games which appear to use f-y-t and can experience spikey high ets will - as f-y-t does - skip renders (IE process more than one update per render). if your frame rate drops to something like 6 or 3 fps for a second two at just the wrong time, you're screwed. update is still running at 30Hz. but now you (the player via your i/o interface - IE input and render) are running at 3 fps - 1/10th the speed of the game. since all motion is relative, from the player's point of view its almost as though update suddenly started running at 300fps!

just TRY to fire off one or two fish at a jap or british destroyer bearing straight down on you then crash dive when everything in the game - EXCEPT YOU - is running at 10x accelerated time. you're usually lucky to just avoid a collision. and the last 30-60 minutes of gaming time spent finding a target and setting up an attack run has been wasted. often i'd stop playing the game entirely for a few days, my dissatisfaction was so great.

my second issue wth f-y-t is its a more complex way than than a frame rate limiter to run update at a fixed speed under normal conditions. unnecessary complexity is against my religion - especially when it comes to physics and graphics code.

January 26, 2015 10:27 PM
Norman Barrows

Code tags are your amigo, amigo.

sometimes it doesn't seem to work when i mix code and non-code blocks in a post. often everything after the first code block gets lost.

but its worked so far today, so who knows, maybe a syntax or typo error on my part.

January 26, 2015 10:30 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement