From 47d7113b03b2481ae4cd0aa8db9257fb6c57edae Mon Sep 17 00:00:00 2001 From: lifning <> Date: Wed, 11 Aug 2021 00:42:13 -0700 Subject: [PATCH] implement the rest of the callbacks in the dispatcher --- examples/ffmpeg_recorder.rs | 22 ++- examples/sdl2_emulator.rs | 4 +- src/components/mod.rs | 283 +++++++++++++++++++----------------- src/lib.rs | 2 +- src/retro/wrapped_types.rs | 2 +- src/retro/wrapper.rs | 136 ++++++++--------- 6 files changed, 238 insertions(+), 211 deletions(-) diff --git a/examples/ffmpeg_recorder.rs b/examples/ffmpeg_recorder.rs index f9c5e95..2469dd6 100644 --- a/examples/ffmpeg_recorder.rs +++ b/examples/ffmpeg_recorder.rs @@ -24,6 +24,7 @@ struct MyEmulator { av_info: SystemAvInfo, audio_buf: Vec<(i16, i16)>, video_pixel_format: format::Pixel, + prev_video_frame: Option, video_frames: VecDeque, video_encoder: ffmpeg::encoder::Video, audio_encoder: ffmpeg::encoder::Audio, @@ -220,6 +221,7 @@ static bool ffmpeg_init_config(struct ff_config_param *params, av_info: av_info.clone(), audio_buf: Default::default(), video_pixel_format: format::Pixel::RGB555, + prev_video_frame: None, video_frames: Default::default(), video_encoder, audio_encoder, @@ -393,11 +395,13 @@ static bool ffmpeg_init_config(struct ff_config_param *params, } } -impl retro::wrapper::RetroCallbacks for MyEmulator { +impl retro::wrapper::LibretroWrapperAccess for MyEmulator { fn libretro_core(&mut self) -> &mut LibretroWrapper { &mut self.retro } +} +impl retro::wrapper::RetroCallbacks for MyEmulator { fn video_refresh(&mut self, data: &[u8], width: u32, height: u32, pitch: u32) { let mut vframe = frame::Video::new(self.video_pixel_format, width, height); @@ -420,9 +424,19 @@ impl retro::wrapper::RetroCallbacks for MyEmulator { //vframe.set_pts(Some(self.frame as i64)); + self.prev_video_frame.replace(vframe.clone()); self.video_frames.push_back(vframe); } + fn video_refresh_dupe(&mut self, width: u32, height: u32, _pitch: u32) { + if let Some(frame) = &self.prev_video_frame { + self.video_frames.push_back(frame.clone()); + } else { + let vframe = frame::Video::new(self.video_pixel_format, width, height); + self.video_frames.push_back(vframe); + } + } + fn audio_sample(&mut self, left: i16, right: i16) { self.audio_buf.push((left, right)); } @@ -434,8 +448,6 @@ impl retro::wrapper::RetroCallbacks for MyEmulator { stereo_pcm.len() } - fn get_can_dupe(&mut self) -> Option { Some(false) } - fn get_system_directory(&mut self) -> Option { self.sys_path.clone() } @@ -465,7 +477,7 @@ impl retro::wrapper::RetroCallbacks for MyEmulator { if system_av_info.timing.sample_rate.round() as i32 > 0 { self.audio_encoder.set_rate(system_av_info.timing.sample_rate.round() as i32); } - self.av_info.timing = system_av_info.timing; + self.av_info.timing = system_av_info.timing.clone(); self.set_geometry(&system_av_info.geometry); true } @@ -499,7 +511,7 @@ impl retro::wrapper::RetroCallbacks for MyEmulator { } } - fn set_variables(&mut self, variables: Vec) -> bool { + fn set_variables(&mut self, variables: &Vec) -> bool { for v in variables { eprintln!("{:?}", v); } diff --git a/examples/sdl2_emulator.rs b/examples/sdl2_emulator.rs index 18b3d1b..eda1978 100644 --- a/examples/sdl2_emulator.rs +++ b/examples/sdl2_emulator.rs @@ -204,11 +204,13 @@ impl Drop for MyEmulator { } } -impl retro::wrapper::RetroCallbacks for MyEmulator { +impl retro::wrapper::LibretroWrapperAccess for MyEmulator { fn libretro_core(&mut self) -> &mut LibretroWrapper { &mut self.retro } +} +impl retro::wrapper::RetroCallbacks for MyEmulator { fn video_refresh(&mut self, data: &[u8], width: u32, height: u32, pitch: u32) { let rect = Rect::new(0, 0, width, height); diff --git a/src/components/mod.rs b/src/components/mod.rs index 3e63a0b..97a9754 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,83 +1,69 @@ use crate::prelude::*; +use crate::retro::ffi::*; use std::os::raw::c_uint; -use std::rc::Rc; -use crate::retro::ffi::{Message, PixelFormat, HwRenderCallback, SensorInterface, CameraCallback, LogCallback, PerfCallback, LocationCallback, SystemAvInfo, GetProcAddressInterface, MemoryMap, GameGeometry, Language, LogLevel, RumbleEffect, Time, PerfTick, PerfCounter, SensorAction}; use std::path::PathBuf; -#[rustfmt::skip] -#[allow(unused)] -pub trait RetroAvCommonComponent { - fn set_system_av_info(&mut self, system_av_info: &SystemAvInfo) -> bool { false } -} - -#[rustfmt::skip] -#[allow(unused)] -pub trait RetroVideoComponent: RetroAvCommonComponent { - fn video_refresh(&mut self, data: &[u8], width: c_uint, height: c_uint, pitch: c_uint) {} - // -- environment callbacks -- - fn set_rotation(&mut self, rotation: EnvRotation) -> bool { false } - fn get_overscan(&mut self) -> Option { None } - fn set_pixel_format(&mut self, format: PixelFormat) -> bool { false } - fn set_hw_render(&mut self, hw_render_callback: &HwRenderCallback) -> bool { false } - fn set_geometry(&mut self, game_geometry: &GameGeometry) -> bool { false } -} - -#[rustfmt::skip] -#[allow(unused)] -pub trait RetroAudioComponent: RetroAvCommonComponent { - fn audio_sample(&mut self, left: i16, right: i16) {} - fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize { 0 } -} - pub struct RetroComponentBase { retro: LibretroWrapper, - pub video_comps: Vec>, - pub audio_comps: Vec>, + pub components: Vec>, } -#[allow(unused)] -impl RetroCallbacks for RetroComponentBase { +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 vc in &mut self.video_comps { - // FIXME: can't have more than 1 ref! - // get_mut fails if one component struct impls both Audio&Video! - Rc::get_mut(vc).unwrap().video_refresh(data, width, height, pitch); + 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) { - todo!() + for comp in &mut self.components { + comp.video_refresh_dupe(width, height, pitch); + } } fn audio_sample(&mut self, left: i16, right: i16) { - todo!() + for comp in &mut self.components { + comp.audio_sample(left, right); + } } fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize { - todo!() + self.components.iter_mut() + .map(|comp| comp.audio_sample_batch(stereo_pcm)) + .max() + .unwrap_or_default() } fn input_poll(&mut self) { - todo!() + for comp in &mut self.components { + comp.input_poll(); + } } fn input_state(&mut self, port: u32, device: InputDeviceId, index: InputIndex) -> i16 { - todo!() + 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) -> bool { - self.video_comps.iter_mut() - .map(|x| Rc::get_mut(x).unwrap().set_rotation(rotation)) + self.components.iter_mut() + .map(|comp| comp.set_rotation(rotation)) .fold(false, |x, y| x || y) // not "any" because we don't short-circuit } fn get_overscan(&mut self) -> Option { - self.video_comps.iter_mut() - .map(|x| Rc::get_mut(x).unwrap().get_overscan()) + 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), @@ -85,177 +71,212 @@ impl RetroCallbacks for RetroComponentBase { }) } - fn set_message(&mut self, message: Message) -> bool { - todo!() + fn set_message(&mut self, message: &Message) -> bool { + self.components.iter_mut() + .map(|comp| comp.set_message(message)) + .fold(false, |x, y| x || y) // not "any" because we don't short-circuit } fn shutdown(&mut self) -> bool { - todo!() - } - - fn set_performance_level(&mut self, level: c_uint) -> bool { - todo!() - } - - fn get_system_directory(&mut self) -> Option { - todo!() - } - - fn set_pixel_format(&mut self, format: PixelFormat) -> bool { - self.video_comps.iter_mut() - .map(|x| Rc::get_mut(x).unwrap().set_pixel_format(format)) + self.components.iter_mut() + .map(|comp| comp.shutdown()) .all(|x| x) } - fn set_input_descriptors(&mut self, input_descriptors: Vec) -> bool { - todo!() + fn set_performance_level(&mut self, level: c_uint) -> bool { + self.components.iter_mut() + .map(|comp| comp.set_performance_level(level)) + .all(|x| x) + } + + 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) -> bool { + self.components.iter_mut() + .map(|comp| comp.set_pixel_format(format)) + .all(|x| x) + } + + fn set_input_descriptors(&mut self, input_descriptors: &Vec) -> bool { + self.components.iter_mut() + .map(|comp| comp.set_input_descriptors(input_descriptors)) + .fold(false, |x, y| x || y) } fn set_hw_render(&mut self, hw_render_callback: &HwRenderCallback) -> bool { - self.video_comps.iter_mut() - .map(|x| Rc::get_mut(x).unwrap().set_hw_render(hw_render_callback)) + self.components.iter_mut() + .map(|comp| comp.set_hw_render(hw_render_callback)) .all(|x| x) } fn get_variable(&mut self, key: &str) -> Option { - todo!() + self.components.iter_mut() + .map(|comp| comp.get_variable(key)) + .flatten() + .next() } - fn set_variables(&mut self, variables: Vec) -> bool { - todo!() + fn set_variables(&mut self, variables: &Vec) -> bool { + self.components.iter_mut() + .map(|comp| comp.set_variables(variables)) + .fold(false, |x, y| x || y) } fn get_variable_update(&mut self) -> Option { - todo!() + 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) -> bool { - todo!() + self.components.iter_mut() + .map(|comp| comp.set_support_no_game(supports_no_game)) + .all(|x| x) } fn get_libretro_path(&mut self) -> Option { - todo!() + self.components.iter_mut() + .map(|comp| comp.get_libretro_path()) + .flatten() + .next() } fn get_input_device_capabilities(&mut self) -> Option { - todo!() - } - - fn get_sensor_interface(&mut self) -> Option { - todo!() - } - - fn get_camera_interface(&mut self) -> Option { - todo!() - } - - fn get_log_interface(&mut self) -> Option { - todo!() - } - - fn get_perf_interface(&mut self) -> Option { - todo!() - } - - fn get_location_interface(&mut self) -> Option { - todo!() + 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 { - todo!() + self.components.iter_mut() + .map(|comp| comp.get_core_assets_directory()) + .flatten() + .next() } fn get_save_directory(&mut self) -> Option { - todo!() + self.components.iter_mut() + .map(|comp| comp.get_save_directory()) + .flatten() + .next() } fn set_system_av_info(&mut self, system_av_info: &SystemAvInfo) -> bool { - // TODO: avoid calling twice for components that are registered in both simultaneously - - let mut result = false; - - for vc in &mut self.video_comps { - result |= Rc::get_mut(vc).unwrap().set_system_av_info(system_av_info); - } - - for ac in &mut self.audio_comps { - result |= Rc::get_mut(ac).unwrap().set_system_av_info(system_av_info); - } - - result + self.components.iter_mut() + .map(|comp| comp.set_system_av_info(system_av_info)) + .fold(false, |x, y| x || y) // not "any" because we don't short-circuit } - fn set_proc_address_callback(&mut self, cb: GetProcAddressInterface) -> bool { - todo!() + fn set_subsystem_info(&mut self, subsystem_info: &Vec) -> bool { + self.components.iter_mut() + .map(|comp| comp.set_subsystem_info(subsystem_info)) + .all(|x| x) } - fn set_subsystem_info(&mut self, subsystem_info: Vec) -> bool { - todo!() + fn set_controller_info(&mut self, controller_info: &Vec) -> bool { + self.components.iter_mut() + .map(|comp| comp.set_controller_info(controller_info)) + .all(|x| x) } - fn set_controller_info(&mut self, controller_info: Vec) -> bool { - todo!() - } - - fn set_memory_maps(&mut self, memory_map: MemoryMap) -> bool { - todo!() + fn set_memory_maps(&mut self, memory_map: &MemoryMap) -> bool { + self.components.iter_mut() + .map(|comp| comp.set_memory_maps(memory_map)) + .all(|x| x) } fn set_geometry(&mut self, game_geometry: &GameGeometry) -> bool { - self.video_comps.iter_mut() - .map(|x| Rc::get_mut(x).unwrap().set_geometry(game_geometry)) + self.components.iter_mut() + .map(|comp| comp.set_geometry(game_geometry)) .fold(false, |x, y| x || y) // not "any" because we don't short-circuit } fn get_username(&mut self) -> Option { - todo!() + self.components.iter_mut() + .map(|comp| comp.get_username()) + .flatten() + .next() } fn get_language(&mut self) -> Option { - todo!() + self.components.iter_mut() + .map(|comp| comp.get_language()) + .flatten() + .next() } fn log_print(&mut self, level: LogLevel, msg: &str) { - todo!() + 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 { - todo!() + 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 { - todo!() + self.components.first_mut() + .map(|comp| comp.perf_get_time_usec_cb()) + .unwrap_or_default() } fn perf_get_counter_cb(&mut self) -> PerfTick { - todo!() + self.components.first_mut() + .map(|comp| comp.perf_get_counter_cb()) + .unwrap_or_default() } fn perf_get_cpu_features_cb(&mut self) -> u64 { - todo!() + self.components.first_mut() + .map(|comp| comp.perf_get_cpu_features_cb()) + .unwrap_or_default() } fn perf_log_cb(&mut self) { - todo!() + if let Some(comp) = self.components.first_mut() { + comp.perf_log_cb() + } } fn perf_register_cb(&mut self, counter: &mut PerfCounter) { - todo!() + if let Some(comp) = self.components.first_mut() { + comp.perf_register_cb(counter) + } } fn perf_start_cb(&mut self, counter: &mut PerfCounter) { - todo!() + if let Some(comp) = self.components.first_mut() { + comp.perf_start_cb(counter) + } } fn perf_stop_cb(&mut self, counter: &mut PerfCounter) { - todo!() + 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 { - todo!() + 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 { - todo!() + self.components.iter_mut() + .map(|comp| comp.get_sensor_input(port, id)) + .filter(|x| *x != 0.0) + .last() + .unwrap_or_default() } } diff --git a/src/lib.rs b/src/lib.rs index b3cc73e..e9ae16f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,5 +7,5 @@ pub mod components; pub mod prelude { pub use crate::retro::constants::*; pub use crate::retro::wrapped_types::*; - pub use crate::retro::wrapper::{RetroCallbacks, LibretroWrapper}; + pub use crate::retro::wrapper::{RetroCallbacks, LibretroWrapper, LibretroWrapperAccess}; } diff --git a/src/retro/wrapped_types.rs b/src/retro/wrapped_types.rs index c51bd76..dd9f1e6 100644 --- a/src/retro/wrapped_types.rs +++ b/src/retro/wrapped_types.rs @@ -6,7 +6,7 @@ use std::slice::from_raw_parts; use super::constants::*; use super::ffi::*; -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub enum InputDeviceId { None(c_uint), Joypad(JoypadButton), diff --git a/src/retro/wrapper.rs b/src/retro/wrapper.rs index 2a3bdf7..d17d916 100644 --- a/src/retro/wrapper.rs +++ b/src/retro/wrapper.rs @@ -6,6 +6,7 @@ use std::convert::TryFrom; use std::ffi::{CStr, CString}; use std::ops::Deref; use std::os::raw::{c_char, c_uint}; +use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; use std::pin::Pin; use std::time::Duration; @@ -28,19 +29,41 @@ extern "C" { fn c_ext_set_log_print_cb(cb: WrappedLogPrintFn); } +pub trait LibretroWrapperAccess { + fn libretro_core(&mut self) -> &mut LibretroWrapper; +} + +// method docs largely copied/lightly-adapted-to-rust straight from libretro.h. #[rustfmt::skip] #[allow(unused)] -pub trait RetroCallbacks: Unpin + 'static { - fn libretro_core(&mut self) -> &mut LibretroWrapper; - fn delegate_handler(&self) -> Option<&mut dyn RetroCallbacks> { None } - +pub trait RetroCallbacks: LibretroWrapperAccess + Unpin + 'static { // -- main callbacks -- + /// Render a frame. Pixel format is 15-bit 0RGB1555 native endian + /// unless changed (see [Self::set_pixel_format]). + /// + /// Width and height specify dimensions of buffer. + /// Pitch specifices length in bytes between two lines in buffer. + /// + /// For performance reasons, it is highly recommended to have a frame + /// that is packed in memory, i.e. pitch == width * byte_per_pixel. + /// Certain graphic APIs, such as OpenGL ES, do not like textures + /// that are not packed in memory. fn video_refresh(&mut self, data: &[u8], width: c_uint, height: c_uint, pitch: c_uint) {} /// Called instead of video_refresh when the core reports a duplicate frame (NULL). fn video_refresh_dupe(&mut self, width: c_uint, height: c_uint, pitch: c_uint) {} + /// Renders a single audio frame. Should only be used if implementation + /// generates a single sample at a time. + /// Format is signed 16-bit native endian. fn audio_sample(&mut self, left: i16, right: i16) {} + /// Renders multiple audio frames in one go. + /// + /// One frame is defined as a sample of left and right channels, interleaved. + /// I.e. int16_t buf\[4\] = { l, r, l, r }; would be 2 frames. + /// Only one of the audio callbacks must ever be used. fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize { stereo_pcm.len() } + /// Polls input. fn input_poll(&mut self) {} + /// Queries for input for player 'port'. fn input_state(&mut self, port: u32, device: InputDeviceId, index: InputIndex) -> i16 { 0 } // -- environment callbacks -- @@ -57,7 +80,7 @@ pub trait RetroCallbacks: Unpin + 'static { /// Should not be used for trivial messages, which should simply be /// logged via [Self::get_log_interface] (or as a /// fallback, stderr). - fn set_message(&mut self, message: Message) -> bool { false } + fn set_message(&mut self, message: &Message) -> bool { false } /// Requests the frontend to shutdown. /// Should only be used if game has a specific /// way to shutdown the game from a menu item or similar. @@ -102,7 +125,7 @@ pub trait RetroCallbacks: Unpin + 'static { /// It is up to the frontend to present this in a usable way. /// This function can be called at any time, but it is recommended /// for the core to call it as early as possible. - fn set_input_descriptors(&mut self, input_descriptors: Vec) -> bool { false } + fn set_input_descriptors(&mut self, input_descriptors: &Vec) -> bool { false } /// Sets an interface to let a libretro core render with /// hardware acceleration. /// The core should call this in [libretro_sys::CoreAPI::retro_load_game]. @@ -143,7 +166,7 @@ pub trait RetroCallbacks: Unpin + 'static { /// /// Only strings are operated on. The possible values will /// generally be displayed and stored as-is by the frontend. - fn set_variables(&mut self, variables: Vec) -> bool { false } + fn set_variables(&mut self, variables: &Vec) -> bool { false } /// Result is set to true if some variables are updated by /// frontend since last call to [Self::get_variable]. /// Variables should be queried with [Self::get_variable]. @@ -168,53 +191,6 @@ pub trait RetroCallbacks: Unpin + 'static { /// Example bitmask: caps = (1 << [libretro_sys::DEVICE_JOYPAD]) | (1 << [libretro_sys::DEVICE_ANALOG]). /// Should only be called in [libretro_sys::CoreAPI::retro_run]. fn get_input_device_capabilities(&mut self) -> Option { None } - /// Gets access to the sensor interface. - /// The purpose of this interface is to allow - /// setting state related to sensors such as polling rate, - /// enabling/disable it entirely, etc. - /// Reading sensor state is done via the normal - /// [Self::input_state] API. - fn get_sensor_interface(&mut self) -> Option { None } - /// Gets an interface to a video camera driver. - /// A libretro core can use this interface to get access to a - /// video camera. - /// New video frames are delivered in a callback in same - /// thread as [libretro_sys::CoreAPI::retro_run]. - /// - /// GET_CAMERA_INTERFACE should be called in [libretro_sys::CoreAPI::retro_load_game](). - /// - /// Depending on the camera implementation used, camera frames - /// will be delivered as a raw framebuffer, - /// or as an OpenGL texture directly. - /// - /// The core has to tell the frontend here which types of - /// buffers can be handled properly. - /// An OpenGL texture can only be handled when using a - /// libretro GL core ([Self::set_hw_render]). - /// It is recommended to use a libretro GL core when - /// using camera interface. - /// - /// The camera is not started automatically. The retrieved start/stop - /// functions must be used to explicitly - /// start and stop the camera driver. - fn get_camera_interface(&mut self) -> Option { None } - /// Gets an interface for logging. This is useful for - /// logging in a cross-platform way - /// as certain platforms cannot use stderr for logging. - /// It also allows the frontend to - /// show logging information in a more suitable way. - /// If this interface is not used, libretro cores should - /// log to stderr as desired. - fn get_log_interface(&mut self) -> Option { None } - /// Gets an interface for performance counters. This is useful - /// for performance logging in a cross-platform way and for detecting - /// architecture-specific features, such as SIMD support. - fn get_perf_interface(&mut self) -> Option { None } - /// Gets access to the location interface. - /// The purpose of this interface is to be able to retrieve - /// location-based information from the host device, - /// such as current latitude / longitude. - fn get_location_interface(&mut self) -> Option { None } /// Returns the "core assets" directory of the frontend. /// This directory can be used to store specific assets that the /// core relies upon, such as art assets, @@ -267,15 +243,6 @@ pub trait RetroCallbacks: Unpin + 'static { /// If this returns false, the frontend does not acknowledge a /// changed av_info struct. fn set_system_av_info(&mut self, system_av_info: &SystemAvInfo) -> bool { false } - /// Allows a libretro core to announce support for the - /// get_proc_address() interface. - /// This interface allows for a standard way to extend libretro where - /// use of environment calls are too indirect, - /// e.g. for cases where the frontend wants to call directly into the core. - /// - /// If a core wants to expose this interface, [Self::set_proc_address_callback] - /// **MUST** be called from within [libretro_sys::CoreAPI::retro_set_environment]. - fn set_proc_address_callback(&mut self, cb: GetProcAddressInterface) -> bool { false } /// This environment call introduces the concept of libretro "subsystems". /// A subsystem is a variant of a libretro core which supports /// different kinds of games. @@ -290,7 +257,7 @@ pub trait RetroCallbacks: Unpin + 'static { /// /// If a core wants to expose this interface, [Self::set_subsystem_info] /// **MUST** be called from within [libretro_sys::CoreAPI::retro_set_environment]. - fn set_subsystem_info(&mut self, subsystem_info: Vec) -> bool { false } + fn set_subsystem_info(&mut self, subsystem_info: &Vec) -> bool { false } /// This environment call lets a libretro core tell the frontend /// which controller subclasses are recognized in calls to /// [libretro_sys::CoreAPI::retro_set_controller_port_device]. @@ -324,7 +291,7 @@ pub trait RetroCallbacks: Unpin + 'static { /// /// NOTE: Even if special device types are set in the libretro core, /// libretro should only poll input based on the base input device types. - fn set_controller_info(&mut self, controller_info: Vec) -> bool { false } + fn set_controller_info(&mut self, controller_info: &Vec) -> bool { false } /// This environment call lets a libretro core tell the frontend /// about the memory maps this core emulates. /// This can be used to implement, for example, cheats in a core-agnostic way. @@ -336,7 +303,7 @@ pub trait RetroCallbacks: Unpin + 'static { /// [libretro_sys::CoreAPI::retro_get_memory_size] as well. /// /// Can be called from [libretro_sys::CoreAPI::retro_init] and [libretro_sys::CoreAPI::retro_load_game]. - fn set_memory_maps(&mut self, memory_map: MemoryMap) -> bool { false } + fn set_memory_maps(&mut self, memory_map: &MemoryMap) -> bool { false } /// This environment call is similar to [Self::set_system_av_info] for changing /// video parameters, but provides a guarantee that drivers will not be /// reinitialized. @@ -367,14 +334,37 @@ pub trait RetroCallbacks: Unpin + 'static { // fn set_serialization_quirks(&mut self, quirks: &mut u64) -> bool { false } // -- environment-set callbacks (API extensions) -- + /// Logging function. fn log_print(&mut self, level: LogLevel, msg: &str) {} + /// Sets rumble state for joypad plugged in port 'port'. + /// Rumble effects are controlled independently, + /// and setting e.g. strong rumble does not override weak rumble. + /// Strength has a range of \[0, 0xffff\]. + /// + /// Returns true if rumble state request was honored. + /// Calling this before first [libretro_sys::CoreAPI::retro_run] is likely to return false. fn set_rumble_state(&mut self, port: c_uint, effect: RumbleEffect, strength: u16) -> bool { false } + /// Returns current time in microseconds. + /// Tries to use the most accurate timer available. fn perf_get_time_usec_cb(&mut self) -> Time { 0 } + /// A simple counter. Usually nanoseconds, but can also be CPU cycles. + /// Can be used directly if desired (when creating a more sophisticated + /// performance counter system). fn perf_get_counter_cb(&mut self) -> PerfTick { 0 } + /// Returns a bit-mask of detected CPU features ([libretro_sys]::SIMD_*). fn perf_get_cpu_features_cb(&mut self) -> u64 { 0 } + /// Asks frontend to log and/or display the state of performance counters. + /// Performance counters can always be poked into manually as well. fn perf_log_cb(&mut self) {} + /// Register a performance counter. + /// ident field must be set with a discrete value and other values in + /// retro_perf_counter must be 0. + /// Registering can be called multiple times. To avoid calling to + /// frontend redundantly, you can check registered field first. fn perf_register_cb(&mut self, counter: &mut PerfCounter) {} + /// Starts a registered counter. fn perf_start_cb(&mut self, counter: &mut PerfCounter) {} + /// Stops a registered counter. fn perf_stop_cb(&mut self, counter: &mut PerfCounter) {} fn set_sensor_state(&mut self, port: c_uint, action: SensorAction, rate: c_uint) -> bool { false } fn get_sensor_input(&mut self, port: c_uint, id: c_uint) -> f32 { 0.0 } @@ -399,7 +389,9 @@ impl StaticCallbacks { Some(true) } fn path_into_void(data: *mut c_void, source: impl AsRef) -> Option { - Self::string_into_void(data, source.as_ref().to_string_lossy()) + *unsafe { (data as *mut *const c_char).as_mut()? } = + CString::new(source.as_ref().as_os_str().as_bytes()).ok()?.into_raw(); + Some(true) } fn from_void(data: *mut c_void) -> Option<&'static mut T> { unsafe { (data as *mut T).as_mut() } @@ -440,7 +432,7 @@ impl StaticCallbacks { EnvCmd::SetRotation => handler.set_rotation(Self::enum_from_void(data)?), EnvCmd::GetOverscan => Self::clone_into_void(data, &handler.get_overscan()?)?, EnvCmd::GetCanDupe => Self::clone_into_void(data, &true)?, - EnvCmd::SetMessage => handler.set_message(Self::from_void::(data)?.clone()), + EnvCmd::SetMessage => handler.set_message(Self::from_void::(data)?), EnvCmd::Shutdown => handler.shutdown(), EnvCmd::SetPerformanceLevel => handler.set_performance_level(*Self::from_void(data)?), EnvCmd::GetSystemDirectory => { @@ -456,7 +448,7 @@ impl StaticCallbacks { descriptors.push(unsafe { input_desc.as_ref() }?.try_into().ok()?); input_desc = input_desc.wrapping_add(1); } - handler.set_input_descriptors(descriptors) + handler.set_input_descriptors(&descriptors) } EnvCmd::SetKeyboardCallback => { let kc: &mut KeyboardCallback = Self::from_void(data)?; @@ -508,7 +500,7 @@ impl StaticCallbacks { descriptors.push(unsafe { var.as_ref() }?.into()); var = var.wrapping_add(1); } - handler.set_variables(descriptors) + handler.set_variables(&descriptors) } EnvCmd::GetVariableUpdate => { Self::clone_into_void(data, &handler.get_variable_update()?)? @@ -585,7 +577,7 @@ impl StaticCallbacks { descriptors.push(unsafe { info.as_ref() }?.into()); info = info.wrapping_add(1); } - handler.set_subsystem_info(descriptors) + handler.set_subsystem_info(&descriptors) } EnvCmd::SetControllerInfo => { let info = unsafe { (data as *const ControllerInfo).as_ref() }?; @@ -596,7 +588,7 @@ impl StaticCallbacks { .map(TryInto::try_into) .filter_map(|x| x.ok()) .collect(); - handler.set_controller_info(controller_info) + handler.set_controller_info(&controller_info) } // TODO (experimental) EnvCmd::SetMemoryMaps => {}, EnvCmd::SetGeometry => {