Skip to main content

libretro_core/
callbacks.rs

1//! Small typed values used by frontend-to-core callbacks.
2//!
3//! The public event registration API lives on `CoreEventConfig`; this module
4//! owns the callback payload types that preserve libretro units without exposing
5//! raw callback table fields to normal core code.
6
7use crate::raw;
8
9#[derive(Clone, Copy, Debug)]
10pub struct CoreProcAddress {
11    raw: raw::retro_proc_address_t,
12}
13
14impl CoreProcAddress {
15    pub const fn from_fn(function: unsafe extern "C" fn()) -> Self {
16        Self {
17            raw: Some(function),
18        }
19    }
20
21    pub(crate) const fn as_raw(self) -> raw::retro_proc_address_t {
22        self.raw
23    }
24}
25
26#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
27pub enum AudioCallbackState {
28    #[default]
29    Inactive,
30    Active,
31}
32
33impl AudioCallbackState {
34    pub const fn from_active(active: bool) -> Self {
35        if active { Self::Active } else { Self::Inactive }
36    }
37
38    pub const fn is_active(self) -> bool {
39        matches!(self, Self::Active)
40    }
41}
42
43#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
44pub struct AudioBufferOccupancy(u32);
45
46impl AudioBufferOccupancy {
47    pub const fn from_percent(percent: u8) -> Option<Self> {
48        if percent <= 100 {
49            Some(Self(percent as u32))
50        } else {
51            None
52        }
53    }
54
55    pub const fn from_raw_percent(percent: u32) -> Self {
56        Self(percent)
57    }
58
59    pub const fn raw_percent(self) -> u32 {
60        self.0
61    }
62
63    pub const fn percent(self) -> Option<u8> {
64        if self.0 <= 100 {
65            Some(self.0 as u8)
66        } else {
67            None
68        }
69    }
70}
71
72#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
73pub struct AudioBufferStatus {
74    pub active: bool,
75    pub occupancy: AudioBufferOccupancy,
76    pub underrun_likely: bool,
77}
78
79impl AudioBufferStatus {
80    pub const fn new(active: bool, occupancy: AudioBufferOccupancy, underrun_likely: bool) -> Self {
81        Self {
82            active,
83            occupancy,
84            underrun_likely,
85        }
86    }
87
88    pub const fn from_raw(active: bool, occupancy: u32, underrun_likely: bool) -> Self {
89        Self::new(
90            active,
91            AudioBufferOccupancy::from_raw_percent(occupancy),
92            underrun_likely,
93        )
94    }
95}
96
97#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
98pub struct FrameTime {
99    micros: i64,
100}
101
102impl FrameTime {
103    pub const fn from_micros(micros: i64) -> Self {
104        Self { micros }
105    }
106
107    pub const fn as_micros(self) -> i64 {
108        self.micros
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::{
115        AudioBufferOccupancy, AudioBufferStatus, AudioCallbackState, CoreProcAddress, FrameTime,
116    };
117
118    unsafe extern "C" fn test_proc_address() {}
119
120    #[test]
121    fn core_proc_address_wraps_type_erased_function_pointer() {
122        let address = CoreProcAddress::from_fn(test_proc_address)
123            .as_raw()
124            .expect("function pointer should be present");
125        let expected: unsafe extern "C" fn() = test_proc_address;
126
127        assert!(std::ptr::fn_addr_eq(address, expected));
128    }
129
130    #[test]
131    fn audio_callback_state_encodes_frontend_active_flag() {
132        assert_eq!(
133            AudioCallbackState::from_active(false),
134            AudioCallbackState::Inactive
135        );
136        assert_eq!(
137            AudioCallbackState::from_active(true),
138            AudioCallbackState::Active
139        );
140        assert!(!AudioCallbackState::Inactive.is_active());
141        assert!(AudioCallbackState::Active.is_active());
142    }
143
144    #[test]
145    fn audio_buffer_occupancy_accepts_libretro_percent_range() {
146        assert_eq!(
147            AudioBufferOccupancy::from_percent(100)
148                .expect("100 percent is valid")
149                .percent(),
150            Some(100)
151        );
152        assert_eq!(AudioBufferOccupancy::from_percent(101), None);
153    }
154
155    #[test]
156    fn audio_buffer_status_preserves_out_of_range_frontend_values() {
157        let status = AudioBufferStatus::from_raw(true, 150, true);
158
159        assert_eq!(status.occupancy.raw_percent(), 150);
160        assert_eq!(status.occupancy.percent(), None);
161    }
162
163    #[test]
164    fn frame_time_preserves_signed_libretro_values() {
165        assert_eq!(FrameTime::from_micros(16_667).as_micros(), 16_667);
166        assert_eq!(FrameTime::from_micros(-1).as_micros(), -1);
167    }
168}