use core::time::Duration;
#[derive(Debug, Clone)]
pub struct FrameClock {
step: Duration,
accumulator: Duration,
max_accumulate: Duration,
}
impl FrameClock {
#[must_use]
pub fn new(hz: u32) -> Self {
assert!(hz > 0, "FrameClock hz must be non-zero");
let step = Duration::from_secs_f64(1.0 / f64::from(hz));
Self {
step,
accumulator: Duration::ZERO,
max_accumulate: step * 5,
}
}
#[must_use]
pub const fn step(&self) -> Duration {
self.step
}
#[must_use]
pub const fn dt_secs(&self) -> f64 {
self.step.as_secs_f64()
}
pub fn advance(&mut self, dt: Duration) {
self.accumulator = (self.accumulator + dt).min(self.max_accumulate);
}
#[must_use]
pub fn tick(&mut self) -> bool {
if self.accumulator >= self.step {
self.accumulator -= self.step;
true
} else {
false
}
}
#[must_use]
pub fn alpha(&self) -> f64 {
self.accumulator.as_secs_f64() / self.step.as_secs_f64()
}
pub const fn reset(&mut self) {
self.accumulator = Duration::ZERO;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn drains_expected_steps() {
let mut clock = FrameClock::new(100); clock.advance(Duration::from_millis(35));
let mut steps = 0;
while clock.tick() {
steps += 1;
}
assert_eq!(steps, 3);
assert!((clock.alpha() - 0.5).abs() < 1e-6);
}
#[test]
fn caps_catch_up() {
let mut clock = FrameClock::new(60);
clock.advance(Duration::from_secs(10));
let mut steps = 0;
while clock.tick() {
steps += 1;
}
assert_eq!(steps, 5); }
#[test]
fn reset_clears_accumulator() {
let mut clock = FrameClock::new(60);
clock.advance(Duration::from_millis(100));
clock.reset();
assert!(!clock.tick());
}
}