Last active
January 6, 2024 23:55
-
-
Save matthewjberger/9aa18ebe277c380085feabb8c4b13633 to your computer and use it in GitHub Desktop.
Fixed timestep example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// [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