Skip to content

Commit

Permalink
letsplay_retro_frontend: Major refactoring
Browse files Browse the repository at this point in the history
- Split all frontend implementation stuff into a new module
- Begin a heavy cleanup of the public API
- ...
- profit?
  • Loading branch information
modeco80 committed Jan 29, 2025
1 parent b0333f8 commit 9207d73
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 50 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
//! Callbacks for libretro
use crate::libretro_sys_new::*;
use crate::{frontend::*, libretro_log, util};
use crate::{libretro_core_variable, libretro_sys_new::*};

use std::ffi;

use tracing::{debug, error};

use letsplay_core::alloc::alloc_boxed_slice;

/// The currently running frontend.
///
/// # Safety
/// Libretro itself is not thread safe, so we do not try and pretend that we are.
/// Only one instance of Frontend can be active in an application.
pub(crate) static mut FRONTEND: *mut Frontend = std::ptr::null_mut();

/// This function is used with HW OpenGL cores to transfer the current FBO's ID.
unsafe extern "C" fn hw_gl_get_framebuffer() -> usize {
(*FRONTEND).gl_fbo_id as usize
Expand Down Expand Up @@ -201,7 +208,7 @@ pub(crate) unsafe extern "C" fn environment_callback(
let key = std::ffi::CStr::from_ptr(var.key).to_str().unwrap();
let value = std::ffi::CStr::from_ptr(var.value).to_str().unwrap();

match libretro_core_variable::CoreVariable::parse(value) {
match CoreVariable::parse(value) {
Ok(value) => {
(*FRONTEND).variables.insert(key.to_string(), value);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use super::CoreVariable;
use crate::input_devices::InputDevice;
use crate::libretro_callbacks;
use crate::libretro_core_variable::CoreVariable;
use crate::libretro_sys_new::*;
use crate::result::{Error, Result};
use ffi::CString;
use libloading::Library;
use libretro_sys::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::ffi;
Expand All @@ -14,42 +13,9 @@ use std::{fs, mem::MaybeUninit};

use tracing::{error, info};

/// The currently running frontend.
///
/// # Safety
/// Libretro itself is not thread safe, so we do not try and pretend that we are.
/// Only one instance of Frontend can be active in an application.
pub(crate) static mut FRONTEND: *mut Frontend = std::ptr::null_mut();

/// Initalization data for HW OpenGL cores.
pub struct HwGlInitData {
/// A pointer to a function to allow cores to request OpenGL extension functions.
pub get_proc_address: *mut ffi::c_void,
}

/// Interface for the frontend to call to user code.
pub trait FrontendInterface {
/// Called when video is updated.
fn video_update(&mut self, slice: &[u32], pitch: u32);

/// Called when video is updated and the core is using HW OpenGL rendering.
fn video_update_gl(&mut self);

/// Called when resize occurs.
fn video_resize(&mut self, width: u32, height: u32);
use super::FrontendInterface;

// TODO(lily): This should probably return the amount of consumed frames,
// as in some cases that *might* differ?
fn audio_sample(&mut self, slice: &[i16], size: usize);

/// Called to poll input
fn input_poll(&mut self);

/// Initalize hardware accelerated rendering using OpenGL.
/// If this returns [Option::None], then it is assumed that
/// OpenGL initalization has failed.
fn hw_gl_init(&mut self) -> Option<HwGlInitData>;
}
use super::callbacks::FRONTEND;

/// Per-core settings
#[derive(Serialize, Deserialize)]
Expand All @@ -58,6 +24,7 @@ struct CoreSettingsFile {
variables: HashMap<String, CoreVariable>,
}

/// A libretro frontend.
pub struct Frontend {
/// The current core's libretro functions.
pub(crate) core_api: Option<CoreAPI>,
Expand Down Expand Up @@ -105,8 +72,10 @@ pub struct Frontend {
}

impl Frontend {
/// Creates a new boxed frontend instance. Note that the returned [Box]
/// must be held until this frontend is no longer used.
/// Creates a new frontend instance.
///
/// # Notes
/// The returned [Box] *must* be held until this frontend is no longer used.
pub fn new(interface: *mut dyn FrontendInterface) -> Box<Self> {
let mut boxed = Box::new(Self {
core_api: None,
Expand Down Expand Up @@ -250,6 +219,7 @@ impl Frontend {
info!("Saved settings to {path}");
}

/// Loads a libretro core into the frontend.
pub fn load_core<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
if self.core_loaded() {
return Err(Error::CoreAlreadyLoaded);
Expand Down Expand Up @@ -321,29 +291,32 @@ impl Frontend {
// Set required libretro callbacks before calling libretro_init.
// Some cores expect some callbacks to be set before libretro_init is called,
// some cores don't. For maximum compatibility, pamper the cores which do.
(core_api_ref.retro_set_environment)(libretro_callbacks::environment_callback);
(core_api_ref.retro_set_environment)(super::callbacks::environment_callback);

// Initalize the libretro core. We do this first because
// there are a Few cores which initalize resources that later
// are poked by the later callback setting that could break if we don't.
(core_api_ref.retro_init)();

// Set more libretro callbacks now that we have initalized the core.
(core_api_ref.retro_set_video_refresh)(libretro_callbacks::video_refresh_callback);
(core_api_ref.retro_set_input_poll)(libretro_callbacks::input_poll_callback);
(core_api_ref.retro_set_input_state)(libretro_callbacks::input_state_callback);
(core_api_ref.retro_set_video_refresh)(super::callbacks::video_refresh_callback);
(core_api_ref.retro_set_input_poll)(super::callbacks::input_poll_callback);
(core_api_ref.retro_set_input_state)(super::callbacks::input_state_callback);
(core_api_ref.retro_set_audio_sample_batch)(
libretro_callbacks::audio_sample_batch_callback,
super::callbacks::audio_sample_batch_callback,
);
(core_api_ref.retro_set_audio_sample)(libretro_callbacks::audio_sample_callback);
(core_api_ref.retro_set_audio_sample)(super::callbacks::audio_sample_callback);

info!("Core {} loaded", path.as_ref().display());
}

Ok(())
}

/// Unloads a loaded core.
pub fn unload_core(&mut self) -> Result<()> {
// FIXME: Does this *really* need to be an error?
// I guess for sanity checking in debug builds we can keep it
if !self.core_loaded() {
return Err(Error::CoreNotLoaded);
}
Expand Down Expand Up @@ -381,6 +354,7 @@ impl Frontend {
Ok(())
}

/// Load game into the core.
pub fn load_game<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
if !self.core_loaded() {
return Err(Error::CoreNotLoaded);
Expand Down Expand Up @@ -429,6 +403,7 @@ impl Frontend {
}
}

/// Unload game from the core.
pub fn unload_game(&mut self) -> Result<()> {
if !self.core_loaded() {
return Err(Error::CoreNotLoaded);
Expand Down Expand Up @@ -499,6 +474,7 @@ impl Frontend {
self.gl_fbo_id = id;
}

/// Reset the emulated game.
pub fn reset(&mut self) {
let core_api = self.core_api.as_ref().unwrap();

Expand All @@ -507,6 +483,7 @@ impl Frontend {
}
}

/// Run a single frame of the emulated game.
pub fn run_frame(&mut self) {
let core_api = self.core_api.as_ref().unwrap();

Expand Down
31 changes: 31 additions & 0 deletions crates/letsplay_retro_frontend/src/frontend/interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use std::ffi;

/// Initalization data for HW OpenGL cores.
pub struct HwGlInitData {
/// A pointer to a function that can be used to request OpenGL extension functions.
/// Given to the core so they can do so.
pub get_proc_address: *mut ffi::c_void,
}

/// Interface for the frontend to call to user code.
pub trait FrontendInterface {
/// Called when video is updated.
fn video_update(&mut self, slice: &[u32], pitch: u32);

/// Called when video is updated and the core is using HW OpenGL rendering.
fn video_update_gl(&mut self);

/// Called when resize occurs.
fn video_resize(&mut self, width: u32, height: u32);

// TODO(lily): This should probably return the amount of consumed frames,
// as in some cases that *might* differ?
fn audio_sample(&mut self, slice: &[i16], size: usize);

/// Called to poll input.
fn input_poll(&mut self);

/// Initalize hardware accelerated rendering using OpenGL.
/// Return [Option::None] to indicate OpenGL initalization has failed.
fn hw_gl_init(&mut self) -> Option<HwGlInitData>;
}
10 changes: 10 additions & 0 deletions crates/letsplay_retro_frontend/src/frontend/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
mod interface;
pub use interface::*;

mod frontend_impl;
pub use frontend_impl::Frontend;

mod core_variable;
pub use core_variable::*;

mod callbacks;
5 changes: 3 additions & 2 deletions crates/letsplay_retro_frontend/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
//! A libretro frontend as a reusable library crate.
mod libretro_callbacks;
mod libretro_core_variable;
mod libretro_log;

pub mod libretro_sys_new;
Expand All @@ -11,3 +9,6 @@ pub mod util;

pub mod frontend;
pub mod result;

// re-export some of our useful interface
pub use frontend::{CoreVariable, Frontend, FrontendInterface};

0 comments on commit 9207d73

Please sign in to comment.