Browse Source

Restructure ServiceReference and wip Hid

pull/137/head
Andrea Ciliberti 1 year ago
parent
commit
1a704b0006
  1. 9
      ctru-rs/examples/buttons.rs
  2. 46
      ctru-rs/examples/movement.rs
  3. 15
      ctru-rs/src/console.rs
  4. 23
      ctru-rs/src/lib.rs
  5. 3
      ctru-rs/src/services/gfx.rs
  6. 159
      ctru-rs/src/services/hid.rs
  7. 3
      ctru-rs/src/services/ndsp/mod.rs
  8. 43
      ctru-rs/src/services/reference.rs
  9. 3
      ctru-rs/src/services/romfs.rs
  10. 3
      ctru-rs/src/services/soc.rs

9
ctru-rs/examples/buttons.rs

@ -25,6 +25,13 @@ fn main() {
// Get information about which keys were held down on this frame. // Get information about which keys were held down on this frame.
let keys = hid.keys_held(); 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 // We only want to print when the keys we're holding now are different
// from what they were on the previous frame. // from what they were on the previous frame.
if keys != old_keys { if keys != old_keys {
@ -44,7 +51,7 @@ fn main() {
// and the `.intersects()` method checks for any of the provided keys. // and the `.intersects()` method checks for any of the provided keys.
// //
// You can also use the `.bits()` method to do direct comparisons on // You can also use the `.bits()` method to do direct comparisons on
// the underlying bits // the underlying bits.
if keys.contains(KeyPad::A) { if keys.contains(KeyPad::A) {
println!("You held A!"); println!("You held A!");

46
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();
}
}

15
ctru-rs/src/console.rs

@ -76,10 +76,7 @@ impl<'screen> Console<'screen> {
unsafe { consoleInit(screen.as_raw(), context.as_mut()) }; unsafe { consoleInit(screen.as_raw(), context.as_mut()) };
Console { Console { context, screen }
context,
screen,
}
} }
/// Returns `true` if a valid [`Console`] to print on is currently selected. /// Returns `true` if a valid [`Console`] to print on is currently selected.
@ -219,7 +216,15 @@ impl<'screen> Console<'screen> {
panic!("height of new console window out of bounds"); 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. /// Reset the window's size to default parameters.

23
ctru-rs/src/lib.rs

@ -87,15 +87,24 @@ fn panic_hook_setup() {
if main_thread == std::thread::current().id() && console::Console::exists() { if main_thread == std::thread::current().id() && console::Console::exists() {
println!("\nPress SELECT to exit the software"); println!("\nPress SELECT to exit the software");
match Hid::new() { // Due to how the Hid service operates, we can't safely use 2 handles to it at the same time.
Ok(mut hid) => loop { // Furthermore, the panic hook runs before the panic cleanup is done, which means that any other handles
hid.scan_input(); // to the service will still be alive during this process.
let keys = hid.keys_down(); // Regardless, we can "unsafely" spin up a new instance, since the module won't be used any further from the main process,
if keys.contains(KeyPad::SELECT) { // 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; break;
} }
}, }
Err(e) => println!("Error while intializing Hid controller during panic: {e}"),
ctru_sys::hidExit();
} }
} }
}); });

3
ctru-rs/src/services/gfx.rs

