Skip to content

Instantly share code, notes, and snippets.

@matthewjberger
Last active January 6, 2024 23:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matthewjberger/9aa18ebe277c380085feabb8c4b13633 to your computer and use it in GitHub Desktop.
Save matthewjberger/9aa18ebe277c380085feabb8c4b13633 to your computer and use it in GitHub Desktop.
Fixed timestep example
use std::time::{Instant};
#[derive(Clone)] // Derive the Clone trait to enable cloning of State
struct State {
// Example fields representing the state of the physics object
position: f64,
velocity: f64,
}
// Function to update the physics state of the object
fn integrate(state: &mut State, t: f64, dt: f64) {
// Here, the integration logic updates position and velocity
// This is a simplistic implementation - replace with actual physics integration logic
state.position += state.velocity * dt;
state.velocity += t * dt;
}
// Function to render the state
fn render(state: &State, alpha: f64) {
// Rendering the state for visualization. In a real application, this would draw the object
println!("Rendering state at t={}, position={}, velocity={}", alpha, state.position, state.velocity);
}
fn main() {
let mut t = 0.0; // Current time in the simulation
let dt = 1.0 / 60.0; // Fixed delta time step
let mut current_time = Instant::now(); // Current real time
let mut accumulator = 0.0; // Accumulator for tracking time between frames
let mut previous_state = State { position: 0.0, velocity: 0.0 };
let mut current_state = State { position: 0.0, velocity: 0.0 };
loop {
let new_time = Instant::now();
// Calculate frame time, ensuring it's not too large to avoid spiral of death
let frame_time = new_time.duration_since(current_time).as_secs_f64().min(0.25);
current_time = new_time;
accumulator += frame_time; // Add frame time to accumulator
// Process all the accumulated time in fixed dt steps
while accumulator >= dt {
previous_state = current_state.clone(); // Clone current_state instead of moving it
integrate(&mut current_state, t, dt); // Integrate state forward by dt
t += dt; // Advance simulation time
accumulator -= dt; // Reduce accumulator
}
// Calculate alpha for interpolation (how far between previous and current state)
let alpha = accumulator / dt;
// Interpolate between previous and current state for smoother rendering
let interpolated_state = State {
position: previous_state.position * (1.0 - alpha) + current_state.position * alpha,
velocity: previous_state.velocity * (1.0 - alpha) + current_state.velocity * alpha,
};
// Render the interpolated state
render(&interpolated_state, alpha);
}
}
// [dependencies]
// winit = "0.28.7"
//! # Fixed Timestep Module
//!
//! This module provides a fixed timestep system for simulations and games in Rust.
//! It is designed to ensure consistent and stable updates in simulations, regardless of the frame rate.
//!
//! ## Concept
//!
//! A fixed timestep decouples the simulation update logic from the frame rendering rate.
//! This approach ensures that the simulation progresses at a consistent, predetermined rate,
//! making it independent of the frame rate which can vary due to system performance or load.
//!
//! - **Timestep (`dt`)**: A constant duration representing the time interval for each simulation update.
//! - **Accumulator**: Accumulates the total elapsed time. When it exceeds `dt`, the simulation is updated.
//! - **Alpha**: A factor for interpolating between the current and previous states for smoother rendering.
//!
//! ## Usage
//!
//! The `FixedTimestep` struct is used within a loop (like a game loop). At each iteration:
//!
//! 1. The frame time is calculated and added to the accumulator.
//! 2. If the accumulator has more time than `dt`, the simulation state is updated, and `dt` is subtracted from the accumulator.
//! 3. The remainder in the accumulator (less than `dt`) represents the partial progress towards the next update.
//! 4. This partial progress (`alpha`) is used to interpolate between the current and previous states for rendering.
//!
//! This method ensures that the simulation logic runs at a consistent rate, defined by `dt`,
//! regardless of how often the rendering or the loop iteration happens.
//!
//! ## Example
//!
//! The following is a basic usage example (omitting the actual simulation and rendering logic):
//!
//! ```rust
//! let mut fixed_timestep = FixedTimestep::new(1.0 / 60.0);
//!
//! loop {
//! fixed_timestep.begin_frame();
//! while fixed_timestep.should_update() {
//! // Update simulation
//! fixed_timestep.step();
//! }
//! let alpha = fixed_timestep.alpha();
//! // Render using alpha for interpolation
//! }
//! ```
//!
//! This module is ideal for games and real-time simulations where consistent and stable update rates are crucial.
use std::time::Instant;
use winit::{
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
#[derive(Clone)]
struct State {
position: f64,
velocity: f64,
}
fn integrate(state: &mut State, t: f64, dt: f64) {
state.position += state.velocity * dt;
state.velocity += t * dt;
}
struct FixedTimestep {
timestep: f64,
accumulator: f64,
current_time: Instant,
}
impl FixedTimestep {
pub fn new(timestep: f64) -> Self {
Self {
timestep,
accumulator: 0.0,
current_time: Instant::now(),
}
}
pub fn begin_frame(&mut self) -> f64 {
let new_time = Instant::now();
let frame_time = new_time
.duration_since(self.current_time)
.as_secs_f64()
.min(0.25);
self.current_time = new_time;
self.accumulator += frame_time;
frame_time
}
pub fn should_update(&self) -> bool {
self.accumulator >= self.timestep
}
pub fn step(&mut self) {
self.accumulator -= self.timestep;
}
pub fn alpha(&self) -> f64 {
self.accumulator / self.timestep
}
}
fn main() {
let event_loop = EventLoop::new();
let _window = WindowBuilder::new()
.with_title("Fixed Timestep Demo")
.build(&event_loop)
.unwrap();
let mut t = 0.0;
let mut fixed_timestep = FixedTimestep::new(1.0 / 60.0);
let mut previous_state = State {
position: 0.0,
velocity: 0.1,
};
let mut current_state = State {
position: 0.0,
velocity: 0.1,
};
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Poll;
match event {
Event::NewEvents(StartCause::Init) => *control_flow = ControlFlow::Poll,
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
Event::MainEventsCleared => {
fixed_timestep.begin_frame();
while fixed_timestep.should_update() {
previous_state = current_state.clone();
integrate(&mut current_state, t, fixed_timestep.timestep);
t += fixed_timestep.timestep;
fixed_timestep.step();
}
let alpha = fixed_timestep.alpha();
let interpolated_state = State {
position: previous_state.position * (1.0 - alpha)
+ current_state.position * alpha,
velocity: previous_state.velocity * (1.0 - alpha)
+ current_state.velocity * alpha,
};
println!(
"Render: Position = {}, Velocity = {}",
interpolated_state.position, interpolated_state.velocity
);
}
_ => {}
}
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment