use std::time::Instant;
#[derive(Debug)]
pub struct Time {
elapsed: f64,
#[allow(dead_code)]
start: Instant,
last_frame: Instant,
delta: f64,
smoothed_delta: f64,
fixed_timestep: f64,
fixed_accumulator: f64,
use_fixed_timestep: bool,
frame_count: u64,
time_scale: f64,
fps_tracker: FpsTracker,
}
#[derive(Debug)]
struct FpsTracker {
samples: [f64; 60],
index: usize,
filled: bool,
}
impl Default for FpsTracker {
fn default() -> Self {
Self {
samples: [0.0; 60],
index: 0,
filled: false,
}
}
}
impl FpsTracker {
fn record(&mut self, delta: f64) {
self.samples[self.index] = delta;
self.index = (self.index + 1) % self.samples.len();
if self.index == 0 {
self.filled = true;
}
}
fn average_fps(&self) -> f64 {
let count = if self.filled {
self.samples.len()
} else {
self.index
};
if count == 0 {
return 0.0;
}
let sum: f64 = self.samples[..count].iter().sum();
if sum < 1e-9 {
return f64::INFINITY;
}
count as f64 / sum
}
}
impl Time {
pub(crate) fn new() -> Self {
let now = Instant::now();
Self {
elapsed: 0.0,
start: now,
last_frame: now,
delta: 0.0,
smoothed_delta: 1.0 / 60.0,
fixed_timestep: 1.0 / 60.0,
fixed_accumulator: 0.0,
use_fixed_timestep: false,
frame_count: 0,
time_scale: 1.0,
fps_tracker: FpsTracker::default(),
}
}
pub(crate) fn tick(&mut self) {
let now = Instant::now();
let raw_delta = now.duration_since(self.last_frame).as_secs_f64();
self.last_frame = now;
let clamped = raw_delta.min(0.25);
self.delta = clamped * self.time_scale;
self.smoothed_delta = self.smoothed_delta * 0.9 + self.delta * 0.1;
self.elapsed += self.delta;
self.frame_count += 1;
self.fps_tracker.record(self.delta);
}
pub(crate) fn fixed_step_count(&mut self) -> u32 {
if !self.use_fixed_timestep {
return 0;
}
self.fixed_accumulator += self.delta;
let max_steps = 5; let mut steps = 0;
while self.fixed_accumulator >= self.fixed_timestep && steps < max_steps {
self.fixed_accumulator -= self.fixed_timestep;
steps += 1;
}
if steps >= max_steps {
self.fixed_accumulator = 0.0;
}
steps
}
#[inline]
pub fn elapsed(&self) -> f64 {
self.elapsed
}
#[inline]
pub fn delta(&self) -> f64 {
self.delta
}
#[inline]
pub fn smoothed_delta(&self) -> f64 {
self.smoothed_delta
}
#[inline]
pub fn fps(&self) -> f64 {
self.fps_tracker.average_fps()
}
#[inline]
pub fn frame_count(&self) -> u64 {
self.frame_count
}
#[inline]
pub fn time_scale(&self) -> f64 {
self.time_scale
}
pub fn set_time_scale(&mut self, scale: f64) {
self.time_scale = scale.clamp(0.0, 10.0);
}
pub fn set_fixed_timestep(&mut self, fps: u32) {
self.use_fixed_timestep = true;
self.fixed_timestep = 1.0 / fps as f64;
self.fixed_accumulator = 0.0;
}
pub fn disable_fixed_timestep(&mut self) {
self.use_fixed_timestep = false;
self.fixed_accumulator = 0.0;
}
#[inline]
pub fn is_fixed_timestep_enabled(&self) -> bool {
self.use_fixed_timestep
}
#[inline]
pub fn fixed_delta(&self) -> f64 {
self.fixed_timestep
}
}
#[derive(Debug, Clone)]
pub struct Timer {
duration: f64,
elapsed: f64,
repeating: bool,
finished: bool,
}
impl Timer {
pub fn from_seconds(seconds: f64, repeating: bool) -> Self {
Self {
duration: seconds,
elapsed: 0.0,
repeating,
finished: false,
}
}
pub fn tick(&mut self, delta: f64) -> bool {
if self.finished && !self.repeating {
return false;
}
self.elapsed += delta;
if self.elapsed >= self.duration {
if self.repeating {
self.elapsed -= self.duration;
} else {
self.finished = true;
self.elapsed = self.duration;
}
true
} else {
false
}
}
pub fn reset(&mut self) {
self.elapsed = 0.0;
self.finished = false;
}
pub fn set_duration(&mut self, seconds: f64) {
self.duration = seconds;
self.reset();
}
pub fn fraction(&self) -> f32 {
(self.elapsed / self.duration).clamp(0.0, 1.0) as f32
}
#[inline]
pub fn is_finished(&self) -> bool {
self.finished
}
#[inline]
pub fn is_running(&self) -> bool {
!self.finished
}
pub fn remaining(&self) -> f64 {
(self.duration - self.elapsed).max(0.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_timer_basic() {
let mut t = Timer::from_seconds(1.0, false);
assert!(!t.tick(0.5));
assert!(!t.tick(0.4));
assert!(t.tick(0.2)); assert!(t.is_finished());
assert!(!t.tick(0.5)); }
#[test]
fn test_timer_repeating() {
let mut t = Timer::from_seconds(1.0, true);
assert!(!t.tick(0.5));
assert!(t.tick(0.5));
assert!(!t.tick(0.3));
assert!(t.tick(0.7));
}
}