use crate::prelude::*; use ferretro_base::retro::ffi::*; use std::os::raw::c_uint; use std::path::{PathBuf, Path}; use std::pin::Pin; use std::io::Read; pub struct RetroComponentBase { retro: LibretroWrapper, libretro_path: PathBuf, // TODO: control when things get added to this with lifetime constraints defined by implementers components: Vec>, // replaying env calls for late-added components cached_rom_path: Option, cached_pixel_format: Option, cached_input_descriptors: Option>, cached_hw_render_callback: Option, cached_variables: Option>, cached_support_no_game: Option, cached_system_av_info: Option, cached_subsystem_info: Option>, cached_controller_info: Option>, cached_memory_map: Option, cached_geometry: Option, } // TODO: replace with std::ops::ControlFlow when it becomes stable pub enum ControlFlow { Continue, Break, } pub type Result = std::result::Result>; #[rustfmt::skip] #[allow(unused_variables)] pub trait RetroComponent: RetroCallbacks { fn pre_run(&mut self, retro: &mut LibretroWrapper) -> ControlFlow { ControlFlow::Continue } fn post_run(&mut self, retro: &mut LibretroWrapper) -> ControlFlow { ControlFlow::Continue } fn pre_load_game(&mut self, retro: &mut LibretroWrapper, rom: &Path) -> Result<()> { Ok(()) } fn post_load_game(&mut self, retro: &mut LibretroWrapper, rom: &Path) -> Result<()> { Ok(()) } } impl RetroComponentBase { // TODO: constructor & wrapper that uses a statically linked libretro? pub fn new(core_path: impl AsRef) -> Pin> { let lib = libloading::Library::new(core_path.as_ref()).unwrap(); let raw_retro = ferretro_base::retro::loading::LibretroApi::from_library(lib).unwrap(); let retro = LibretroWrapper::from(raw_retro); let emu = RetroComponentBase { retro, libretro_path: core_path.as_ref().to_path_buf(), components: Vec::new(), cached_rom_path: None, cached_pixel_format: None, cached_input_descriptors: None, cached_hw_render_callback: None, cached_variables: None, cached_support_no_game: None, cached_system_av_info: None, cached_subsystem_info: None, cached_controller_info: None, cached_memory_map: None, cached_geometry: None }; let mut pin_emu = Box::pin(emu); ferretro_base::retro::wrapper::set_handler(pin_emu.as_mut()); pin_emu.retro.init(); pin_emu } pub fn register_component(&mut self, comp: T) -> Option<()> // TODO: Result where T: RetroComponent { // TODO: match comp.schedule { BeforeInit, BeforeLoad, BeforeFirstRun, Anytime } let mut comp = Box::new(comp); if let Some(cached) = &self.cached_pixel_format { if let Some(false) = comp.set_pixel_format(*cached) { // TODO: error, and propagate this pattern downward } } if let Some(cached) = &self.cached_input_descriptors { comp.set_input_descriptors(cached); } if let Some(cached) = &self.cached_hw_render_callback { comp.set_hw_render(cached); } if let Some(cached) = &self.cached_variables { comp.set_variables(cached); } if let Some(cached) = &self.cached_support_no_game { comp.set_support_no_game(*cached); } if let Some(cached) = &self.cached_system_av_info { comp.set_system_av_info(cached); } if let Some(cached) = &self.cached_subsystem_info { comp.set_subsystem_info(cached); } if let Some(cached) = &self.cached_controller_info { comp.set_controller_info(cached); } if let Some(cached) = &self.cached_memory_map { comp.set_memory_maps(cached); } if let Some(cached) = &self.cached_geometry { comp.set_geometry(cached); } if let Some(cached) = &self.cached_rom_path { comp.post_load_game(&mut self.retro, &cached); } self.components.push(comp); Some(()) } pub fn load_game(&mut self, rom: impl AsRef) -> Result<()> { let path = rom.as_ref(); self.cached_rom_path = Some(path.to_path_buf()); for comp in &mut self.components { comp.pre_load_game(&mut self.retro, path)?; } let mut data = None; let mut v = Vec::new(); if !self.retro.get_system_info().need_fullpath { if let Ok(mut f) = std::fs::File::open(path) { if f.read_to_end(&mut v).is_ok() { data = Some(v.as_ref()); } } } self.retro.load_game(Some(path), data, None)?; for comp in &mut self.components { comp.post_load_game(&mut self.retro, path)?; } Ok(()) } pub fn run(&mut self) -> ControlFlow { for comp in &mut self.components { if let ControlFlow::Break = comp.pre_run(&mut self.retro) { return ControlFlow::Break; } } self.retro.run(); for comp in &mut self.components { if let ControlFlow::Break = comp.post_run(&mut self.retro) { return ControlFlow::Break; } } ControlFlow::Continue } pub fn unserialize_path(&mut self, state: impl AsRef) -> Result<()> { let path = state.as_ref(); let mut v = Vec::new(); if let Ok(mut f) = std::fs::File::open(path) { if f.read_to_end(&mut v).is_ok(){ return self.unserialize_buf(v); } } Err("Couldn't read file to unserialize".into()) } pub fn unserialize_buf(&mut self, data: impl AsRef<[u8]>) -> Result<()> { self.retro.unserialize(data.as_ref()) } } impl LibretroWrapperAccess for RetroComponentBase { fn libretro_core(&mut self) -> &mut LibretroWrapper { &mut self.retro } } impl RetroCallbacks for RetroComponentBase { fn video_refresh(&mut self, data: &[u8], width: c_uint, height: c_uint, pitch: c_uint) { for comp in &mut self.components { comp.video_refresh(data, width, height, pitch); } } fn video_refresh_dupe(&mut self, width: c_uint, height: c_uint, pitch: c_uint) { for comp in &mut self.components { comp.video_refresh_dupe(width, height, pitch); } } fn audio_sample(&mut self, left: i16, right: i16) { for comp in &mut self.components { comp.audio_sample(left, right); } } fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize { self.components.iter_mut() .map(|comp| comp.audio_sample_batch(stereo_pcm)) .max() .unwrap_or_default() } fn input_poll(&mut self) { for comp in &mut self.components { comp.input_poll(); } } fn input_state(&mut self, port: u32, device: InputDeviceId, index: InputIndex) -> i16 { self.components.iter_mut() .map(|comp| comp.input_state(port, device, index)) .filter(|x| *x != 0) // TODO: is this really the semantic we want? .last() .unwrap_or_default() } fn set_rotation(&mut self, rotation: EnvRotation) -> Option { self.components.iter_mut() .map(|comp| comp.set_rotation(rotation)) .flatten() .fold(false, |x, y| x || y) // not "any" because we don't short-circuit .into() } fn get_overscan(&mut self) -> Option { self.components.iter_mut() .map(|comp| comp.get_overscan()) .fold(None, |x, y| match (x, y) { (Some(a), Some(b)) => Some(a || b), (Some(a), None) | (None, Some(a)) => Some(a), (None, None) => None, }) } fn set_message(&mut self, message: &Message) -> Option { self.components.iter_mut() .map(|comp| comp.set_message(message)) .flatten() .fold(false, |x, y| x || y) // not "any" because we don't short-circuit .into() } fn shutdown(&mut self) -> Option { self.components.iter_mut() .map(|comp| comp.shutdown()) .flatten() .all(|x| x) .into() } fn set_performance_level(&mut self, level: c_uint) -> Option { self.components.iter_mut() .map(|comp| comp.set_performance_level(level)) .flatten() .all(|x| x) .into() } fn get_system_directory(&mut self) -> Option { self.components.iter_mut() .map(|comp| comp.get_system_directory()) .flatten() .next() } fn set_pixel_format(&mut self, format: PixelFormat) -> Option { self.cached_pixel_format = Some(format); self.components.iter_mut() .map(|comp| comp.set_pixel_format(format)) .flatten() .all(|x| x) .into() } fn set_input_descriptors(&mut self, input_descriptors: &Vec) -> Option { self.cached_input_descriptors = Some(input_descriptors.to_vec()); self.components.iter_mut() .map(|comp| comp.set_input_descriptors(input_descriptors)) .flatten() .fold(false, |x, y| x || y) .into() } fn set_hw_render(&mut self, hw_render_callback: &HwRenderCallback) -> Option { self.cached_hw_render_callback = Some(hw_render_callback.to_owned()); self.components.iter_mut() .map(|comp| comp.set_hw_render(hw_render_callback)) .flatten() .all(|x| x) .into() } fn get_variable(&mut self, key: &str) -> Option { self.components.iter_mut() .map(|comp| comp.get_variable(key)) .flatten() .next() } fn set_variables(&mut self, variables: &Vec) -> Option { self.cached_variables = Some(variables.to_vec()); self.components.iter_mut() .map(|comp| comp.set_variables(variables)) .flatten() .fold(false, |x, y| x || y) .into() } fn get_variable_update(&mut self) -> Option { self.components.iter_mut() .map(|comp| comp.get_variable_update()) .flatten() .reduce(|x, y| x || y) } fn set_support_no_game(&mut self, supports_no_game: bool) -> Option { self.cached_support_no_game = Some(supports_no_game); self.components.iter_mut() .map(|comp| comp.set_support_no_game(supports_no_game)) .flatten() .all(|x| x) .into() } // allow it to be overridden, but we *do* have the answer at this level since we loaded it. fn get_libretro_path(&mut self) -> Option { self.components.iter_mut() .map(|comp| comp.get_libretro_path()) .flatten() .next() .unwrap_or_else(|| self.libretro_path.clone()) .into() } fn get_input_device_capabilities(&mut self) -> Option { self.components.iter_mut() .map(|comp| comp.get_input_device_capabilities()) .flatten() .reduce(|x, y| x & y) } fn get_core_assets_directory(&mut self) -> Option { self.components.iter_mut() .map(|comp| comp.get_core_assets_directory()) .flatten() .next() } fn get_save_directory(&mut self) -> Option { self.components.iter_mut() .map(|comp| comp.get_save_directory()) .flatten() .next() } fn set_system_av_info(&mut self, system_av_info: &SystemAvInfo) -> Option { self.cached_system_av_info = Some(system_av_info.to_owned()); self.cached_geometry = Some(system_av_info.geometry.clone()); self.components.iter_mut() .map(|comp| comp.set_system_av_info(system_av_info)) .flatten() .fold(false, |x, y| x || y) // not "any" because we don't short-circuit .into() } fn set_subsystem_info(&mut self, subsystem_info: &Vec) -> Option { self.cached_subsystem_info = Some(subsystem_info.to_vec()); self.components.iter_mut() .map(|comp| comp.set_subsystem_info(subsystem_info)) .flatten() .all(|x| x) .into() } fn set_controller_info(&mut self, controller_info: &Vec) -> Option { self.cached_controller_info = Some(controller_info.to_vec()); self.components.iter_mut() .map(|comp| comp.set_controller_info(controller_info)) .flatten() .all(|x| x) .into() } fn set_memory_maps(&mut self, memory_map: &MemoryMap) -> Option { self.cached_memory_map = Some(memory_map.to_owned()); self.components.iter_mut() .map(|comp| comp.set_memory_maps(memory_map)) .flatten() .all(|x| x) .into() } fn set_geometry(&mut self, game_geometry: &GameGeometry) -> Option { self.cached_geometry = Some(game_geometry.to_owned()); self.components.iter_mut() .map(|comp| comp.set_geometry(game_geometry)) .flatten() .fold(false, |x, y| x || y) // not "any" because we don't short-circuit .into() } fn get_username(&mut self) -> Option { self.components.iter_mut() .map(|comp| comp.get_username()) .flatten() .next() } fn get_language(&mut self) -> Option { self.components.iter_mut() .map(|comp| comp.get_language()) .flatten() .next() } fn log_print(&mut self, level: LogLevel, msg: &str) { for comp in &mut self.components { comp.log_print(level, msg); } } fn set_rumble_state(&mut self, port: c_uint, effect: RumbleEffect, strength: u16) -> bool { self.components.iter_mut() .map(|comp| comp.set_rumble_state(port, effect, strength)) .fold(false, |x, y| x || y) // not "any" because we don't short-circuit } fn perf_get_time_usec_cb(&mut self) -> Time { self.components.first_mut() .map(|comp| comp.perf_get_time_usec_cb()) .unwrap_or_default() } fn perf_get_counter_cb(&mut self) -> PerfTick { self.components.first_mut() .map(|comp| comp.perf_get_counter_cb()) .unwrap_or_default() } fn perf_get_cpu_features_cb(&mut self) -> u64 { self.components.first_mut() .map(|comp| comp.perf_get_cpu_features_cb()) .unwrap_or_default() } fn perf_log_cb(&mut self) { if let Some(comp) = self.components.first_mut() { comp.perf_log_cb() } } fn perf_register_cb(&mut self, counter: &mut PerfCounter) { if let Some(comp) = self.components.first_mut() { comp.perf_register_cb(counter) } } fn perf_start_cb(&mut self, counter: &mut PerfCounter) { if let Some(comp) = self.components.first_mut() { comp.perf_start_cb(counter) } } fn perf_stop_cb(&mut self, counter: &mut PerfCounter) { if let Some(comp) = self.components.first_mut() { comp.perf_stop_cb(counter) } } fn set_sensor_state(&mut self, port: c_uint, action: SensorAction, rate: c_uint) -> bool { self.components.iter_mut() .map(|comp| comp.set_sensor_state(port, action, rate)) .fold(false, |x, y| x || y) // not "any" because we don't short-circuit } fn get_sensor_input(&mut self, port: c_uint, id: c_uint) -> f32 { self.components.iter_mut() .map(|comp| comp.get_sensor_input(port, id)) .filter(|x| *x != 0.0) .last() .unwrap_or_default() } } impl Drop for RetroComponentBase { fn drop(&mut self) { ferretro_base::retro::wrapper::unset_handler(); } }