use crate::base::ControlFlow; use crate::prelude::*; use std::time::{Duration, Instant}; use std::path::Path; /// Uses [std::thread::sleep] to maintain the target FPS specified by the core. /// /// The sleep occurs during either `video_refresh` or at the end of `run()`. pub struct SleepFramerateLimitComponent { did_sleep: bool, started_video: bool, fps: f64, frame_begin: Instant, } impl RetroComponent for SleepFramerateLimitComponent { fn post_load_game(&mut self, retro: &mut LibretroWrapper, _rom: &Path) -> crate::base::Result<()> { self.fps = retro.get_system_av_info().timing.fps; Ok(()) } fn pre_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow { self.did_sleep = false; ControlFlow::Continue } fn post_run(&mut self, _retro: &mut LibretroWrapper) -> ControlFlow { if self.started_video && !self.did_sleep { self.do_sleep(); } ControlFlow::Continue } } impl RetroCallbacks for SleepFramerateLimitComponent { fn video_refresh(&mut self, _frame: &VideoFrame) { self.started_video = true; self.do_sleep(); } fn set_system_av_info(&mut self, system_av_info: &SystemAvInfo) -> Option { self.fps = system_av_info.timing.fps; Some(true) } } impl Default for SleepFramerateLimitComponent { fn default() -> Self { SleepFramerateLimitComponent { did_sleep: false, started_video: false, fps: 60.0, // default until load_game frame_begin: Instant::now(), } } } impl SleepFramerateLimitComponent { pub fn do_sleep(&mut self) { // similar hack to the sample rate, make sure we don't divide by zero. let mut spf = 1.0 / self.fps; if spf.is_nan() || spf.is_infinite() { spf = 1.0 / 60.0; } Duration::from_secs_f64(spf) .checked_sub(self.frame_begin.elapsed()) .map(std::thread::sleep); self.did_sleep = true; self.frame_begin = Instant::now(); } }