use std::time::{Duration, Instant}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[non_exhaustive] pub enum VideoMode { Ntsc, Pal, Dendy, } impl VideoMode { pub fn from_ines_timing_mode(mode: u8) -> Self { match mode { 1 => Self::Pal, 3 => Self::Dendy, _ => Self::Ntsc, } } pub fn cpu_hz(self) -> f64 { match self { Self::Ntsc => 1_789_773.0, Self::Pal => 1_662_607.0, Self::Dendy => 1_773_448.0, } } pub fn frame_hz(self) -> f64 { match self { Self::Ntsc => 60.098_8, Self::Pal => 50.007_0, Self::Dendy => 50.0, } } pub fn frame_duration(self) -> Duration { Duration::from_secs_f64(1.0 / self.frame_hz()) } } #[derive(Debug)] pub struct FramePacer { frame_duration: Duration, next_deadline: Option, } impl FramePacer { pub fn new(mode: VideoMode) -> Self { Self::with_frame_duration(mode.frame_duration()) } pub fn with_frame_duration(frame_duration: Duration) -> Self { Self { frame_duration, next_deadline: None, } } pub fn reset(&mut self) { self.next_deadline = None; } pub fn wait_next_frame(&mut self) { let now = Instant::now(); match self.next_deadline { None => { self.next_deadline = Some(now + self.frame_duration); } Some(deadline) => { if now < deadline { std::thread::sleep(deadline - now); self.next_deadline = Some(deadline + self.frame_duration); } else { let frame_ns = self.frame_duration.as_nanos(); let late_ns = now.duration_since(deadline).as_nanos(); let missed = (late_ns / frame_ns) + 1; self.next_deadline = Some(deadline + self.frame_duration.mul_f64(missed as f64 + 1.0)); } } } } }