@ -244,7 +244,7 @@ pub struct Gfx {
_service_handler: ServiceReference, _service_handler: ServiceReference,
} }
static GFX_ACTIVE: Mutex<usize> = Mutex::new(0); static GFX_ACTIVE: Mutex<()> = Mutex::new(());
impl Gfx { impl Gfx {
/// Initialize a new default service handle. /// Initialize a new default service handle.
@ -311,7 +311,6 @@ impl Gfx {
) -> Result<Self> { ) -> Result<Self> {
let handler = ServiceReference::new( let handler = ServiceReference::new(
&GFX_ACTIVE, &GFX_ACTIVE,
false,
|| unsafe { || unsafe {
ctru_sys::gfxInit(top_fb_fmt.into(), bottom_fb_fmt.into(), use_vram_buffers); ctru_sys::gfxInit(top_fb_fmt.into(), bottom_fb_fmt.into(), use_vram_buffers);

159
ctru-rs/src/services/hid.rs

@ -1,15 +1,21 @@
//! Human Interface Device service. //! 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), //! 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. //! and [circle pad information](Hid::circlepad_position). It also provides information from the [3D slider](Hid::slider_3d()), the [volume slider](Hid::slider_volume()),
// TODO: Implement volume slider, accelerometer and gyroscope + any other missing functionality. //! the [accelerometer](Hid::accellerometer_vector()), and the [gyroscope](Hid::gyroscope_rate()).
#![doc(alias = "input")] #![doc(alias = "input")]
#![doc(alias = "controller")] #![doc(alias = "controller")]
#![doc(alias = "gamepad")] #![doc(alias = "gamepad")]
use std::sync::Mutex;
use crate::error::ResultCode; use crate::error::ResultCode;
use crate::services::ServiceReference;
use bitflags::bitflags; use bitflags::bitflags;
static HID_ACTIVE: Mutex<()> = Mutex::new(());
bitflags! { bitflags! {
/// A set of flags corresponding to the button and directional pad inputs present on the 3DS. /// 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)] #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
@ -75,7 +81,11 @@ bitflags! {
} }
/// Handle to the HID service. /// Handle to the HID service.
pub struct Hid(()); pub struct Hid {
active_accellerometer: bool,
active_gyroscope: bool,
_service_handler: ServiceReference,
}
impl Hid { impl Hid {
/// Initialize a new service handle. /// Initialize a new service handle.
@ -100,10 +110,26 @@ impl Hid {
/// ``` /// ```
#[doc(alias = "hidInit")] #[doc(alias = "hidInit")]
pub fn new() -> crate::Result<Hid> { pub fn new() -> crate::Result<Hid> {
unsafe { let handler = ServiceReference::new(
ResultCode(ctru_sys::hidInit())?; &HID_ACTIVE,
Ok(Hid(())) || {
} 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. /// Scan the HID service for all user input occurring on the current frame.
@ -282,11 +308,122 @@ impl Hid {
(res.dx, res.dy) (res.dx, res.dy)
} }
/// 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<dyn Error>> {
/// #
/// 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<dyn Error>> {
/// #
/// 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);
} }
impl Drop for Hid { (res.x, res.y, res.z)
#[doc(alias = "hidExit")]
fn drop(&mut self) {
unsafe { ctru_sys::hidExit() };
} }
} }

3
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 _rf: RefMut<'ndsp, ()>, // we don't need to hold any data
} }
static NDSP_ACTIVE: Mutex<usize> = Mutex::new(0); static NDSP_ACTIVE: Mutex<()> = Mutex::new(());
/// Handle to the DSP service. /// Handle to the DSP service.
/// ///
@ -133,7 +133,6 @@ impl Ndsp {
pub fn new() -> crate::Result<Self> { pub fn new() -> crate::Result<Self> {
let _service_handler = ServiceReference::new( let _service_handler = ServiceReference::new(
&NDSP_ACTIVE, &NDSP_ACTIVE,
false,
|| { || {
ResultCode(unsafe { ctru_sys::ndspInit() })?; ResultCode(unsafe { ctru_sys::ndspInit() })?;

43
ctru-rs/src/services/reference.rs

@ -1,35 +1,37 @@
use crate::Error; use crate::Error;
use std::sync::Mutex; use std::sync::{Mutex, MutexGuard, TryLockError};
pub(crate) struct ServiceReference { pub(crate) struct ServiceReference {
counter: &'static Mutex<usize>, _guard: MutexGuard<'static, ()>,
close: Box<dyn Fn() + Send + Sync>, close: Box<dyn Fn() + Send + Sync>,
} }
impl ServiceReference { impl ServiceReference {
pub fn new<S, E>( pub fn new<S, E>(counter: &'static Mutex<()>, start: S, close: E) -> crate::Result<Self>
counter: &'static Mutex<usize>,
allow_multiple: bool,
start: S,
close: E,
) -> crate::Result<Self>
where where
S: FnOnce() -> crate::Result<()>, S: FnOnce() -> crate::Result<()>,
E: Fn() + Send + Sync + 'static, E: Fn() + Send + Sync + 'static,
{ {
let mut value = counter let _guard = match counter.try_lock() {
.lock() Ok(lock) => lock,
.expect("Mutex Counter for ServiceReference is poisoned"); // todo: handle poisoning 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 { guard.into_inner()
start()?;
} else if !allow_multiple {
return Err(Error::ServiceAlreadyActive);
} }
TryLockError::WouldBlock => return Err(Error::ServiceAlreadyActive),
},
};
*value += 1; start()?;
Ok(Self { Ok(Self {
counter, _guard,
close: Box::new(close), close: Box::new(close),
}) })
} }
@ -37,13 +39,6 @@ impl ServiceReference {
impl Drop for ServiceReference { impl Drop for ServiceReference {
fn drop(&mut self) { 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)();
} }
} }
}

3
ctru-rs/src/services/romfs.rs

@ -38,7 +38,7 @@ pub struct RomFS {
_service_handler: ServiceReference, _service_handler: ServiceReference,
} }
static ROMFS_ACTIVE: Mutex<usize> = Mutex::new(0); static ROMFS_ACTIVE: Mutex<()> = Mutex::new(());
impl RomFS { impl RomFS {
/// Mount the bundled RomFS archive as a virtual drive. /// Mount the bundled RomFS archive as a virtual drive.
@ -63,7 +63,6 @@ impl RomFS {
pub fn new() -> crate::Result<Self> { pub fn new() -> crate::Result<Self> {
let _service_handler = ServiceReference::new( let _service_handler = ServiceReference::new(
&ROMFS_ACTIVE, &ROMFS_ACTIVE,
true,
|| { || {
let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap(); let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap();
ResultCode(unsafe { ctru_sys::romfsMountSelf(mount_name.as_ptr()) })?; ResultCode(unsafe { ctru_sys::romfsMountSelf(mount_name.as_ptr()) })?;

3
ctru-rs/src/services/soc.rs

@ -19,7 +19,7 @@ pub struct Soc {
sock_3dslink: libc::c_int, sock_3dslink: libc::c_int,
} }
static SOC_ACTIVE: Mutex<usize> = Mutex::new(0); static SOC_ACTIVE: Mutex<()> = Mutex::new(());
impl Soc { impl Soc {
/// Initialize a new service handle using a socket buffer size of `0x100000` bytes. /// 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<Self> { pub fn init_with_buffer_size(num_bytes: usize) -> crate::Result<Self> {
let _service_handler = ServiceReference::new( let _service_handler = ServiceReference::new(
&SOC_ACTIVE, &SOC_ACTIVE,
false,
|| { || {
let soc_mem = unsafe { memalign(0x1000, num_bytes) } as *mut u32; let soc_mem = unsafe { memalign(0x1000, num_bytes) } as *mut u32;
ResultCode(unsafe { ctru_sys::socInit(soc_mem, num_bytes as u32) })?; ResultCode(unsafe { ctru_sys::socInit(soc_mem, num_bytes as u32) })?;

Loading…
Cancel
Save