From d71774b6bf2f5333b2c040fb0ee936f98484e0ae Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Mon, 18 Sep 2023 20:37:29 +0200 Subject: [PATCH] Less panics, more errors --- ctru-rs/src/console.rs | 74 +++++++++++++++++++++++++++---- ctru-rs/src/lib.rs | 4 +- ctru-rs/src/services/hid.rs | 32 ++++++++++--- ctru-rs/src/services/ndsp/mod.rs | 58 +++++++++++------------- ctru-rs/src/services/ndsp/wave.rs | 12 ++--- ctru-rs/src/services/romfs.rs | 14 +++--- ctru-sys/build.rs | 2 +- 7 files changed, 131 insertions(+), 65 deletions(-) diff --git a/ctru-rs/src/console.rs b/ctru-rs/src/console.rs index d37ea06..ba85222 100644 --- a/ctru-rs/src/console.rs +++ b/ctru-rs/src/console.rs @@ -14,6 +14,31 @@ use crate::services::gfx::Screen; static mut EMPTY_CONSOLE: PrintConsole = unsafe { const_zero::const_zero!(PrintConsole) }; +/// Error enum for generic errors within [`Console`]. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Error { + /// The coordinate specified on the given axis exceeds the limits imposed by the [`Console`] window. + CoordinateOutOfBounds(Axis), + /// The size specified for the given dimension exceeds the limits imposed by the [`Console`] window. + DimensionOutOfBounds(Dimension), +} + +/// 2D coordinate axes. +#[allow(missing_docs)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Axis { + X, + Y, +} + +/// 2D dimensions. +#[allow(missing_docs)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Dimension { + Width, + Height, +} + /// Virtual text console. /// /// [`Console`] lets the application redirect `stdout` and `stderr` to a simple text displayer on the 3DS screen. @@ -174,10 +199,6 @@ impl<'screen> Console<'screen> { /// 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 @@ -198,22 +219,22 @@ impl<'screen> Console<'screen> { /// # } /// ``` #[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) -> Result<(), Error> { let height_limit = 30; let length_limit = self.max_width(); if x >= length_limit { - panic!("x coordinate of new console window out of bounds"); + return Err(Error::CoordinateOutOfBounds(Axis::X)); } if y >= height_limit { - panic!("y coordinate of new console window out of bounds"); + return Err(Error::CoordinateOutOfBounds(Axis::Y)); } if (x + width) > length_limit { - panic!("width of new console window out of bounds"); + return Err(Error::DimensionOutOfBounds(Dimension::Width)); } if (y + height) > height_limit { - panic!("height of new console window out of bounds"); + return Err(Error::DimensionOutOfBounds(Dimension::Height)); } unsafe { @@ -225,6 +246,8 @@ impl<'screen> Console<'screen> { height.into(), ) }; + + Ok(()) } /// Reset the window's size to default parameters. @@ -321,3 +344,36 @@ impl Drop for Console<'_> { } } } + +impl std::fmt::Display for Axis { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::X => write!(f, "x"), + Self::Y => write!(f, "y"), + } + } +} + +impl std::fmt::Display for Dimension { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Width => write!(f, "width"), + Self::Height => write!(f, "height"), + } + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::CoordinateOutOfBounds(a) => { + write!(f, "coordinate specified for the {a} axis is out of bounds") + } + Self::DimensionOutOfBounds(d) => { + write!(f, "size specified for the {d} is out of bounds") + } + } + } +} + +impl std::error::Error for Error {} diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index 1ba18a6..329d063 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -73,7 +73,7 @@ pub fn use_panic_handler() { /// When `test` is enabled, this function will be ignored. #[cfg(not(test))] fn panic_hook_setup() { - use crate::services::hid::{Hid, KeyPad}; + use crate::services::hid::KeyPad; use std::panic::PanicInfo; let main_thread = std::thread::current().id(); @@ -103,7 +103,7 @@ fn panic_hook_setup() { break; } } - + ctru_sys::hidExit(); } } diff --git a/ctru-rs/src/services/hid.rs b/ctru-rs/src/services/hid.rs index 16d10c5..1811c28 100644 --- a/ctru-rs/src/services/hid.rs +++ b/ctru-rs/src/services/hid.rs @@ -80,6 +80,15 @@ bitflags! { } } +/// Error enum for generic errors within the [`Hid`] service. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Error { + /// An attempt was made to access the accelerometer while disabled. + UnavailableAccelerometer, + /// An attempt was made to access the gyroscope while disabled. + UnavailableGyroscope, +} + /// Handle to the HID service. pub struct Hid { active_accellerometer: bool, @@ -398,9 +407,9 @@ impl Hid { } #[doc(alias = "hidAccelRead")] - pub fn accellerometer_vector(&self) -> (i16, i16, i16) { + pub fn accellerometer_vector(&self) -> Result<(i16, i16, i16), Error> { if !self.active_accellerometer { - panic!("tried to read accellerometer while disabled") + return Err(Error::UnavailableAccelerometer); } let mut res = ctru_sys::accelVector { x: 0, y: 0, z: 0 }; @@ -409,13 +418,13 @@ impl Hid { ctru_sys::hidAccelRead(&mut res); } - (res.x, res.y, res.z) + Ok((res.x, res.y, res.z)) } #[doc(alias = "hidGyroRead")] - pub fn gyroscope_rate(&self) -> (i16, i16, i16) { + pub fn gyroscope_rate(&self) -> Result<(i16, i16, i16), Error> { if !self.active_gyroscope { - panic!("tried to read accellerometer while disabled") + return Err(Error::UnavailableGyroscope); } let mut res = ctru_sys::angularRate { x: 0, y: 0, z: 0 }; @@ -424,6 +433,17 @@ impl Hid { ctru_sys::hidGyroRead(&mut res); } - (res.x, res.y, res.z) + Ok((res.x, res.y, res.z)) } } + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::UnavailableAccelerometer => write!(f, "tried using accelerometer while disabled"), + Self::UnavailableGyroscope => write!(f, "tried using gyroscope while disabled"), + } + } +} + +impl std::error::Error for Error {} diff --git a/ctru-rs/src/services/ndsp/mod.rs b/ctru-rs/src/services/ndsp/mod.rs index 1da683c..78c744b 100644 --- a/ctru-rs/src/services/ndsp/mod.rs +++ b/ctru-rs/src/services/ndsp/mod.rs @@ -52,6 +52,16 @@ pub struct AudioMix { raw: [f32; 12], } +/// Auxiliary Device index. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(usize)] +pub enum AuxDevice { + /// Aux device with index 0. + Zero = 0, + /// Aux device with index 1. + One = 1, +} + /// Interpolation used between audio frames. #[doc(alias = "ndspInterpType")] #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -67,7 +77,7 @@ pub enum InterpolationType { /// Errors returned by [`ndsp`](self) functions. #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum NdspError { +pub enum Error { /// Channel with the specified ID does not exist. InvalidChannel(u8), /// Channel with the specified ID is already being used. @@ -169,7 +179,7 @@ impl Ndsp { /// # Ok(()) /// # } /// ``` - pub fn channel(&self, id: u8) -> std::result::Result { + pub fn channel(&self, id: u8) -> std::result::Result { let in_bounds = self.channel_flags.get(id as usize); match in_bounds { @@ -177,10 +187,10 @@ impl Ndsp { let flag = ref_cell.try_borrow_mut(); match flag { Ok(_rf) => Ok(Channel { id, _rf }), - Err(_) => Err(NdspError::ChannelAlreadyInUse(id)), + Err(_) => Err(Error::ChannelAlreadyInUse(id)), } } - None => Err(NdspError::InvalidChannel(id)), + None => Err(Error::InvalidChannel(id)), } } @@ -516,9 +526,9 @@ impl Channel<'_> { // TODO: Find a better way to handle the wave lifetime problem. // These "alive wave" shenanigans are the most substantial reason why I'd like to fully re-write this service in Rust. #[doc(alias = "ndspChnWaveBufAdd")] - pub fn queue_wave(&mut self, wave: &mut Wave) -> std::result::Result<(), NdspError> { + pub fn queue_wave(&mut self, wave: &mut Wave) -> std::result::Result<(), Error> { match wave.status() { - Status::Playing | Status::Queued => return Err(NdspError::WaveBusy(self.id)), + Status::Playing | Status::Queued => return Err(Error::WaveBusy(self.id)), _ => (), } @@ -660,23 +670,15 @@ impl AudioMix { } /// Returns the values set for the "front" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1). - pub fn aux_front(&self, id: usize) -> (f32, f32) { - if id > 1 { - panic!("invalid auxiliary output device index") - } - - let index = 4 + id * 4; + pub fn aux_front(&self, id: AuxDevice) -> (f32, f32) { + let index = 4 + (id as usize * 4); (self.raw[index], self.raw[index + 1]) } /// Returns the values set for the "back" volume mix (left and right channel) for the specified auxiliary output device (either 0 or 1). - pub fn aux_back(&self, id: usize) -> (f32, f32) { - if id > 1 { - panic!("invalid auxiliary output device index") - } - - let index = 6 + id * 4; + pub fn aux_back(&self, id: AuxDevice) -> (f32, f32) { + let index = 6 + (id as usize * 4); (self.raw[index], self.raw[index + 1]) } @@ -709,12 +711,8 @@ impl AudioMix { /// /// [`Channel`] will normalize the mix values to be within 0 and 1. /// However, an [`AudioMix`] instance with larger/smaller values is valid. - pub fn set_aux_front(&mut self, left: f32, right: f32, id: usize) { - if id > 1 { - panic!("invalid auxiliary output device index") - } - - let index = 4 + id * 4; + pub fn set_aux_front(&mut self, left: f32, right: f32, id: AuxDevice) { + let index = 4 + (id as usize * 4); self.raw[index] = left; self.raw[index + 1] = right; @@ -726,12 +724,8 @@ impl AudioMix { /// /// [`Channel`] will normalize the mix values to be within 0 and 1. /// However, an [`AudioMix`] instance with larger/smaller values is valid. - pub fn set_aux_back(&mut self, left: f32, right: f32, id: usize) { - if id > 1 { - panic!("invalid auxiliary output device index") - } - - let index = 6 + id * 4; + pub fn set_aux_back(&mut self, left: f32, right: f32, id: AuxDevice) { + let index = 6 + (id as usize * 4); self.raw[index] = left; self.raw[index + 1] = right; @@ -754,7 +748,7 @@ impl From<[f32; 12]> for AudioMix { } } -impl fmt::Display for NdspError { +impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::InvalidChannel(id) => write!(f, "audio Channel with ID {id} doesn't exist. Valid channels have an ID between 0 and 23"), @@ -765,7 +759,7 @@ impl fmt::Display for NdspError { } } -impl error::Error for NdspError {} +impl error::Error for Error {} impl Drop for Ndsp { #[doc(alias = "ndspExit")] diff --git a/ctru-rs/src/services/ndsp/wave.rs b/ctru-rs/src/services/ndsp/wave.rs index 1a383ca..7f8c9f9 100644 --- a/ctru-rs/src/services/ndsp/wave.rs +++ b/ctru-rs/src/services/ndsp/wave.rs @@ -2,7 +2,7 @@ //! //! This modules has all methods and structs required to work with audio waves meant to be played via the [`ndsp`](crate::services::ndsp) service. -use super::{AudioFormat, NdspError}; +use super::{AudioFormat, Error}; use crate::linear::LinearAllocator; /// Informational struct holding the raw audio data and playback info. @@ -97,10 +97,10 @@ impl Wave { /// /// This function will return an error if the [`Wave`] is currently busy, /// with the id to the channel in which it's queued. - pub fn get_buffer_mut(&mut self) -> Result<&mut [u8], NdspError> { + pub fn get_buffer_mut(&mut self) -> Result<&mut [u8], Error> { match self.status() { Status::Playing | Status::Queued => { - Err(NdspError::WaveBusy(self.played_on_channel.unwrap())) + Err(Error::WaveBusy(self.played_on_channel.unwrap())) } _ => Ok(&mut self.buffer), } @@ -163,10 +163,10 @@ impl Wave { /// /// This function will return an error if the sample size exceeds the buffer's capacity /// or if the [`Wave`] is currently queued. - pub fn set_sample_count(&mut self, sample_count: usize) -> Result<(), NdspError> { + pub fn set_sample_count(&mut self, sample_count: usize) -> Result<(), Error> { match self.status() { Status::Playing | Status::Queued => { - return Err(NdspError::WaveBusy(self.played_on_channel.unwrap())); + return Err(Error::WaveBusy(self.played_on_channel.unwrap())); } _ => (), } @@ -174,7 +174,7 @@ impl Wave { let max_count = self.buffer.len() / self.audio_format.size(); if sample_count > max_count { - return Err(NdspError::SampleCountOutOfBounds(sample_count, max_count)); + return Err(Error::SampleCountOutOfBounds(sample_count, max_count)); } self.raw_data.nsamples = sample_count as u32; diff --git a/ctru-rs/src/services/romfs.rs b/ctru-rs/src/services/romfs.rs index aaf863f..fd6064b 100644 --- a/ctru-rs/src/services/romfs.rs +++ b/ctru-rs/src/services/romfs.rs @@ -83,16 +83,12 @@ mod tests { use super::*; #[test] - fn romfs_counter() { - let _romfs = RomFS::new().unwrap(); - let value = *ROMFS_ACTIVE.lock().unwrap(); + #[should_panic] + fn romfs_lock() { + let romfs = RomFS::new().unwrap(); - assert_eq!(value, 1); + let _value = *ROMFS_ACTIVE.lock().unwrap(); - drop(_romfs); - - let value = *ROMFS_ACTIVE.lock().unwrap(); - - assert_eq!(value, 0); + drop(romfs); } } diff --git a/ctru-sys/build.rs b/ctru-sys/build.rs index 1643b8b..fa34560 100644 --- a/ctru-sys/build.rs +++ b/ctru-sys/build.rs @@ -169,7 +169,7 @@ fn check_libctru_version() -> Result<(String, String, String), Box> { .expect("crate version should have '+' delimeter"); if lib_version != crate_built_version { - return Err(format!( + Err(format!( "libctru version is {lib_version} but this crate was built for {crate_built_version}" ))?; }