diff --git a/ctru-rs/src/error.rs b/ctru-rs/src/error.rs index 2cfe55a..96ade26 100644 --- a/ctru-rs/src/error.rs +++ b/ctru-rs/src/error.rs @@ -9,7 +9,7 @@ pub type Result = ::std::result::Result; #[non_exhaustive] pub enum Error { Os(ctru_sys::Result), - ServiceAlreadyActive(&'static str), + ServiceAlreadyActive, } impl From for Error { @@ -29,10 +29,7 @@ impl fmt::Debug for Error { .field("summary", &R_SUMMARY(err)) .field("level", &R_LEVEL(err)) .finish(), - Error::ServiceAlreadyActive(service) => f - .debug_tuple("ServiceAlreadyActive") - .field(&String::from(service)) - .finish(), + Error::ServiceAlreadyActive => f.debug_tuple("ServiceAlreadyActive").finish(), } } } @@ -44,7 +41,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Error::Os(err) => write!(f, "libctru result code: 0x{:08X}", err), - Error::ServiceAlreadyActive(service) => write!(f, "Service {service} already active"), + Error::ServiceAlreadyActive => write!(f, "Service already active"), } } } diff --git a/ctru-rs/src/gfx.rs b/ctru-rs/src/gfx.rs index 0d244c1..4016a91 100644 --- a/ctru-rs/src/gfx.rs +++ b/ctru-rs/src/gfx.rs @@ -1,12 +1,13 @@ //! LCD screens manipulation helper use std::cell::RefCell; +use std::lazy::SyncLazy; use std::marker::PhantomData; -use std::ops::Drop; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Mutex; use crate::error::{Error, Result}; use crate::services::gspgpu::{self, FramebufferFormat}; +use crate::services::ServiceHandler; /// Trait implemented by TopScreen and BottomScreen for common methods pub trait Screen { @@ -72,9 +73,10 @@ pub enum Side { pub struct Gfx { pub top_screen: RefCell, pub bottom_screen: RefCell, + _service_handler: ServiceHandler, } -static GFX_ACTIVE: AtomicBool = AtomicBool::new(false); +static GFX_ACTIVE: SyncLazy> = SyncLazy::new(|| Mutex::new(0)); impl Gfx { /// Initialize the Gfx module with the chosen framebuffer formats for the top and bottom @@ -86,19 +88,22 @@ impl Gfx { bottom_fb_fmt: FramebufferFormat, use_vram_buffers: bool, ) -> Result { - match GFX_ACTIVE.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) { - Ok(_) => { - unsafe { - ctru_sys::gfxInit(top_fb_fmt.into(), bottom_fb_fmt.into(), use_vram_buffers); - } - - Ok(Gfx { - top_screen: RefCell::new(TopScreen), - bottom_screen: RefCell::new(BottomScreen), - }) - } - Err(_) => Err(Error::ServiceAlreadyActive("Gfx")), - } + let _service_handler = ServiceHandler::new( + &GFX_ACTIVE, + false, + || unsafe { + ctru_sys::gfxInit(top_fb_fmt.into(), bottom_fb_fmt.into(), use_vram_buffers); + + Ok(()) + }, + || unsafe { ctru_sys::gfxExit() }, + )?; + + Ok(Self { + top_screen: RefCell::new(TopScreen), + bottom_screen: RefCell::new(BottomScreen), + _service_handler, + }) } /// Creates a new Gfx instance with default init values @@ -212,14 +217,6 @@ impl From for ctru_sys::gfx3dSide_t { } } -impl Drop for Gfx { - fn drop(&mut self) { - unsafe { ctru_sys::gfxExit() }; - - GFX_ACTIVE.store(false, Ordering::SeqCst); - } -} - #[cfg(test)] mod tests { use super::*; @@ -227,9 +224,6 @@ mod tests { #[test] fn gfx_duplicate() { // We don't need to build a `Gfx` because the test runner has one already - match Gfx::init() { - Err(Error::ServiceAlreadyActive("Gfx")) => return, - _ => panic!(), - } + assert!(matches!(Gfx::init(), Err(Error::ServiceAlreadyActive))) } } diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index 8cad7a9..559f2db 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -3,6 +3,7 @@ #![feature(test)] #![feature(custom_test_frameworks)] #![test_runner(test_runner::run)] +#![feature(once_cell)] extern "C" fn services_deinit() { unsafe { @@ -28,6 +29,7 @@ pub fn init() { use std::panic::PanicInfo; + #[cfg(not(test))] let main_thread = thread::current().id(); // Panic Hook setup @@ -36,6 +38,7 @@ pub fn init() { default_hook(info); // Only for panics in the main thread + #[cfg(not(test))] if main_thread == thread::current().id() && console::Console::exists() { println!("\nPress SELECT to exit the software"); diff --git a/ctru-rs/src/romfs.rs b/ctru-rs/src/romfs.rs index 78005fc..0d366c8 100644 --- a/ctru-rs/src/romfs.rs +++ b/ctru-rs/src/romfs.rs @@ -11,39 +11,39 @@ //! ``` use std::ffi::CStr; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::lazy::SyncLazy; +use std::sync::Mutex; -use crate::error::Error; +use crate::services::ServiceHandler; #[non_exhaustive] -pub struct RomFS; +pub struct RomFS { + _service_handler: ServiceHandler, +} -static ROMFS_ACTIVE: AtomicBool = AtomicBool::new(false); +static ROMFS_ACTIVE: SyncLazy> = SyncLazy::new(|| Mutex::new(0)); impl RomFS { pub fn init() -> crate::Result { - match ROMFS_ACTIVE.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) { - Ok(_) => { + let _service_handler = ServiceHandler::new( + &ROMFS_ACTIVE, + true, + || { let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap(); - let result = unsafe { ctru_sys::romfsMountSelf(mount_name.as_ptr()) }; - - if result < 0 { - Err(result.into()) - } else { - Ok(Self) + let r = unsafe { ctru_sys::romfsMountSelf(mount_name.as_ptr()) }; + if r < 0 { + return Err(r.into()); } - } - Err(_) => Err(Error::ServiceAlreadyActive("RomFS")), - } - } -} -impl Drop for RomFS { - fn drop(&mut self) { - let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap(); - unsafe { ctru_sys::romfsUnmount(mount_name.as_ptr()) }; + Ok(()) + }, + || { + let mount_name = CStr::from_bytes_with_nul(b"romfs\0").unwrap(); + unsafe { ctru_sys::romfsUnmount(mount_name.as_ptr()) }; + }, + )?; - ROMFS_ACTIVE.store(false, Ordering::SeqCst); + Ok(Self { _service_handler }) } } @@ -54,10 +54,14 @@ mod tests { #[test] fn romfs_duplicate() { let _romfs = RomFS::init().unwrap(); + let lock = *ROMFS_ACTIVE.lock().unwrap(); + + assert_eq!(lock, 1); + + drop(_romfs); + + let lock = *ROMFS_ACTIVE.lock().unwrap(); - match RomFS::init() { - Err(Error::ServiceAlreadyActive("RomFS")) => return, - _ => panic!(), - } + assert_eq!(lock, 0); } } diff --git a/ctru-rs/src/services/apt.rs b/ctru-rs/src/services/apt.rs index 29ab58c..089b896 100644 --- a/ctru-rs/src/services/apt.rs +++ b/ctru-rs/src/services/apt.rs @@ -1,25 +1,38 @@ -use std::sync::atomic::{AtomicBool, Ordering}; +use std::lazy::SyncLazy; +use std::sync::Mutex; -use crate::error::Error; +use crate::services::ServiceHandler; #[non_exhaustive] -pub struct Apt(()); +pub struct Apt { + _service_handler: ServiceHandler, +} -static APT_ACTIVE: AtomicBool = AtomicBool::new(false); +static APT_ACTIVE: SyncLazy> = SyncLazy::new(|| Mutex::new(0)); impl Apt { pub fn init() -> crate::Result { - match APT_ACTIVE.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) { - Ok(_) => { + let _service_handler = ServiceHandler::new( + &APT_ACTIVE, + true, + || { let r = unsafe { ctru_sys::aptInit() }; if r < 0 { - Err(r.into()) - } else { - Ok(Self(())) + return Err(r.into()); } - } - Err(_) => Err(Error::ServiceAlreadyActive("Apt")), - } + + Ok(()) + }, + // `socExit` returns an error code. There is no documentantion of when errors could happen, + // but we wouldn't be able to handle them in the `Drop` implementation anyways. + // Surely nothing bad will happens :D + || unsafe { + // The socket buffer is freed automatically by `socExit` + ctru_sys::aptExit(); + }, + )?; + + Ok(Self { _service_handler }) } pub fn main_loop(&self) -> bool { @@ -36,14 +49,6 @@ impl Apt { } } -impl Drop for Apt { - fn drop(&mut self) { - unsafe { ctru_sys::aptExit() }; - - APT_ACTIVE.store(false, Ordering::SeqCst); - } -} - #[cfg(test)] mod tests { use super::*; @@ -51,9 +56,8 @@ mod tests { #[test] fn apt_duplicate() { // We don't need to build a `Apt` because the test runner has one already - match Apt::init() { - Err(Error::ServiceAlreadyActive("Apt")) => return, - _ => panic!(), - } + let lock = *APT_ACTIVE.lock().unwrap(); + + assert_eq!(lock, 1); } } diff --git a/ctru-rs/src/services/fs.rs b/ctru-rs/src/services/fs.rs index cd8fc20..731e1e0 100644 --- a/ctru-rs/src/services/fs.rs +++ b/ctru-rs/src/services/fs.rs @@ -9,15 +9,16 @@ use std::io::Error as IoError; use std::io::ErrorKind as IoErrorKind; use std::io::Result as IoResult; use std::io::{Read, Seek, SeekFrom, Write}; +use std::lazy::SyncLazy; use std::mem; use std::path::{Path, PathBuf}; use std::ptr; use std::slice; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; + use widestring::{WideCStr, WideCString}; -use crate::error::Error; +use crate::services::ServiceHandler; bitflags! { #[derive(Default)] @@ -86,9 +87,11 @@ pub enum ArchiveID { /// /// The service exits when all instances of this struct go out of scope. #[non_exhaustive] -pub struct Fs(()); +pub struct Fs { + _service_handler: ServiceHandler, +} -static FS_ACTIVE: AtomicBool = AtomicBool::new(false); +static FS_ACTIVE: SyncLazy> = SyncLazy::new(|| Mutex::new(0)); /// Handle to an open filesystem archive. /// @@ -308,17 +311,27 @@ impl Fs { /// as many times as desired and the service will not exit until all /// instances of Fs drop out of scope. pub fn init() -> crate::Result { - match FS_ACTIVE.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) { - Ok(_) => { + let _service_handler = ServiceHandler::new( + &FS_ACTIVE, + true, + || { let r = unsafe { ctru_sys::fsInit() }; if r < 0 { - Err(r.into()) - } else { - Ok(Self(())) + return Err(r.into()); } - } - Err(_) => Err(Error::ServiceAlreadyActive("Fs")), - } + + Ok(()) + }, + // `socExit` returns an error code. There is no documentantion of when errors could happen, + // but we wouldn't be able to handle them in the `Drop` implementation anyways. + // Surely nothing bad will happens :D + || unsafe { + // The socket buffer is freed automatically by `socExit` + ctru_sys::fsExit(); + }, + )?; + + Ok(Self { _service_handler }) } /// Returns a handle to the SDMC (memory card) Archive. @@ -998,16 +1011,6 @@ impl Seek for File { } } -impl Drop for Fs { - fn drop(&mut self) { - unsafe { - ctru_sys::fsExit(); - } - - FS_ACTIVE.store(false, Ordering::SeqCst); - } -} - impl Drop for Archive { fn drop(&mut self) { unsafe { @@ -1083,9 +1086,14 @@ mod tests { fn fs_duplicate() { let _fs = Fs::init().unwrap(); - match Fs::init() { - Err(Error::ServiceAlreadyActive("Fs")) => return, - _ => panic!(), - } + let lock = *FS_ACTIVE.lock().unwrap(); + + assert_eq!(lock, 1); + + drop(_fs); + + let lock = *FS_ACTIVE.lock().unwrap(); + + assert_eq!(lock, 0); } } diff --git a/ctru-rs/src/services/hid.rs b/ctru-rs/src/services/hid.rs index 567849f..c26e96a 100644 --- a/ctru-rs/src/services/hid.rs +++ b/ctru-rs/src/services/hid.rs @@ -4,9 +4,10 @@ //! and circle pad information. It also provides information from the sound volume slider, //! the accelerometer, and the gyroscope. -use std::sync::atomic::{AtomicBool, Ordering}; +use std::lazy::SyncLazy; +use std::sync::Mutex; -use crate::error::Error; +use crate::services::ServiceHandler; bitflags::bitflags! { /// A set of flags corresponding to the button and directional pad @@ -49,9 +50,11 @@ bitflags::bitflags! { /// /// This service requires no special permissions to use. #[non_exhaustive] -pub struct Hid(()); +pub struct Hid { + _service_handler: ServiceHandler, +} -static HID_ACTIVE: AtomicBool = AtomicBool::new(false); +static HID_ACTIVE: SyncLazy> = SyncLazy::new(|| Mutex::new(0)); /// Represents user input to the touchscreen. #[non_exhaustive] @@ -70,17 +73,23 @@ pub struct CirclePosition(ctru_sys::circlePosition); /// rare in practice. impl Hid { pub fn init() -> crate::Result { - match HID_ACTIVE.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) { - Ok(_) => { + let _service_handler = ServiceHandler::new( + &HID_ACTIVE, + true, + || { let r = unsafe { ctru_sys::hidInit() }; if r < 0 { - Err(r.into()) - } else { - Ok(Self(())) + return Err(r.into()); } - } - Err(_) => Err(Error::ServiceAlreadyActive("Hid")), - } + + Ok(()) + }, + || unsafe { + ctru_sys::hidExit(); + }, + )?; + + Ok(Self { _service_handler }) } /// Scans the HID service for all user input occurring on the current @@ -160,14 +169,6 @@ impl CirclePosition { } } -impl Drop for Hid { - fn drop(&mut self) { - unsafe { ctru_sys::hidExit() }; - - HID_ACTIVE.store(false, Ordering::SeqCst); - } -} - #[cfg(test)] mod tests { use super::*; @@ -175,9 +176,8 @@ mod tests { #[test] fn hid_duplicate() { // We don't need to build a `Hid` because the test runner has one already - match Hid::init() { - Err(Error::ServiceAlreadyActive("Hid")) => return, - _ => panic!(), - } + let lock = *HID_ACTIVE.lock().unwrap(); + + assert_eq!(lock, 1); } } diff --git a/ctru-rs/src/services/mod.rs b/ctru-rs/src/services/mod.rs index 2159e9e..67418de 100644 --- a/ctru-rs/src/services/mod.rs +++ b/ctru-rs/src/services/mod.rs @@ -9,3 +9,48 @@ pub mod sslc; pub use self::apt::Apt; pub use self::hid::Hid; pub use self::sslc::SslC; + +use crate::Error; +use std::sync::Mutex; +pub(crate) struct ServiceHandler { + counter: &'static Mutex, + close: Box, +} + +impl ServiceHandler { + pub fn new( + counter: &'static Mutex, + allow_multiple: bool, + start: S, + close: E, + ) -> crate::Result + where + S: FnOnce() -> crate::Result<()>, + E: Fn() + 'static, + { + let mut value = counter.lock().unwrap(); // todo: handle poisoning + + if *value == 0 { + start()?; + } else if !allow_multiple { + return Err(Error::ServiceAlreadyActive); + } + + *value += 1; + + Ok(Self { + counter, + close: Box::new(close), + }) + } +} + +impl Drop for ServiceHandler { + fn drop(&mut self) { + let mut value = self.counter.lock().unwrap(); // should probably handle poisoning - could just map_err to ignore it. + *value -= 1; + if *value == 0 { + (self.close)(); + } + } +} diff --git a/ctru-rs/src/services/soc.rs b/ctru-rs/src/services/soc.rs index e04e040..44041f4 100644 --- a/ctru-rs/src/services/soc.rs +++ b/ctru-rs/src/services/soc.rs @@ -1,18 +1,18 @@ -use libc::{free, memalign}; +use libc::memalign; +use std::lazy::SyncLazy; use std::net::Ipv4Addr; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Mutex; -use crate::error::Error; -use ctru_sys::{socExit, socInit}; +use crate::services::ServiceHandler; /// Soc service. Initializing this service will enable the use of network sockets and utilities /// such as those found in `std::net`. The service will be closed when this struct is is dropped. #[non_exhaustive] pub struct Soc { - soc_mem: *mut u32, + _service_handler: ServiceHandler, } -static SOC_ACTIVE: AtomicBool = AtomicBool::new(false); +static SOC_ACTIVE: SyncLazy> = SyncLazy::new(|| Mutex::new(0)); impl Soc { /// Initialize the Soc service with a default buffer size of 0x100000 bytes @@ -31,20 +31,28 @@ impl Soc { /// /// This function will return an error if the `Soc` service is already initialized pub fn init_with_buffer_size(num_bytes: usize) -> crate::Result { - match SOC_ACTIVE.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) { - Ok(_) => unsafe { - let soc_mem = memalign(0x1000, num_bytes) as *mut u32; - - let r = socInit(soc_mem, num_bytes as u32); + let _service_handler = ServiceHandler::new( + &SOC_ACTIVE, + true, + || { + let soc_mem = unsafe { memalign(0x1000, num_bytes) } as *mut u32; + let r = unsafe { ctru_sys::socInit(soc_mem, num_bytes as u32) }; if r < 0 { - free(soc_mem as *mut _); - Err(r.into()) - } else { - Ok(Self { soc_mem }) + return Err(r.into()); } + + Ok(()) + }, + // `socExit` returns an error code. There is no documentantion of when errors could happen, + // but we wouldn't be able to handle them in the `Drop` implementation anyways. + // Surely nothing bad will happens :D + || unsafe { + // The socket buffer is freed automatically by `socExit` + ctru_sys::socExit(); }, - Err(_) => Err(Error::ServiceAlreadyActive("Soc")), - } + )?; + + Ok(Self { _service_handler }) } /// IP Address of the Nintendo 3DS system. @@ -54,17 +62,6 @@ impl Soc { } } -impl Drop for Soc { - fn drop(&mut self) { - unsafe { - socExit(); - free(self.soc_mem as *mut _); - } - - SOC_ACTIVE.store(false, Ordering::SeqCst); - } -} - #[cfg(test)] mod tests { use super::*; @@ -72,10 +69,14 @@ mod tests { #[test] fn soc_duplicate() { let _soc = Soc::init().unwrap(); + let lock = *SOC_ACTIVE.lock().unwrap(); + + assert_eq!(lock, 1); + + drop(_soc); + + let lock = *SOC_ACTIVE.lock().unwrap(); - match Soc::init() { - Err(Error::ServiceAlreadyActive("Soc")) => return, - _ => panic!(), - } + assert_eq!(lock, 0); } } diff --git a/ctru-rs/src/services/sslc.rs b/ctru-rs/src/services/sslc.rs index 0ee2661..ba8d41f 100644 --- a/ctru-rs/src/services/sslc.rs +++ b/ctru-rs/src/services/sslc.rs @@ -1,28 +1,41 @@ // TODO: Implement remaining functions -use std::sync::atomic::{AtomicBool, Ordering}; +use std::lazy::SyncLazy; +use std::sync::Mutex; -use crate::error::Error; +use crate::services::ServiceHandler; #[non_exhaustive] -pub struct SslC(()); +pub struct SslC { + _service_handler: ServiceHandler, +} -static SSLC_ACTIVE: AtomicBool = AtomicBool::new(false); +static SSLC_ACTIVE: SyncLazy> = SyncLazy::new(|| Mutex::new(0)); impl SslC { /// Initialize sslc pub fn init() -> crate::Result { - match SSLC_ACTIVE.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) { - Ok(_) => { + let _service_handler = ServiceHandler::new( + &SSLC_ACTIVE, + true, + || { let r = unsafe { ctru_sys::sslcInit(0) }; if r < 0 { - Err(r.into()) - } else { - Ok(Self(())) + return Err(r.into()); } - } - Err(_) => Err(Error::ServiceAlreadyActive("SslC")), - } + + Ok(()) + }, + // `socExit` returns an error code. There is no documentantion of when errors could happen, + // but we wouldn't be able to handle them in the `Drop` implementation anyways. + // Surely nothing bad will happens :D + || unsafe { + // The socket buffer is freed automatically by `socExit` + ctru_sys::sslcExit(); + }, + )?; + + Ok(Self { _service_handler }) } /// Fill `buf` with `buf.len()` random bytes @@ -36,14 +49,6 @@ impl SslC { } } -impl Drop for SslC { - fn drop(&mut self) { - unsafe { ctru_sys::sslcExit() }; - - SSLC_ACTIVE.store(false, Ordering::SeqCst); - } -} - #[cfg(test)] mod tests { use super::*; @@ -52,9 +57,14 @@ mod tests { fn sslc_duplicate() { let _sslc = SslC::init().unwrap(); - match SslC::init() { - Err(Error::ServiceAlreadyActive("SslC")) => return, - _ => panic!(), - } + let lock = *SSLC_ACTIVE.lock().unwrap(); + + assert_eq!(lock, 1); + + drop(_sslc); + + let lock = *SSLC_ACTIVE.lock().unwrap(); + + assert_eq!(lock, 0); } } diff --git a/ctru-rs/src/srv.rs b/ctru-rs/src/srv.rs index 5445066..fa0f353 100644 --- a/ctru-rs/src/srv.rs +++ b/ctru-rs/src/srv.rs @@ -1,33 +1,34 @@ -use std::sync::atomic::{AtomicBool, Ordering}; +use std::lazy::SyncLazy; +use std::sync::Mutex; -use crate::error::Error; +use crate::services::ServiceHandler; #[non_exhaustive] -pub struct Srv(()); +pub struct Srv { + _service_handler: ServiceHandler, +} -static SRV_ACTIVE: AtomicBool = AtomicBool::new(false); +static SRV_ACTIVE: SyncLazy> = SyncLazy::new(|| Mutex::new(0)); impl Srv { pub fn init() -> crate::Result { - match SRV_ACTIVE.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) { - Ok(_) => { + let _service_handler = ServiceHandler::new( + &SRV_ACTIVE, + true, + || { let r = unsafe { ctru_sys::srvInit() }; if r < 0 { - Err(r.into()) - } else { - Ok(Self(())) + return Err(r.into()); } - } - Err(_) => Err(Error::ServiceAlreadyActive("Srv")), - } - } -} -impl Drop for Srv { - fn drop(&mut self) { - unsafe { ctru_sys::srvExit() }; + Ok(()) + }, + || unsafe { + ctru_sys::srvExit(); + }, + )?; - SRV_ACTIVE.store(false, Ordering::SeqCst); + Ok(Self { _service_handler }) } } @@ -39,9 +40,14 @@ mod tests { fn srv_duplicate() { let _srv = Srv::init().unwrap(); - match Srv::init() { - Err(Error::ServiceAlreadyActive("Srv")) => return, - _ => panic!(), - } + let lock = *SRV_ACTIVE.lock().unwrap(); + + assert_eq!(lock, 1); + + drop(_srv); + + let lock = *SRV_ACTIVE.lock().unwrap(); + + assert_eq!(lock, 0); } }