use std::{
hint, thread,
time::{Duration, Instant},
};
pub struct Timer {
previous: Instant,
previous_log: Instant,
target: Instant,
log_target: Instant,
delta_time: Duration,
log_interval: Duration,
prev_framecount: u64,
framecount: u64,
max_delay_frames: u32,
high_precision: bool,
}
fn sleep_until_high_precision(target: Instant) -> Instant {
let now = Instant::now();
if now >= target {
return now;
}
let approx_duration = target.duration_since(now);
#[cfg(unix)]
const MAX_BUSY_WAIT: Duration = Duration::from_micros(250);
#[cfg(not(unix))]
const MAX_BUSY_WAIT: Duration = Duration::from_millis(1);
if approx_duration > MAX_BUSY_WAIT {
thread::sleep(approx_duration - MAX_BUSY_WAIT);
}
busy_wait_until(target)
}
fn sleep_until(target: Instant) -> Instant {
let now = Instant::now();
if now >= target {
return now;
}
let suspend_duration = target - now;
thread::sleep(suspend_duration);
busy_wait_until(target)
}
fn busy_wait_until(target: Instant) -> Instant {
loop {
let time = Instant::now();
if time >= target {
break time;
}
hint::spin_loop();
}
}
#[derive(Debug)]
pub struct Log {
delta_avg: Duration,
}
impl Log {
pub fn delta_time_avg(&self) -> Duration {
self.delta_avg
}
pub fn delta_time_avg_ms(&self) -> f64 {
self.delta_avg.as_secs_f64() * 1000.
}
pub fn fps_average(&self) -> f64 {
1. / self.delta_avg.as_secs_f64()
}
}
impl Default for Timer {
fn default() -> Self {
let now = Instant::now();
let delta_time = Duration::from_secs_f64(1.0 / 60.);
let log_interval = Duration::from_millis(100);
Self {
framecount: 0,
log_interval,
previous: now,
target: now + delta_time,
previous_log: now,
prev_framecount: 0,
log_target: now + log_interval,
delta_time,
max_delay_frames: 2,
high_precision: true,
}
}
}
impl Timer {
pub fn log_interval(mut self, log_interval: Duration) -> Self {
self.log_interval = log_interval;
self.log_target = self.previous + log_interval;
self
}
pub fn frame_time(mut self, delta: Duration) -> Self {
self.delta_time = delta;
self.target = self.previous + delta;
self
}
pub fn fps(self, fps: f64) -> Self {
let duration = match fps {
0. => Duration::ZERO,
fps => Duration::from_secs_f64(1. / fps),
};
self.frame_time(duration)
}
pub fn high_precision(mut self, enabled: bool) -> Self {
self.high_precision = enabled;
self
}
pub fn frame(&mut self) -> Duration {
self.framecount += 1;
let mut current = Instant::now();
if self.delta_time > Duration::ZERO {
let behind = if current > self.target {
current - self.target
} else {
Duration::ZERO
};
if behind > self.slack() {
self.target = current;
}
if current < self.target {
current = if self.high_precision {
sleep_until_high_precision(self.target)
} else {
sleep_until(self.target)
};
}
self.target += self.delta_time;
}
let frame_time = current.duration_since(self.previous);
self.previous = current;
frame_time
}
pub fn log(&mut self) -> Option<Log> {
let current = self.previous;
if current < self.log_target {
return None;
}
let frames = (self.framecount - self.prev_framecount) as u32;
if frames == 0 {
return None;
}
let delta_avg = current.duration_since(self.previous_log) / frames;
self.log_target = current + self.log_interval;
self.previous_log = current;
self.prev_framecount = self.framecount;
Some(Log { delta_avg })
}
fn slack(&self) -> Duration {
self.max_delay_frames * self.delta_time
}
}