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. 53
      ctru-rs/src/console.rs
  4. 23
      ctru-rs/src/lib.rs
  5. 3
      ctru-rs/src/services/gfx.rs
  6. 161
      ctru-rs/src/services/hid.rs
  7. 3
      ctru-rs/src/services/ndsp/mod.rs
  8. 47
      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();
}
}

53
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.
@ -176,13 +173,13 @@ impl<'screen> Console<'screen> {
/// The first two arguments are the desired coordinates of the top-left corner /// 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. /// of the new window based on the row/column coordinates of a full-screen console.
/// The second pair is the new width and height. /// The second pair is the new width and height.
/// ///
/// # Panics /// # Panics
/// ///
/// This function will panic if the new window's position or size does not fit the screen. /// This function will panic if the new window's position or size does not fit the screen.
/// ///
/// # Example /// # Example
/// ///
/// ```no_run /// ```no_run
/// # use std::error::Error; /// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> { /// # fn main() -> Result<(), Box<dyn Error>> {
@ -194,7 +191,7 @@ impl<'screen> Console<'screen> {
/// # /// #
/// let mut top_console = Console::new(gfx.top_screen.borrow_mut()); /// let mut top_console = Console::new(gfx.top_screen.borrow_mut());
/// top_console.set_window(10, 10, 16, 6); /// top_console.set_window(10, 10, 16, 6);
/// ///
/// println!("I'm becoming claustrophobic in here!"); /// println!("I'm becoming claustrophobic in here!");
/// # /// #
/// # Ok(()) /// # Ok(())
@ -202,7 +199,7 @@ impl<'screen> Console<'screen> {
/// ``` /// ```
#[doc(alias = "consoleSetWindow")] #[doc(alias = "consoleSetWindow")]
pub fn set_window(&mut self, x: u8, y: u8, width: u8, height: u8) { 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(); let length_limit = self.max_width();
if x >= length_limit { if x >= length_limit {
@ -212,22 +209,30 @@ impl<'screen> Console<'screen> {
panic!("y coordinate of new console window out of bounds"); 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"); 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"); 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.
/// ///
/// This can be used to undo the changes made by [`set_window()`](Console::set_window()). /// This can be used to undo the changes made by [`set_window()`](Console::set_window()).
/// ///
/// # Example /// # Example
/// ///
/// ```no_run /// ```no_run
/// # use std::error::Error; /// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> { /// # fn main() -> Result<(), Box<dyn Error>> {
@ -239,11 +244,11 @@ impl<'screen> Console<'screen> {
/// # /// #
/// let mut top_console = Console::new(gfx.top_screen.borrow_mut()); /// let mut top_console = Console::new(gfx.top_screen.borrow_mut());
/// top_console.set_window(15, 15, 8, 10); /// top_console.set_window(15, 15, 8, 10);
/// ///
/// println!("It's really jammed in here!"); /// println!("It's really jammed in here!");
/// ///
/// top_console.reset_window(); /// top_console.reset_window();
/// ///
/// println!("Phew, finally a breath of fresh air."); /// println!("Phew, finally a breath of fresh air.");
/// # /// #
/// # Ok(()) /// # Ok(())
@ -256,9 +261,9 @@ impl<'screen> Console<'screen> {
} }
/// Returns this [`Console`]'s maximum character width depending on the screen used. /// Returns this [`Console`]'s maximum character width depending on the screen used.
/// ///
/// # Example /// # Example
/// ///
/// ```no_run /// ```no_run
/// # use std::error::Error; /// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> { /// # fn main() -> Result<(), Box<dyn Error>> {
@ -267,9 +272,9 @@ impl<'screen> Console<'screen> {
/// # use ctru::console::Console; /// # use ctru::console::Console;
/// # /// #
/// let gfx = Gfx::new()?; /// let gfx = Gfx::new()?;
/// ///
/// let top_console = Console::new(gfx.top_screen.borrow_mut()); /// let top_console = Console::new(gfx.top_screen.borrow_mut());
/// ///
/// // The maximum width for the top screen (without any alterations) is 50 characters. /// // The maximum width for the top screen (without any alterations) is 50 characters.
/// assert_eq!(top_console.max_width(), 50); /// assert_eq!(top_console.max_width(), 50);
/// # /// #

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);

161
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)
} }
}
impl Drop for Hid { /// Returns the current volume slider position (between 0 and 1).
#[doc(alias = "hidExit")] ///
fn drop(&mut self) { /// # Notes
unsafe { ctru_sys::hidExit() }; ///
/// 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);
}
(res.x, res.y, res.z)
} }
} }

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

47
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 { TryLockError::WouldBlock => return Err(Error::ServiceAlreadyActive),
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 (self.close)();
.counter
.lock()
.expect("Mutex Counter for ServiceReference is poisoned"); // todo: handle poisoning
*value -= 1;
if *value == 0 {
(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