595 lines
20 KiB
Rust
595 lines
20 KiB
Rust
//! Implementation of the [component system](RetroComponentBase)
|
|
//! and declaration of the [component trait](RetroComponent)
|
|
//! for composable subsets of [RetroCallbacks] implementations.
|
|
|
|
use crate::prelude::*;
|
|
use ferretro_base::retro::ffi::*;
|
|
|
|
use core::any::{Any, TypeId};
|
|
use core::pin::Pin;
|
|
|
|
use std::os::raw::c_uint;
|
|
use std::path::{PathBuf, Path};
|
|
use std::io::Read;
|
|
use std::collections::HashMap;
|
|
use std::ffi::c_void;
|
|
use ferretro_base::prelude::{LibretroWrapperAccess, RetroHandlerId};
|
|
|
|
pub struct RetroComponentBase {
|
|
retro: LibretroWrapper,
|
|
libretro_path: PathBuf,
|
|
|
|
// TODO: control when things get added to this with lifecycle constraints defined by implementers
|
|
components: Vec<Pin<Box<dyn RetroComponent>>>,
|
|
component_ptrs: HashMap<TypeId, *mut ()>,
|
|
|
|
// replaying env calls for late-added components
|
|
cached_rom_path: Option<PathBuf>,
|
|
cached_pixel_format: Option<PixelFormat>,
|
|
cached_input_descriptors: Option<Vec<InputDescriptor2>>,
|
|
cached_hw_render_callback: Option<HwRenderCallback>,
|
|
cached_variables: Option<Vec<Variable2>>,
|
|
cached_support_no_game: Option<bool>,
|
|
cached_system_av_info: Option<SystemAvInfo>,
|
|
cached_subsystem_info: Option<Vec<SubsystemInfo2>>,
|
|
cached_controller_info: Option<Vec<ControllerDescription2>>,
|
|
cached_memory_map: Option<MemoryMap>,
|
|
cached_geometry: Option<GameGeometry>,
|
|
|
|
handler_id: Option<RetroHandlerId>,
|
|
}
|
|
|
|
// TODO: replace with std::ops::ControlFlow when it becomes stable
|
|
pub enum ControlFlow {
|
|
Continue,
|
|
Break,
|
|
}
|
|
|
|
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
|
|
|
#[rustfmt::skip]
|
|
#[allow(unused_variables)]
|
|
pub trait RetroComponent: RetroCallbacks + Any {
|
|
fn pre_init(&mut self, retro: &mut LibretroWrapper) -> Result<()> { Ok(()) }
|
|
fn post_init(&mut self, retro: &mut LibretroWrapper) -> Result<()> { Ok(()) }
|
|
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(()) }
|
|
fn pre_run(&mut self, retro: &mut LibretroWrapper) -> ControlFlow { ControlFlow::Continue }
|
|
fn post_run(&mut self, retro: &mut LibretroWrapper) -> ControlFlow { ControlFlow::Continue }
|
|
}
|
|
|
|
impl RetroComponentBase {
|
|
// TODO: constructor & wrapper that uses a statically linked libretro?
|
|
pub fn new(core_path: impl AsRef<Path>) -> Pin<Box<Self>> {
|
|
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: Default::default(),
|
|
component_ptrs: Default::default(),
|
|
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,
|
|
handler_id: None,
|
|
};
|
|
|
|
let mut pin_emu = Box::pin(emu);
|
|
let id = ferretro_base::retro::set_handler(pin_emu.as_mut()).unwrap();
|
|
pin_emu.handler_id = Some(id);
|
|
pin_emu
|
|
}
|
|
|
|
pub fn init(&mut self) -> Result<()> {
|
|
for comp in &mut self.components {
|
|
comp.pre_init(&mut self.retro)?;
|
|
}
|
|
|
|
self.retro.init();
|
|
|
|
for comp in &mut self.components {
|
|
comp.post_init(&mut self.retro)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn register_component<T>(&mut self, comp: T) -> Result<()>
|
|
where T: RetroComponent + Any
|
|
{
|
|
// TODO: match comp.schedule { BeforeInit, BeforeLoad, BeforeFirstRun, Anytime }
|
|
let comp_type = comp.type_id();
|
|
if self.component_ptrs.contains_key(&comp_type) {
|
|
return Err(format!("A component of type {:?} has already been registered.", comp_type).into())
|
|
}
|
|
let mut comp = Box::pin(comp);
|
|
let comp_ptr = comp.as_mut().get_mut() as *mut T;
|
|
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);
|
|
self.component_ptrs.insert(comp_type, comp_ptr as *mut ());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn component_ref<T: RetroComponent>(&self) -> Result<&T> {
|
|
self.component_ptr()
|
|
.map(|x| unsafe { &*x })
|
|
}
|
|
|
|
pub fn component_mut<T: RetroComponent>(&mut self) -> Result<&mut T> {
|
|
self.component_ptr()
|
|
.map(|x| unsafe { &mut *x })
|
|
}
|
|
|
|
fn component_ptr<T: RetroComponent>(&self) -> Result<*mut T> {
|
|
let id = TypeId::of::<T>();
|
|
let comp_ptr = self.component_ptrs
|
|
.get(&id)
|
|
.ok_or_else(|| format!("No component of type {:?} has been registered.", id))?;
|
|
Ok(*comp_ptr as *mut T)
|
|
}
|
|
|
|
pub fn load_game(&mut self, rom: impl AsRef<Path>) -> 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 serialize_buf(&mut self, mut state: impl AsMut<[u8]>) -> Result<()> {
|
|
let state = state.as_mut();
|
|
let serialize_size = unsafe { (&self.retro.core_api.retro_serialize_size)() };
|
|
if state.len() != serialize_size {
|
|
Err(format!("serialize_size mismatch: {} != {}", state.len(), serialize_size).into())
|
|
} else {
|
|
unsafe { (&self.retro.core_api.retro_serialize)(state.as_mut_ptr() as *mut c_void, state.len()) }
|
|
Ok(()) // FIXME: make retro_serialize return bool in libretro-sys ffi
|
|
}
|
|
}
|
|
|
|
pub fn unserialize_path(&mut self, state: impl AsRef<Path>) -> 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, frame: &VideoFrame) {
|
|
for comp in &mut self.components {
|
|
comp.video_refresh(frame);
|
|
}
|
|
}
|
|
|
|
fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize {
|
|
self.components.iter_mut()
|
|
.map(|comp| comp.audio_samples(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()
|
|
}
|
|
|
|
// TODO: for this, set_pixel_format, and other mutation-themed calls returning Option<bool>,
|
|
// if any include both Some(false) and Some(true), then re-run with the previous value
|
|
// assumed to be supported by all components (to signal to true-returners to revert).
|
|
fn set_rotation(&mut self, rotation: EnvRotation) -> Option<bool> {
|
|
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<bool> {
|
|
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<bool> {
|
|
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<bool> {
|
|
self.components.iter_mut()
|
|
.map(|comp| comp.shutdown())
|
|
.flatten()
|
|
.all(|x| x)
|
|
.into()
|
|
}
|
|
|
|
fn set_performance_level(&mut self, level: c_uint) -> Option<bool> {
|
|
self.components.iter_mut()
|
|
.map(|comp| comp.set_performance_level(level))
|
|
.flatten()
|
|
.all(|x| x)
|
|
.into()
|
|
}
|
|
|
|
fn get_system_directory(&mut self) -> Option<PathBuf> {
|
|
self.components.iter_mut()
|
|
.map(|comp| comp.get_system_directory())
|
|
.flatten()
|
|
.next()
|
|
}
|
|
|
|
fn set_pixel_format(&mut self, format: PixelFormat) -> Option<bool> {
|
|
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: &[InputDescriptor2]) -> Option<bool> {
|
|
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<bool> {
|
|
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<String> {
|
|
self.components.iter_mut()
|
|
.map(|comp| comp.get_variable(key))
|
|
.flatten()
|
|
.next()
|
|
}
|
|
|
|
fn set_variables(&mut self, variables: &[Variable2]) -> Option<bool> {
|
|
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<bool> {
|
|
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<bool> {
|
|
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<PathBuf> {
|
|
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<u64> {
|
|
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<PathBuf> {
|
|
self.components.iter_mut()
|
|
.map(|comp| comp.get_core_assets_directory())
|
|
.flatten()
|
|
.next()
|
|
}
|
|
|
|
fn get_save_directory(&mut self) -> Option<PathBuf> {
|
|
self.components.iter_mut()
|
|
.map(|comp| comp.get_save_directory())
|
|
.flatten()
|
|
.next()
|
|
}
|
|
|
|
fn set_system_av_info(&mut self, system_av_info: &SystemAvInfo) -> Option<bool> {
|
|
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: &[SubsystemInfo2]) -> Option<bool> {
|
|
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: &[ControllerDescription2]) -> Option<bool> {
|
|
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<bool> {
|
|
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<bool> {
|
|
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<String> {
|
|
self.components.iter_mut()
|
|
.map(|comp| comp.get_username())
|
|
.flatten()
|
|
.next()
|
|
}
|
|
|
|
fn get_language(&mut self) -> Option<Language> {
|
|
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(&mut self) -> Time {
|
|
self.components.first_mut()
|
|
.map(|comp| comp.perf_get_time_usec())
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
fn perf_get_counter(&mut self) -> PerfTick {
|
|
self.components.first_mut()
|
|
.map(|comp| comp.perf_get_counter())
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
fn perf_get_cpu_features(&mut self) -> u64 {
|
|
self.components.first_mut()
|
|
.map(|comp| comp.perf_get_cpu_features())
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
fn perf_log(&mut self) {
|
|
if let Some(comp) = self.components.first_mut() {
|
|
comp.perf_log()
|
|
}
|
|
}
|
|
|
|
fn perf_register(&mut self, counter: &mut PerfCounter) {
|
|
if let Some(comp) = self.components.first_mut() {
|
|
comp.perf_register(counter)
|
|
}
|
|
}
|
|
|
|
fn perf_start(&mut self, counter: &mut PerfCounter) {
|
|
if let Some(comp) = self.components.first_mut() {
|
|
comp.perf_start(counter)
|
|
}
|
|
}
|
|
|
|
fn perf_stop(&mut self, counter: &mut PerfCounter) {
|
|
if let Some(comp) = self.components.first_mut() {
|
|
comp.perf_stop(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()
|
|
}
|
|
|
|
fn get_camera_interface(&mut self, width: c_uint, height: c_uint, cap_raw_fb: bool, cap_gl_tex: bool) -> Option<bool> {
|
|
self.components.iter_mut()
|
|
.map(|comp| comp.get_camera_interface(width, height, cap_raw_fb, cap_gl_tex))
|
|
.flatten()
|
|
.fold(false, |x, y| x || y) // not "any" because we don't short-circuit
|
|
.into()
|
|
}
|
|
|
|
fn camera_start(&mut self) -> Option<bool> {
|
|
self.components.iter_mut()
|
|
.map(|comp| comp.camera_start())
|
|
.flatten()
|
|
.fold(true, |x, y| x && y) // not "all" because we don't short-circuit
|
|
.into()
|
|
}
|
|
|
|
fn camera_stop(&mut self) -> Option<()> {
|
|
self.components.iter_mut()
|
|
.map(|comp| comp.camera_stop())
|
|
.flatten()
|
|
.fold((), |_, _| {}) // not "all" because we don't short-circuit
|
|
.into()
|
|
}
|
|
|
|
fn hw_get_current_framebuffer(&mut self) -> Option<usize> {
|
|
self.components.iter_mut()
|
|
.map(|comp| comp.hw_get_current_framebuffer())
|
|
.flatten()
|
|
.next()
|
|
}
|
|
|
|
fn hw_get_proc_address(&mut self, sym: &str) -> Option<*const ()> {
|
|
self.components.iter_mut()
|
|
.map(|comp| comp.hw_get_proc_address(sym))
|
|
.flatten()
|
|
.next()
|
|
}
|
|
}
|
|
|
|
impl Drop for RetroComponentBase {
|
|
fn drop(&mut self) {
|
|
ferretro_base::retro::unset_handler(self.handler_id.unwrap()).unwrap();
|
|
}
|
|
}
|