From 1a704b0006dd5133e5d9782a0d0d100aca59ed8c Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Mon, 18 Sep 2023 19:42:54 +0200 Subject: [PATCH] Restructure ServiceReference and wip Hid --- ctru-rs/examples/buttons.rs | 9 +- ctru-rs/examples/movement.rs | 46 +++++++++ ctru-rs/src/console.rs | 53 +++++----- ctru-rs/src/lib.rs | 23 +++-- ctru-rs/src/services/gfx.rs | 3 +- ctru-rs/src/services/hid.rs | 161 +++++++++++++++++++++++++++--- ctru-rs/src/services/ndsp/mod.rs | 3 +- ctru-rs/src/services/reference.rs | 47 ++++----- ctru-rs/src/services/romfs.rs | 3 +- ctru-rs/src/services/soc.rs | 3 +- 10 files changed, 273 insertions(+), 78 deletions(-) create mode 100644 ctru-rs/examples/movement.rs diff --git a/ctru-rs/examples/buttons.rs b/ctru-rs/examples/buttons.rs index 0de9e7c..e92c349 100644 --- a/ctru-rs/examples/buttons.rs +++ b/ctru-rs/examples/buttons.rs @@ -25,6 +25,13 @@ fn main() { // Get information about which keys were held down on this frame. let keys = hid.keys_held(); + // Print the status of the 2 sliders. + println!( + "\x1b[20;0HVolume slider: {} ", + hid.slider_volume() + ); + println!("\x1b[21;0H3D slider: {} ", hid.slider_3d()); + // We only want to print when the keys we're holding now are different // from what they were on the previous frame. if keys != old_keys { @@ -44,7 +51,7 @@ fn main() { // and the `.intersects()` method checks for any of the provided keys. // // You can also use the `.bits()` method to do direct comparisons on - // the underlying bits + // the underlying bits. if keys.contains(KeyPad::A) { println!("You held A!"); diff --git a/ctru-rs/examples/movement.rs b/ctru-rs/examples/movement.rs new file mode 100644 index 0000000..984b779 --- /dev/null +++ b/ctru-rs/examples/movement.rs @@ -0,0 +1,46 @@ +//! Movement example. +//! +//! Simple application to showcase the use of the accellerometer and gyroscope. + +use ctru::prelude::*; + +fn main() { + ctru::use_panic_handler(); + + let gfx = Gfx::new().expect("Couldn't obtain GFX controller"); + let mut hid = Hid::new().expect("Couldn't obtain HID controller"); + let apt = Apt::new().expect("Couldn't obtain APT controller"); + + let _console = Console::new(gfx.top_screen.borrow_mut()); + + println!("Move the console around!"); + println!("\x1b[29;16HPress Start to exit"); + + // Activate the accelerometer and the gyroscope. + // Because of the complex nature of the movement sensors, they aren't activated by default with the `Hid` service. + // However, they can simply be turned on and off whenever necessary. + hid.enable_accellerometer(); + hid.enable_gyroscope(); + + while apt.main_loop() { + // Scan all the controller inputs. + // Accellerometer and gyroscope require this step to update the readings. + hid.scan_input(); + + if hid.keys_down().contains(KeyPad::START) { + break; + } + + // Be careful: reading without activating the sensors (as done before this loop) will result in a panic. + println!( + "\x1b[3;0HAccelleration: {:?} ", + hid.accellerometer_vector() + ); + println!( + "\x1b[4;0HGyroscope angular rate: {:?} ", + hid.gyroscope_rate() + ); + + gfx.wait_for_vblank(); + } +} diff --git a/ctru-rs/src/console.rs b/ctru-rs/src/console.rs index e39f8d0..d37ea06 100644 --- a/ctru-rs/src/console.rs +++ b/ctru-rs/src/console.rs @@ -76,10 +76,7 @@ impl<'screen> Console<'screen> { unsafe { consoleInit(screen.as_raw(), context.as_mut()) }; - Console { - context, - screen, - } + Console { context, screen } } /// Returns `true` if a valid [`Console`] to print on is currently selected. @@ -176,13 +173,13 @@ impl<'screen> Console<'screen> { /// The first two arguments are the desired coordinates of the top-left corner /// of the new window based on the row/column coordinates of a full-screen console. /// The second pair is the new width and height. - /// + /// /// # Panics - /// + /// /// This function will panic if the new window's position or size does not fit the screen. - /// + /// /// # Example - /// + /// /// ```no_run /// # use std::error::Error; /// # fn main() -> Result<(), Box> { @@ -194,7 +191,7 @@ impl<'screen> Console<'screen> { /// # /// let mut top_console = Console::new(gfx.top_screen.borrow_mut()); /// top_console.set_window(10, 10, 16, 6); - /// + /// /// println!("I'm becoming claustrophobic in here!"); /// # /// # Ok(()) @@ -202,7 +199,7 @@ impl<'screen> Console<'screen> { /// ``` #[doc(alias = "consoleSetWindow")] pub fn set_window(&mut self, x: u8, y: u8, width: u8, height: u8) { - let height_limit = 30; + let height_limit = 30; let length_limit = self.max_width(); if x >= length_limit { @@ -212,22 +209,30 @@ impl<'screen> Console<'screen> { panic!("y coordinate of new console window out of bounds"); } - if (x+width) > length_limit { + if (x + width) > length_limit { panic!("width of new console window out of bounds"); } - if (y+height) > height_limit { + if (y + height) > height_limit { panic!("height of new console window out of bounds"); } - - unsafe { consoleSetWindow(self.context.as_mut(), x.into(), y.into(), width.into(), height.into()) }; + + unsafe { + consoleSetWindow( + self.context.as_mut(), + x.into(), + y.into(), + width.into(), + height.into(), + ) + }; } /// Reset the window's size to default parameters. - /// + /// /// This can be used to undo the changes made by [`set_window()`](Console::set_window()). - /// + /// /// # Example - /// + /// /// ```no_run /// # use std::error::Error; /// # fn main() -> Result<(), Box> { @@ -239,11 +244,11 @@ impl<'screen> Console<'screen> { /// # /// let mut top_console = Console::new(gfx.top_screen.borrow_mut()); /// top_console.set_window(15, 15, 8, 10); - /// + /// /// println!("It's really jammed in here!"); - /// + /// /// top_console.reset_window(); - /// + /// /// println!("Phew, finally a breath of fresh air."); /// # /// # Ok(()) @@ -256,9 +261,9 @@ impl<'screen> Console<'screen> { } /// Returns this [`Console`]'s maximum character width depending on the screen used. - /// + /// /// # Example - /// + /// /// ```no_run /// # use std::error::Error; /// # fn main() -> Result<(), Box> { @@ -267,9 +272,9 @@ impl<'screen> Console<'screen> { /// # use ctru::console::Console; /// # /// let gfx = Gfx::new()?; - /// + /// /// let top_console = Console::new(gfx.top_screen.borrow_mut()); - /// + /// /// // The maximum width for the top screen (without any alterations) is 50 characters. /// assert_eq!(top_console.max_width(), 50); /// # diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index ecc7dd9..1ba18a6 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -87,15 +87,24 @@ fn panic_hook_setup() { if main_thread == std::thread::current().id() && console::Console::exists() { println!("\nPress SELECT to exit the software"); - match Hid::new() { - Ok(mut hid) => loop { - hid.scan_input(); - let keys = hid.keys_down(); - if keys.contains(KeyPad::SELECT) { + // Due to how the Hid service operates, we can't safely use 2 handles to it at the same time. + // Furthermore, the panic hook runs before the panic cleanup is done, which means that any other handles + // to the service will still be alive during this process. + // Regardless, we can "unsafely" spin up a new instance, since the module won't be used any further from the main process, + // which is going to get cleaned up right after this loop. + unsafe { + let _ = ctru_sys::hidInit(); + + loop { + ctru_sys::hidScanInput(); + let keys = ctru_sys::hidKeysDown(); + + if KeyPad::from_bits_truncate(keys).contains(KeyPad::SELECT) { break; } - }, - Err(e) => println!("Error while intializing Hid controller during panic: {e}"), + } + + ctru_sys::hidExit(); } } }); diff --git a/ctru-rs/src/services/gfx.rs b/ctru-rs/src/services/gfx.rs index 68ac957..c32a603 100644 --- a/ctru-rs/src/services/gfx.rs +++ b/ctru-rs/src/services/gfx.rs @@ -244,7 +244,7 @@ pub struct Gfx { _service_handler: ServiceReference, } -static GFX_ACTIVE: Mutex = Mutex::new(0); +static GFX_ACTIVE: Mutex<()> = Mutex::new(()); impl Gfx { /// Initialize a new default service handle. @@ -311,7 +311,6 @@ impl Gfx { ) -> Result { let handler = ServiceReference::new( &GFX_ACTIVE, - false, || unsafe { ctru_sys::gfxInit(top_fb_fmt.into(), bottom_fb_fmt.into(), use_vram_buffers); diff --git a/ctru-rs/src/services/hid.rs b/ctru-rs/src/services/hid.rs index 56a6035..16d10c5 100644 --- a/ctru-rs/src/services/hid.rs +++ b/ctru-rs/src/services/hid.rs @@ -1,15 +1,21 @@ //! Human Interface Device service. //! //! The HID service provides read access to user input such as [button presses](Hid::keys_down), [touch screen presses](Hid::touch_position), -//! and [circle pad information](Hid::circlepad_position). It also provides information from the sound volume slider, the accelerometer, and the gyroscope. -// TODO: Implement volume slider, accelerometer and gyroscope + any other missing functionality. +//! and [circle pad information](Hid::circlepad_position). It also provides information from the [3D slider](Hid::slider_3d()), the [volume slider](Hid::slider_volume()), +//! the [accelerometer](Hid::accellerometer_vector()), and the [gyroscope](Hid::gyroscope_rate()). #![doc(alias = "input")] #![doc(alias = "controller")] #![doc(alias = "gamepad")] +use std::sync::Mutex; + use crate::error::ResultCode; +use crate::services::ServiceReference; + use bitflags::bitflags; +static HID_ACTIVE: Mutex<()> = Mutex::new(()); + bitflags! { /// A set of flags corresponding to the button and directional pad inputs present on the 3DS. #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)] @@ -75,7 +81,11 @@ bitflags! { } /// Handle to the HID service. -pub struct Hid(()); +pub struct Hid { + active_accellerometer: bool, + active_gyroscope: bool, + _service_handler: ServiceReference, +} impl Hid { /// Initialize a new service handle. @@ -100,10 +110,26 @@ impl Hid { /// ``` #[doc(alias = "hidInit")] pub fn new() -> crate::Result { - unsafe { - ResultCode(ctru_sys::hidInit())?; - Ok(Hid(())) - } + let handler = ServiceReference::new( + &HID_ACTIVE, + || { + ResultCode(unsafe { ctru_sys::hidInit() })?; + + Ok(()) + }, + || unsafe { + let _ = ctru_sys::HIDUSER_DisableGyroscope(); + let _ = ctru_sys::HIDUSER_DisableAccelerometer(); + + ctru_sys::hidExit(); + }, + )?; + + Ok(Self { + active_accellerometer: false, + active_gyroscope: false, + _service_handler: handler, + }) } /// Scan the HID service for all user input occurring on the current frame. @@ -282,11 +308,122 @@ impl Hid { (res.dx, res.dy) } -} -impl Drop for Hid { - #[doc(alias = "hidExit")] - fn drop(&mut self) { - unsafe { ctru_sys::hidExit() }; + /// Returns the current volume slider position (between 0 and 1). + /// + /// # Notes + /// + /// The [`ndsp`](crate::services::ndsp) service automatically uses the volume slider's position to handle audio mixing. + /// As such this method should not be used to programmatically change the volume. + /// + /// Its purpose is only to inform the program of the volume slider's position (e.g. checking if the user has muted the audio). + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::hid::Hid; + /// let mut hid = Hid::new()?; + /// + /// hid.scan_input(); + /// + /// let volume = hid.slider_volume(); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "HIDUSER_GetSoundVolume")] + pub fn slider_volume(&self) -> f32 { + let mut slider = 0; + + unsafe { + let _ = ctru_sys::HIDUSER_GetSoundVolume(&mut slider); + } + + (slider as f32) / 63. + } + + /// Returns the current 3D slider position (between 0 and 1). + /// + /// # Example + /// + /// ```no_run + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::hid::Hid; + /// let mut hid = Hid::new()?; + /// + /// hid.scan_input(); + /// + /// let volume = hid.volume_slider(); + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "osGet3DSliderState")] + pub fn slider_3d(&self) -> f32 { + // TODO: Replace with the static inline function `osGet3DSliderState`, which works the exact same way. + unsafe { (*(ctru_sys::OS_SHAREDCFG_VADDR as *mut ctru_sys::osSharedConfig_s)).slider_3d } + } + + #[doc(alias = "HIDUSER_EnableAccelerometer")] + pub fn enable_accellerometer(&mut self) { + let _ = unsafe { ctru_sys::HIDUSER_EnableAccelerometer() }; + + self.active_accellerometer = true; + } + + #[doc(alias = "HIDUSER_EnableGyroscope")] + pub fn enable_gyroscope(&mut self) { + let _ = unsafe { ctru_sys::HIDUSER_EnableGyroscope() }; + + self.active_gyroscope = true; + } + + #[doc(alias = "HIDUSER_DisableAccelerometer")] + pub fn disable_accellerometer(&mut self) { + let _ = unsafe { ctru_sys::HIDUSER_DisableAccelerometer() }; + + self.active_accellerometer = false; + } + + #[doc(alias = "HIDUSER_DisableGyroscope")] + pub fn disable_gyroscope(&mut self) { + let _ = unsafe { ctru_sys::HIDUSER_DisableGyroscope() }; + + self.active_gyroscope = false; + } + + #[doc(alias = "hidAccelRead")] + pub fn accellerometer_vector(&self) -> (i16, i16, i16) { + if !self.active_accellerometer { + panic!("tried to read accellerometer while disabled") + } + + let mut res = ctru_sys::accelVector { x: 0, y: 0, z: 0 }; + + unsafe { + ctru_sys::hidAccelRead(&mut res); + } + + (res.x, res.y, res.z) + } + + #[doc(alias = "hidGyroRead")] + pub fn gyroscope_rate(&self) -> (i16, i16, i16) { + if !self.active_gyroscope { + panic!("tried to read accellerometer while disabled") + } + + let mut res = ctru_sys::angularRate { x: 0, y: 0, z: 0 }; + + unsafe { + ctru_sys::hidGyroRead(&mut res); + } + + (res.x, res.y, res.z) } } diff --git a/ctru-rs/src/services/ndsp/mod.rs b/ctru-rs/src/services/ndsp/mod.rs index 8cda802..1da683c 100644 --- a/ctru-rs/src/services/ndsp/mod.rs +++ b/ctru-rs/src/services/ndsp/mod.rs @@ -98,7 +98,7 @@ pub struct Channel<'ndsp> { _rf: RefMut<'ndsp, ()>, // we don't need to hold any data } -static NDSP_ACTIVE: Mutex = Mutex::new(0); +static NDSP_ACTIVE: Mutex<()> = Mutex::new(()); /// Handle to the DSP service. /// @@ -133,7 +133,6 @@ impl Ndsp { pub fn new() -> crate::Result { let _service_handler = ServiceReference::new( &NDSP_ACTIVE, - false, || { ResultCode(unsafe { ctru_sys::ndspInit() })?; diff --git a/ctru-rs/src/services/reference.rs b/ctru-rs/src/services/reference.rs index 41319a7..0fad37d 100644 --- a/ctru-rs/src/services/reference.rs +++ b/ctru-rs/src/services/reference.rs @@ -1,35 +1,37 @@ use crate::Error; -use std::sync::Mutex; +use std::sync::{Mutex, MutexGuard, TryLockError}; + pub(crate) struct ServiceReference { - counter: &'static Mutex, + _guard: MutexGuard<'static, ()>, close: Box, } impl ServiceReference { - pub fn new( - counter: &'static Mutex, - allow_multiple: bool, - start: S, - close: E, - ) -> crate::Result + pub fn new(counter: &'static Mutex<()>, start: S, close: E) -> crate::Result where S: FnOnce() -> crate::Result<()>, E: Fn() + Send + Sync + 'static, { - let mut value = counter - .lock() - .expect("Mutex Counter for ServiceReference is poisoned"); // todo: handle poisoning + let _guard = match counter.try_lock() { + Ok(lock) => lock, + Err(e) => match e { + TryLockError::Poisoned(guard) => { + // If the MutexGuard is poisoned that means that the "other" service instance (of which the thread panicked) + // was NOT properly closed. To avoid any weird behaviour, we try closing the service now, to then re-open a fresh instance. + // + // It's up to our `close()` implementations to avoid panicking/doing weird stuff again. + close(); - if *value == 0 { - start()?; - } else if !allow_multiple { - return Err(Error::ServiceAlreadyActive); - } + guard.into_inner() + } + TryLockError::WouldBlock => return Err(Error::ServiceAlreadyActive), + }, + }; - *value += 1; + start()?; Ok(Self { - counter, + _guard, close: Box::new(close), }) } @@ -37,13 +39,6 @@ impl ServiceReference { impl Drop for ServiceReference { fn drop(&mut self) { - let mut value = self - .counter - .lock() - .expect("Mutex Counter for ServiceReference is poisoned"); // todo: handle poisoning - *value -= 1; - if *value == 0 { - (self.close)(); - } + (self.close)(); } } diff --git a/ctru-rs/src/services/romfs.rs b/ctru-rs/src/services/romfs.rs index e6f1c3b..aaf863f 100644 --- a/ctru-rs/src/services/romfs.rs +++ b/ctru-rs/src/services/romfs.rs @@ -38,7 +38,7 @@ pub struct RomFS { _service_handler: ServiceReference, } -static ROMFS_ACTIVE: Mutex = Mutex::new(0); +static ROMFS_ACTIVE: Mutex<()> = Mutex::new(()); impl RomFS { /// Mount the bundled RomFS archive as a virtual drive. @@ -63,7 +63,6 @@ impl RomFS { pub fn new() -> crate::Result { let _service_handler = ServiceReference::new( &ROMFS_ACTIVE, - true, || { let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap(); ResultCode(unsafe { ctru_sys::romfsMountSelf(mount_name.as_ptr()) })?; diff --git a/ctru-rs/src/services/soc.rs b/ctru-rs/src/services/soc.rs index d418919..5e61484 100644 --- a/ctru-rs/src/services/soc.rs +++ b/ctru-rs/src/services/soc.rs @@ -19,7 +19,7 @@ pub struct Soc { sock_3dslink: libc::c_int, } -static SOC_ACTIVE: Mutex = Mutex::new(0); +static SOC_ACTIVE: Mutex<()> = Mutex::new(()); impl Soc { /// Initialize a new service handle using a socket buffer size of `0x100000` bytes. @@ -71,7 +71,6 @@ impl Soc { pub fn init_with_buffer_size(num_bytes: usize) -> crate::Result { let _service_handler = ServiceReference::new( &SOC_ACTIVE, - false, || { let soc_mem = unsafe { memalign(0x1000, num_bytes) } as *mut u32; ResultCode(unsafe { ctru_sys::socInit(soc_mem, num_bytes as u32) })?;