#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FixedTimestep {
pub step_dt: f32,
accumulator: f32,
}
impl FixedTimestep {
pub fn new(hz: f32) -> Self {
assert!(hz > 0.0 && hz.is_finite(), "hz must be positive and finite");
Self {
step_dt: 1.0 / hz,
accumulator: 0.0,
}
}
pub fn advance(&mut self, dt: f32) -> FixedStepIter<'_> {
self.accumulator += dt;
FixedStepIter { ts: self }
}
pub fn alpha(&self) -> f32 {
(self.accumulator / self.step_dt).clamp(0.0, 1.0)
}
pub fn reset(&mut self) {
self.accumulator = 0.0;
}
}
pub struct FixedStepIter<'a> {
ts: &'a mut FixedTimestep,
}
impl<'a> Iterator for FixedStepIter<'a> {
type Item = f32;
fn next(&mut self) -> Option<f32> {
if self.ts.accumulator >= self.ts.step_dt {
self.ts.accumulator -= self.ts.step_dt;
Some(self.ts.step_dt)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_steps_on_short_frame() {
let mut ts = FixedTimestep::new(60.0);
let steps: Vec<_> = ts.advance(0.005).collect();
assert!(steps.is_empty());
}
#[test]
fn test_one_step_on_normal_frame() {
let mut ts = FixedTimestep::new(60.0);
let steps: Vec<_> = ts.advance(1.0 / 60.0).collect();
assert_eq!(steps.len(), 1);
assert!((steps[0] - 1.0 / 60.0).abs() < 1e-6);
}
#[test]
fn test_multiple_steps_on_long_frame() {
let mut ts = FixedTimestep::new(60.0);
let steps: Vec<_> = ts.advance(ts.step_dt * 3.0 + 0.0001).collect();
assert_eq!(steps.len(), 3);
}
#[test]
fn test_alpha_between_zero_and_one_after_step() {
let mut ts = FixedTimestep::new(60.0);
let step_dt = 1.0 / 60.0;
ts.advance(step_dt + 0.004).for_each(drop);
let alpha = ts.alpha();
assert!(alpha > 0.0 && alpha < 1.0, "alpha was {alpha}");
}
#[test]
fn test_accumulator_carries_over_across_frames() {
let mut ts = FixedTimestep::new(60.0);
let step_dt = 1.0 / 60.0;
ts.advance(step_dt * 0.5).for_each(drop);
let steps: Vec<_> = ts.advance(step_dt * 0.5).collect();
assert_eq!(steps.len(), 1);
}
#[test]
fn test_reset_clears_accumulator() {
let mut ts = FixedTimestep::new(60.0);
ts.advance(0.5).for_each(drop);
ts.reset();
assert_eq!(ts.alpha(), 0.0);
let steps: Vec<_> = ts.advance(0.0).collect();
assert!(steps.is_empty());
}
#[test]
#[should_panic]
fn test_new_panics_on_zero_hz() {
FixedTimestep::new(0.0);
}
#[test]
#[should_panic]
fn test_new_panics_on_negative_hz() {
FixedTimestep::new(-60.0);
}
}