Skip to content

Commit

Permalink
Simplify lifetimes with JUCE application handle
Browse files Browse the repository at this point in the history
See #10
  • Loading branch information
JamesHallowell committed Dec 22, 2024
1 parent 4c17441 commit 7a60380
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ jobs:
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
run: cargo test --verbose -- --test-threads=1
11 changes: 5 additions & 6 deletions src/juce_audio_devices.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use {
crate::{juce, Result, JUCE},
slotmap::SlotMap,
std::{
marker::PhantomData,
ops::{Index, IndexMut},
pin::Pin,
},
Expand Down Expand Up @@ -222,19 +221,19 @@ slotmap::new_key_type! {
}

/// Manages the state of an audio device.
pub struct AudioDeviceManager<'juce> {
pub struct AudioDeviceManager {
device_manager: cxx::UniquePtr<juce::AudioDeviceManager>,
callbacks: SlotMap<AudioCallbackKey, cxx::UniquePtr<juce::AudioCallbackWrapper>>,
_juce: PhantomData<&'juce ()>,
_juce: JUCE,
}

impl<'juce> AudioDeviceManager<'juce> {
impl AudioDeviceManager {
/// Create a new [`AudioDeviceManager`].
pub fn new(_juce: &'juce JUCE) -> Self {
pub fn new(juce: &JUCE) -> Self {
Self {
device_manager: juce::create_audio_device_manager(),
callbacks: SlotMap::with_key(),
_juce: PhantomData,
_juce: juce.clone(),
}
}

Expand Down
131 changes: 113 additions & 18 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,46 +17,85 @@ use {
},
BoxedAudioIODevice, BoxedAudioIODeviceCallback, BoxedAudioIODeviceType,
},
std::sync::{Mutex, MutexGuard},
std::{
rc::Rc,
sync::atomic::{AtomicBool, Ordering},
},
};

/// Returns the version of the JUCE library.
pub fn juce_version() -> String {
juce::version()
}

/// An RAII guard for JUCE. Required for certain JUCE classes.
/// A handle to the JUCE runtime. Required for certain JUCE classes.
///
/// Once all references to this object are dropped, the JUCE runtime will be shut down.
#[must_use]
pub struct JUCE<'juce> {
_guard: MutexGuard<'juce, ()>,
#[derive(Clone)]
pub struct JUCE {
_app: Rc<JuceApp>,
}

static JUCE_INSTANCE: Mutex<()> = Mutex::new(());

impl<'juce> JUCE<'juce> {
/// Initialise JUCE. Panics if JUCE is already initialised.
pub fn initialise() -> Self {
Self::new(JUCE_INSTANCE.try_lock().expect("JUCE already initialised"))
}
static IS_JUCE_RUNNING: AtomicBool = AtomicBool::new(false);

#[doc(hidden)]
pub fn wait_to_initialise_in_test_context() -> Self {
Self::new(JUCE_INSTANCE.lock().unwrap())
}
struct JuceApp;

fn new(guard: MutexGuard<'juce, ()>) -> Self {
impl JuceApp {
fn new() -> Self {
juce::initialise_juce();

#[cfg(target_os = "macos")]
juce::initialise_ns_application();

Self { _guard: guard }
Self
}
}

impl Drop for JUCE<'_> {
impl Drop for JuceApp {
fn drop(&mut self) {
juce::shutdown_juce();

IS_JUCE_RUNNING.store(false, Ordering::SeqCst);
}
}

#[derive(Debug)]
enum InitialiseError {
JuceAlreadyInitialised,
}

impl std::error::Error for InitialiseError {}

impl std::fmt::Display for InitialiseError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::JuceAlreadyInitialised => write!(f, "JUCE has already been initialised"),
}
}
}

impl JUCE {
/// Initialises the JUCE runtime.
///
/// # Panics
///
/// This function will panic if the JUCE runtime is already initialised.
pub fn initialise() -> Self {
Self::try_initialise().unwrap()
}

fn try_initialise() -> std::result::Result<Self, InitialiseError> {
let result =
IS_JUCE_RUNNING.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst);

if result.is_err() {
return Err(InitialiseError::JuceAlreadyInitialised);
}

Ok(Self {
_app: Rc::new(JuceApp::new()),
})
}
}

Expand Down Expand Up @@ -428,3 +467,59 @@ pub(crate) mod juce {
pub fn makeNotchFilter(sample_rate: f64, frequency: f64, q: f64) -> [f32; 5];
}
}

#[cfg(test)]
mod test {
use super::*;

fn try_to_initialise_juce_on_new_thread() -> std::thread::Result<()> {
std::thread::spawn(move || {
let _juce = JUCE::initialise();
})
.join()
}

#[test]
#[should_panic]
fn initialising_juce_twice_on_the_same_thread_should_panic() {
let _juce = JUCE::initialise();
let _juce = JUCE::initialise();
}

#[test]
fn initialising_juce_again_on_the_same_thread_after_shutdown_is_ok() {
let juce = JUCE::initialise();
drop(juce);

let _juce = JUCE::initialise();
}

#[test]
fn juce_cant_be_initialised_simultaneously_on_two_different_threads() {
let _juce = JUCE::initialise();

assert!(try_to_initialise_juce_on_new_thread().is_err());
}

#[test]
fn juce_can_run_on_a_different_thread_after_finishing_on_another() {
let juce = JUCE::initialise();
drop(juce);

assert!(try_to_initialise_juce_on_new_thread().is_ok());
}

#[test]
fn juce_is_shutdown_once_all_references_have_been_dropped() {
let a = JUCE::initialise();
let b = a.clone();

drop(a);

assert!(try_to_initialise_juce_on_new_thread().is_err());

drop(b);

assert!(try_to_initialise_juce_on_new_thread().is_ok());
}
}
6 changes: 3 additions & 3 deletions tests/juce_audio_devices.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ impl AudioIODevice for MockAudioDevice {

#[test]
fn can_query_audio_device_types() {
let juce = JUCE::wait_to_initialise_in_test_context();
let juce = JUCE::initialise();
let mut audio_device_manager = AudioDeviceManager::new(&juce);
audio_device_manager.add_audio_device_type(MockAudioDeviceType::default());
audio_device_manager.set_current_audio_device_type("Test");
Expand Down Expand Up @@ -131,7 +131,7 @@ fn can_query_audio_device_types() {

#[test]
fn can_configure_audio_device_setup() {
let juce = JUCE::wait_to_initialise_in_test_context();
let juce = JUCE::initialise();
let mut audio_device_manager = AudioDeviceManager::new(&juce);
audio_device_manager.add_audio_device_type(MockAudioDeviceType::default());
audio_device_manager.set_current_audio_device_type("Test");
Expand All @@ -158,7 +158,7 @@ fn can_configure_audio_device_setup() {

#[test]
fn can_create_devices() {
let juce = JUCE::wait_to_initialise_in_test_context();
let juce = JUCE::initialise();
let mut audio_device_manager = AudioDeviceManager::new(&juce);
audio_device_manager.add_audio_device_type(MockAudioDeviceType::default());
audio_device_manager.set_current_audio_device_type("Test");
Expand Down

0 comments on commit 7a60380

Please sign in to comment.