Files
nesemu/src/runtime/timing.rs
se.cherkasov bdf23de8db
Some checks failed
CI / rust (push) Has been cancelled
Initial commit: NES emulator with GTK4 desktop frontend
Full NES emulation: CPU, PPU, APU, 47 mappers, iNES/NES 2.0 parsing.
GTK4 desktop client with HeaderBar, pixel-perfect Cairo rendering,
drag-and-drop ROM loading, and keyboard shortcuts.
187 tests covering core emulation, mappers, and runtime.
2026-03-13 11:48:45 +03:00

84 lines
2.1 KiB
Rust

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<Instant>,
}
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));
}
}
}
}
}