use std::time::{Duration, Instant};
use winit::window::Window;
use crate::os;
const DEFAULT_REFRESH: Duration = Duration::from_micros(16_667);
const IDLE_TICKS: u8 = 2;
pub(crate) struct Pacing {
vsync: Option<os::Vsync>,
refresh: Duration,
tick_due: Option<Instant>,
drawn_since_tick: bool,
idle_ticks: u8,
}
impl Pacing {
pub(crate) fn new() -> Pacing {
Pacing {
vsync: None,
refresh: DEFAULT_REFRESH,
tick_due: None,
drawn_since_tick: false,
idle_ticks: 0,
}
}
pub(crate) fn keep_ticking(&mut self, window: &Window, on_tick: impl Fn() + 'static) {
self.idle_ticks = 0;
if self.vsync.is_none() {
self.vsync = os::Vsync::start(window, Box::new(on_tick));
self.refresh = refresh_interval(window);
}
match &self.vsync {
Some(vsync) => vsync.set_paused(false),
None => {
if self.tick_due.is_none() {
self.tick_due = Some(Instant::now() + self.refresh);
}
}
}
}
pub(crate) fn tick_due(&self) -> Option<Instant> {
self.tick_due
}
pub(crate) fn tick_is_due(&self, at: Instant) -> bool {
self.tick_due.is_some_and(|due| due <= at)
}
pub(crate) fn tick(&mut self, frame_wanted: bool) -> bool {
let drawn = std::mem::take(&mut self.drawn_since_tick);
if frame_wanted {
self.idle_ticks = 0;
} else {
self.idle_ticks += 1;
}
let stop = self.idle_ticks >= IDLE_TICKS;
if stop {
self.idle_ticks = 0;
}
match &self.vsync {
Some(vsync) => vsync.set_paused(stop),
None => {
self.tick_due = (!stop).then(|| {
let now = Instant::now();
let next = self.tick_due.unwrap_or(now) + self.refresh;
if next < now { now + self.refresh } else { next }
});
}
}
frame_wanted && !drawn
}
pub(crate) fn drawn(&mut self) {
self.drawn_since_tick = true;
}
pub(crate) fn window_gone(&mut self) {
self.vsync = None;
self.tick_due = None;
}
}
fn refresh_interval(window: &Window) -> Duration {
window
.current_monitor()
.and_then(|monitor| monitor.refresh_rate_millihertz())
.filter(|&millihertz| millihertz > 0)
.map(|millihertz| Duration::from_secs_f64(1000.0 / f64::from(millihertz)))
.unwrap_or(DEFAULT_REFRESH)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_wanted_frame_is_drawn_at_every_tick() {
let mut pacing = Pacing::new();
assert!(pacing.tick(true));
assert!(
pacing.tick(true),
"the frame a tick draws is that refresh's"
);
assert!(pacing.tick(true));
}
#[test]
fn a_frame_drawn_between_ticks_stands_in_for_the_next() {
let mut pacing = Pacing::new();
assert!(pacing.tick(true));
pacing.drawn();
assert!(!pacing.tick(true));
assert!(pacing.tick(true));
}
#[test]
fn the_ticks_stop_after_idle_refreshes() {
let mut pacing = Pacing::new();
assert!(pacing.tick(true));
assert!(!pacing.tick(false));
assert!(pacing.tick_due().is_some());
assert!(!pacing.tick(false));
assert!(pacing.tick_due().is_none(), "stopped after IDLE_TICKS");
}
}