let mut t: 0.0;
let dt = 1.0 / 60.0;
loop {
update(t, dt);
render();
t += dt;
}If physics delta time matches refresh rate and one can guarantee the updae loop will take less than a frame, this is ideal. However, it's nearly 100% to make this guarantees across all systems.
let mut t: 0.0;
let mut current_time = std::time::Instant::now();
loop {
let new_time = std::time::Instant::now();
let frame_time = new_time.from(current_time).as_nanos / 1000000000; // from ns to s
current_time = new_time;
update(t, frame_time);
render();
t += frame_time;
}Allows delta time to be variable but this will affect physics with inconsistent delta times.
let mut t: 0.0;
let dt = 1.0 / 60.0;
let mut current_time = std::time::Instant::now();
loop {
let new_time = std::time::Instant::now();
let mut frame_time = new_time.from(current_time).as_nanos / 1000000000; // from ns to s
current_time = new_time;
while frame_time > 0.0 {
let delta_time = std::cmp::min(frame_time, dt);
update(t, delta_time);
frame_time -= delta_time;
t += delta_time;
}
render();
}Delta time has a max. value here; it is "semi-fixed.
However, this has a problem: multiple update steps are taken every render frame. If an update is more expensive than a render, this will lead to issues such as the "spiral of death". This occurs when simulating N seconds of physics takes M real-world seconds, and M > N. The update loop will always be behind and struggling to catch up but it will always be too expensive to do so.
const dt = 0.01;
let mut t: 0.0;
let mut current_time = std::time::Instant::now();
let mut accumulator = 0.0;
loop {
let new_time = std::time::Instant::now();
let mut frame_time = new_time.from(current_time).as_nanos / 1000000000; // from ns to s
current_time = new_time;
accumulator += frame__time;
while accumulator >= dt {
update(t, dt);
accumulator -= dt;
t += dt;
}
render();
}We want a fixed delta time for simulation & the ability to render at different frame rates.
Instead of thinking about it like "updates need to take less than renders", frame it (HA!) like this: Render frames produce time and the simulation consumes it in discreet delta time-sized chunks. This allows render FPS to vary but simulations are always moved forward in delta-time sized chunks.
The remaining time in the accumulator will build up over time. This will sometimes cause two sim. frames per render. This can cause a slight difference in sim. FPS and render FPS, causing what is known as "temporal aliasing".
const dt = 0.0;
let mut t = 0.0;
let mut current_time = std::time::Instant::now();
let mut accumulator = 0.0;
let mut prev: State = State::new();
let mut curr: State = State::new();
loop {
let new_time = std::time::Instant::now();
let mut frame_time = new_time.from(current_time).as_nanos / 1000000000; // from ns to s
if frame_time > 0.25 { // where did this constant come from?
frame_time = 0.25;
}
current_time = new_time;
accumulator += frame__time;
while accumulator >= dt {
prev = curr;
update(curr, t, dt);
accumulator -= dt;
t += dt;
}
let alpha = accumulator / dt;
let state: State = curr * alpha + prev * (1.0 - alpha);
render(state); // render state should match physics state
}The 'remainder' in the accumulator can be used to see how much time is required before the next sim. step. We can use that time value (the 'alpha') to belond (via linear interlopation) between the current & previous physics states to keep sim. and render steps in porportional sync. g