Skip to main content

libretro_core/
sensors.rs

1//! Sensor and location service-interface wrappers.
2//!
3//! Sensor polling is a typed frontend service. Location lifecycle callbacks are
4//! event-shaped and should be registered through `CoreEventConfig`.
5
6use crate::{InputPort, raw};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9pub enum Sensor {
10    Accelerometer,
11    Gyroscope,
12    Illuminance,
13}
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16pub enum SensorAction {
17    Enable(Sensor),
18    Disable(Sensor),
19}
20
21impl SensorAction {
22    pub(crate) const fn as_raw(self) -> raw::retro_sensor_action {
23        match self {
24            Self::Enable(Sensor::Accelerometer) => raw::retro_sensor_action::AccelerometerEnable,
25            Self::Disable(Sensor::Accelerometer) => raw::retro_sensor_action::AccelerometerDisable,
26            Self::Enable(Sensor::Gyroscope) => raw::retro_sensor_action::GyroscopeEnable,
27            Self::Disable(Sensor::Gyroscope) => raw::retro_sensor_action::GyroscopeDisable,
28            Self::Enable(Sensor::Illuminance) => raw::retro_sensor_action::IlluminanceEnable,
29            Self::Disable(Sensor::Illuminance) => raw::retro_sensor_action::IlluminanceDisable,
30        }
31    }
32}
33
34#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
35pub struct SensorRateHz(u32);
36
37impl SensorRateHz {
38    pub const fn new(rate: u32) -> Self {
39        Self(rate)
40    }
41
42    pub const fn as_raw(self) -> u32 {
43        self.0
44    }
45}
46
47impl From<u32> for SensorRateHz {
48    fn from(rate: u32) -> Self {
49        Self::new(rate)
50    }
51}
52
53#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
54pub struct LocationIntervalMillis(u32);
55
56impl LocationIntervalMillis {
57    pub const fn new(milliseconds: u32) -> Self {
58        Self(milliseconds)
59    }
60
61    pub const fn as_millis(self) -> u32 {
62        self.0
63    }
64}
65
66impl From<u32> for LocationIntervalMillis {
67    fn from(milliseconds: u32) -> Self {
68        Self::new(milliseconds)
69    }
70}
71
72#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
73pub struct LocationIntervalMeters(u32);
74
75impl LocationIntervalMeters {
76    pub const fn new(meters: u32) -> Self {
77        Self(meters)
78    }
79
80    pub const fn as_meters(self) -> u32 {
81        self.0
82    }
83}
84
85impl From<u32> for LocationIntervalMeters {
86    fn from(meters: u32) -> Self {
87        Self::new(meters)
88    }
89}
90
91#[derive(Clone, Copy, Debug, Default, PartialEq)]
92pub struct LocationPosition {
93    pub latitude_degrees: f64,
94    pub longitude_degrees: f64,
95    pub horizontal_accuracy: f64,
96    pub vertical_accuracy: f64,
97}
98
99#[derive(Clone, Copy, Debug, Default)]
100pub struct LocationInterface {
101    raw: raw::retro_location_callback,
102}
103
104impl LocationInterface {
105    pub(crate) const fn from_raw(raw: raw::retro_location_callback) -> Self {
106        Self { raw }
107    }
108
109    pub const fn is_available(self) -> bool {
110        self.raw.start.is_some()
111            && self.raw.stop.is_some()
112            && self.raw.get_position.is_some()
113            && self.raw.set_interval.is_some()
114    }
115
116    pub fn start(self) -> bool {
117        self.raw.start.is_some_and(|start| unsafe { start() })
118    }
119
120    pub fn stop(self) -> bool {
121        let Some(stop) = self.raw.stop else {
122            return false;
123        };
124        unsafe { stop() };
125        true
126    }
127
128    pub fn set_interval(
129        self,
130        interval: impl Into<LocationIntervalMillis>,
131        distance: impl Into<LocationIntervalMeters>,
132    ) -> bool {
133        let Some(set_interval) = self.raw.set_interval else {
134            return false;
135        };
136        unsafe { set_interval(interval.into().as_millis(), distance.into().as_meters()) };
137        true
138    }
139
140    pub fn position(self) -> Option<LocationPosition> {
141        let get_position = self.raw.get_position?;
142        let mut latitude_degrees = 0.0;
143        let mut longitude_degrees = 0.0;
144        let mut horizontal_accuracy = 0.0;
145        let mut vertical_accuracy = 0.0;
146        if unsafe {
147            get_position(
148                &mut latitude_degrees,
149                &mut longitude_degrees,
150                &mut horizontal_accuracy,
151                &mut vertical_accuracy,
152            )
153        } {
154            Some(LocationPosition {
155                latitude_degrees,
156                longitude_degrees,
157                horizontal_accuracy,
158                vertical_accuracy,
159            })
160        } else {
161            None
162        }
163    }
164}
165
166#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
167pub enum SensorInput {
168    AccelerometerX,
169    AccelerometerY,
170    AccelerometerZ,
171    GyroscopeX,
172    GyroscopeY,
173    GyroscopeZ,
174    Illuminance,
175}
176
177impl SensorInput {
178    pub const fn as_raw(self) -> u32 {
179        match self {
180            Self::AccelerometerX => raw::RETRO_SENSOR_ACCELEROMETER_X,
181            Self::AccelerometerY => raw::RETRO_SENSOR_ACCELEROMETER_Y,
182            Self::AccelerometerZ => raw::RETRO_SENSOR_ACCELEROMETER_Z,
183            Self::GyroscopeX => raw::RETRO_SENSOR_GYROSCOPE_X,
184            Self::GyroscopeY => raw::RETRO_SENSOR_GYROSCOPE_Y,
185            Self::GyroscopeZ => raw::RETRO_SENSOR_GYROSCOPE_Z,
186            Self::Illuminance => raw::RETRO_SENSOR_ILLUMINANCE,
187        }
188    }
189}
190
191#[derive(Clone, Copy, Debug, Default)]
192pub struct SensorInterface {
193    raw: raw::retro_sensor_interface,
194}
195
196impl SensorInterface {
197    pub(crate) const fn from_raw(raw: raw::retro_sensor_interface) -> Self {
198        Self { raw }
199    }
200
201    pub const fn is_available(self) -> bool {
202        self.raw.set_sensor_state.is_some() && self.raw.get_sensor_input.is_some()
203    }
204
205    pub fn set_state(
206        self,
207        port: impl Into<InputPort>,
208        action: SensorAction,
209        rate: impl Into<SensorRateHz>,
210    ) -> bool {
211        let Some(set_sensor_state) = self.raw.set_sensor_state else {
212            return false;
213        };
214
215        unsafe { set_sensor_state(port.into().as_raw(), action.as_raw(), rate.into().as_raw()) }
216    }
217
218    pub fn enable(
219        self,
220        port: impl Into<InputPort>,
221        sensor: Sensor,
222        rate: impl Into<SensorRateHz>,
223    ) -> bool {
224        self.set_state(port, SensorAction::Enable(sensor), rate)
225    }
226
227    pub fn disable(self, port: impl Into<InputPort>, sensor: Sensor) -> bool {
228        self.set_state(port, SensorAction::Disable(sensor), SensorRateHz::new(0))
229    }
230
231    pub fn input(self, port: impl Into<InputPort>, input: SensorInput) -> Option<f32> {
232        self.raw.get_sensor_input.map(|get_sensor_input| unsafe {
233            get_sensor_input(port.into().as_raw(), input.as_raw())
234        })
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::{
241        LocationInterface, LocationIntervalMeters, LocationIntervalMillis, LocationPosition,
242        Sensor, SensorAction, SensorInput, SensorInterface, SensorRateHz,
243    };
244
245    #[test]
246    fn sensor_actions_encode_libretro_values() {
247        assert_eq!(
248            SensorAction::Enable(Sensor::Accelerometer).as_raw(),
249            crate::raw::retro_sensor_action::AccelerometerEnable
250        );
251        assert_eq!(
252            SensorAction::Disable(Sensor::Illuminance).as_raw(),
253            crate::raw::retro_sensor_action::IlluminanceDisable
254        );
255    }
256
257    #[test]
258    fn sensor_inputs_encode_libretro_ids() {
259        assert_eq!(
260            SensorInput::AccelerometerX.as_raw(),
261            crate::raw::RETRO_SENSOR_ACCELEROMETER_X
262        );
263        assert_eq!(
264            SensorInput::Illuminance.as_raw(),
265            crate::raw::RETRO_SENSOR_ILLUMINANCE
266        );
267    }
268
269    #[test]
270    fn empty_sensor_interface_reports_unavailable() {
271        let sensors = SensorInterface::default();
272
273        assert!(!sensors.is_available());
274        assert!(!sensors.enable(0, Sensor::Accelerometer, SensorRateHz::new(60)));
275        assert_eq!(sensors.input(0, SensorInput::AccelerometerX), None);
276    }
277
278    #[test]
279    fn empty_location_interface_reports_unavailable() {
280        let location = LocationInterface::default();
281
282        assert!(!location.is_available());
283        assert!(!location.start());
284        assert!(!location.stop());
285        assert!(!location.set_interval(LocationIntervalMillis::new(1000), 10));
286        assert_eq!(location.position(), None);
287    }
288
289    #[test]
290    fn location_interval_newtypes_preserve_units() {
291        assert_eq!(LocationIntervalMillis::new(500).as_millis(), 500);
292        assert_eq!(LocationIntervalMeters::new(20).as_meters(), 20);
293        assert_eq!(
294            LocationPosition {
295                latitude_degrees: 1.0,
296                longitude_degrees: 2.0,
297                horizontal_accuracy: 3.0,
298                vertical_accuracy: 4.0,
299            }
300            .horizontal_accuracy,
301            3.0
302        );
303    }
304}