diff --git a/Cargo.lock b/Cargo.lock index 9f82c4fdc..d316d198f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5628,7 +5628,6 @@ dependencies = [ [[package]] name = "reaper-common-types" version = "0.1.0" -source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#93b53a7d8389f8856a60ab818ad85cc77e161a78" dependencies = [ "hex-literal", "nutype", @@ -5639,7 +5638,6 @@ dependencies = [ [[package]] name = "reaper-fluent" version = "0.1.0" -source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#93b53a7d8389f8856a60ab818ad85cc77e161a78" dependencies = [ "fragile", "reaper-low", @@ -5649,7 +5647,6 @@ dependencies = [ [[package]] name = "reaper-high" version = "0.1.0" -source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#93b53a7d8389f8856a60ab818ad85cc77e161a78" dependencies = [ "backtrace", "base64 0.13.0", @@ -5678,7 +5675,6 @@ dependencies = [ [[package]] name = "reaper-low" version = "0.1.0" -source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#93b53a7d8389f8856a60ab818ad85cc77e161a78" dependencies = [ "c_str_macro", "cc 1.1.10", @@ -5694,7 +5690,6 @@ dependencies = [ [[package]] name = "reaper-macros" version = "0.1.0" -source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#93b53a7d8389f8856a60ab818ad85cc77e161a78" dependencies = [ "darling 0.10.2", "quote", @@ -5704,7 +5699,6 @@ dependencies = [ [[package]] name = "reaper-medium" version = "0.1.0" -source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#93b53a7d8389f8856a60ab818ad85cc77e161a78" dependencies = [ "camino", "derive_more", @@ -5723,7 +5717,6 @@ dependencies = [ [[package]] name = "reaper-rx" version = "0.1.0" -source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#93b53a7d8389f8856a60ab818ad85cc77e161a78" dependencies = [ "helgoboss-midi", "reaper-high", @@ -6018,7 +6011,6 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rppxml-parser" version = "0.1.0" -source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#93b53a7d8389f8856a60ab818ad85cc77e161a78" dependencies = [ "splitty", ] diff --git a/Cargo.toml b/Cargo.toml index bf4582d4e..1653a862f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -253,15 +253,15 @@ vst = { git = "https://github.com/helgoboss/vst-rs.git", branch = "feature/param #vst = { path = "../vst-rs" } # This is for temporary development with local reaper-rs. -#[patch.'https://github.com/helgoboss/reaper-rs.git'] -#reaper-common-types = { path = "../reaper-rs/main/common-types" } -#reaper-fluent = { path = "../reaper-rs/main/fluent" } -#reaper-high = { path = "../reaper-rs/main/high" } -#reaper-medium = { path = "../reaper-rs/main/medium" } -#reaper-macros = { path = "../reaper-rs/main/macros" } -#reaper-low = { path = "../reaper-rs/main/low" } -#reaper-rx = { path = "../reaper-rs/main/rx" } -#rppxml-parser = { path = "../reaper-rs/main/rppxml-parser" } +[patch.'https://github.com/helgoboss/reaper-rs.git'] +reaper-common-types = { path = "../reaper-rs/main/common-types" } +reaper-fluent = { path = "../reaper-rs/main/fluent" } +reaper-high = { path = "../reaper-rs/main/high" } +reaper-medium = { path = "../reaper-rs/main/medium" } +reaper-macros = { path = "../reaper-rs/main/macros" } +reaper-low = { path = "../reaper-rs/main/low" } +reaper-rx = { path = "../reaper-rs/main/rx" } +rppxml-parser = { path = "../reaper-rs/main/rppxml-parser" } ## This is for temporary development with local egui-baseview. #[patch.'https://github.com/helgoboss/egui-baseview.git'] diff --git a/base/src/global_macros.rs b/base/src/global_macros.rs index c691d56f2..a5111a9c2 100644 --- a/base/src/global_macros.rs +++ b/base/src/global_macros.rs @@ -2,70 +2,28 @@ #[macro_export] macro_rules! make_available_globally_in_main_thread_on_demand { ($instance_struct:path) => { - // This is safe (see https://doc.rust-lang.org/std/sync/struct.Once.html#examples-1). - // TODO-high CONTINUE Use new once_cell-like stuff in std instead - static mut INSTANCE: Option<$instance_struct> = None; + static INSTANCE: std::sync::OnceLock> = + std::sync::OnceLock::new(); impl $instance_struct { pub fn make_available_globally(create_instance: impl FnOnce() -> $instance_struct) { - static INIT_INSTANCE: std::sync::Once = std::sync::Once::new(); - unsafe { - INIT_INSTANCE.call_once(|| { - INSTANCE = Some(create_instance()); - reaper_low::register_plugin_destroy_hook(reaper_low::PluginDestroyHook { - name: stringify!($instance_struct), - callback: || INSTANCE = None, - }); - }); + if INSTANCE.get().is_some() { + return; } + let _ = INSTANCE.set(fragile::Fragile::new(create_instance())); } /// Whether this instance is (already/still) loaded. pub fn is_loaded() -> bool { - unsafe { INSTANCE.is_some() } + INSTANCE.get().is_some() } /// Panics if not in main thread. pub fn get() -> &'static $instance_struct { - reaper_high::Reaper::get().require_main_thread(); - unsafe { - INSTANCE - .as_ref() - .expect("call `make_available_globally()` before using `get()`") - } - } - } - }; -} - -/// Use only where absolutely necessary because of static-only FFI stuff! -/// -/// The given struct must be thread-safe. If not, all of its public methods should first check if -/// the thread is correct. -#[macro_export] -macro_rules! make_available_globally_in_any_non_rt_thread { - ($instance_struct:path) => { - impl $instance_struct { - pub fn get() -> &'static $instance_struct { - assert!( - !reaper_high::Reaper::get() - .medium_reaper() - .is_in_real_time_audio(), - "this function must not be called in a real-time thread" - ); - // This is safe (see https://doc.rust-lang.org/std/sync/struct.Once.html#examples-1). - static mut INSTANCE: Option<$instance_struct> = None; - static INIT_INSTANCE: std::sync::Once = std::sync::Once::new(); - unsafe { - INIT_INSTANCE.call_once(|| { - INSTANCE = Some(Default::default()); - reaper_low::register_plugin_destroy_hook(reaper_low::PluginDestroyHook { - name: stringify!($instance_struct), - callback: || INSTANCE = None, - }); - }); - INSTANCE.as_ref().unwrap() - } + INSTANCE + .get() + .expect("call `make_available_globally()` before using `get()`") + .get() } } }; diff --git a/main/src/infrastructure/plugin/backbone_shell.rs b/main/src/infrastructure/plugin/backbone_shell.rs index 471de1ad0..8bdba81c8 100644 --- a/main/src/infrastructure/plugin/backbone_shell.rs +++ b/main/src/infrastructure/plugin/backbone_shell.rs @@ -71,7 +71,7 @@ use once_cell::sync::Lazy; use reaper_high::{ ChangeEvent, Fx, Guid, MiddlewareControlSurface, PluginInfo, Project, Reaper, Track, }; -use reaper_low::{raw, PluginContext, Swell}; +use reaper_low::{raw, register_plugin_destroy_hook, PluginContext, PluginDestroyHook, Swell}; use reaper_macros::reaper_extension_plugin; use reaper_medium::{ AccelMsg, AcceleratorPosition, ActionValueChange, CommandId, Hmenu, HookCustomMenu, @@ -119,7 +119,18 @@ fn plugin_main(context: PluginContext) -> Result<(), Box> } pub fn init_backbone_shell(context: PluginContext) { - BackboneShell::make_available_globally(|| BackboneShell::init(context)); + BackboneShell::make_available_globally(|| { + let backbone_shell = BackboneShell::init(context); + register_plugin_destroy_hook(PluginDestroyHook { + name: "BackboneShell", + callback: || { + let backbone_shell = BackboneShell::get(); + let _ = backbone_shell.go_to_sleep(); + backbone_shell.dispose(); + }, + }); + backbone_shell + }); BackboneShell::get().show_welcome_screen_if_necessary(); } @@ -161,9 +172,9 @@ type _App = BackboneShell; pub struct BackboneShell { /// This should always be set except in the destructor. /// - /// The only reason why this is optional is, because we need to take ownership of the runtime when the shell is + /// The only reason why this is optional is that we need to take ownership of the runtime when the shell is /// dropped. - async_runtime: Option, + async_runtime: RefCell>, /// RAII _tracing_hook: Option, /// RAII @@ -178,7 +189,6 @@ pub struct BackboneShell { server: SharedRealearnServer, config: RefCell, sessions_changed_subject: RefCell>, - party_is_over_subject: LocalSubject<'static, (), ()>, control_surface_main_task_sender: RealearnControlSurfaceMainTaskSender, instance_event_sender: SenderToNormalThread, #[cfg(feature = "playtime")] @@ -478,7 +488,7 @@ impl BackboneShell { let shutdown_detection_panel = SharedView::new(HiddenHelperPanel::new()); shutdown_detection_panel.clone().open(reaper_main_window()); BackboneShell { - async_runtime: Some(async_runtime), + async_runtime: RefCell::new(Some(async_runtime)), _tracing_hook: tracing_hook, _metrics_hook: metrics_hook, state: RefCell::new(AppState::Sleeping(sleeping_state)), @@ -491,7 +501,6 @@ impl BackboneShell { server: Rc::new(RefCell::new(server)), config: RefCell::new(config), sessions_changed_subject, - party_is_over_subject: Default::default(), control_surface_main_task_sender, instance_event_sender, #[cfg(feature = "playtime")] @@ -514,17 +523,18 @@ impl BackboneShell { /// Called when static is destroyed (REAPER exit or - on Windows if configured - complete VST /// unload if extension not loaded, just VST) - pub fn dispose(&mut self) { + pub fn dispose(&self) { // Shutdown async runtime tracing::info!("Shutting down async runtime..."); - if let Some(async_runtime) = self.async_runtime.take() { - // 1 second timeout caused a freeze sometimes (Windows) - async_runtime.shutdown_background(); + if let Ok(mut async_runtime) = self.async_runtime.try_borrow_mut() { + if let Some(async_runtime) = async_runtime.take() { + // 1 second timeout caused a freeze sometimes (Windows) + async_runtime.shutdown_background(); + } } tracing::info!("Async runtime shut down successfully"); let _ = Reaper::get().go_to_sleep(); self.message_panel.close(); - self.party_is_over_subject.next(()); let _ = unregister_api(); } @@ -665,10 +675,15 @@ impl BackboneShell { } } - fn async_runtime(&self) -> &Runtime { - self.async_runtime + fn with_async_runtime(&self, f: impl FnOnce(&Runtime) -> R) -> anyhow::Result { + let runtime = self + .async_runtime + .try_borrow() + .context("async runtime already borrowed")?; + let runtime = runtime .as_ref() - .expect("async runtime dropped already") + .context("async runtime already destroyed")?; + Ok(f(runtime)) } /// Executed whenever the first Helgobox instance is loaded. @@ -715,10 +730,12 @@ impl BackboneShell { ); // Activate server if self.config.borrow().server_is_enabled() { - self.server() - .borrow_mut() - .start(self.async_runtime(), self.create_services()) - .unwrap_or_else(warn_about_failed_server_start); + let _ = self.with_async_runtime(|runtime| { + self.server() + .borrow_mut() + .start(runtime, self.create_services()) + .unwrap_or_else(warn_about_failed_server_start); + }); } let mut session = Reaper::get().medium_session(); // Action hooks @@ -774,25 +791,23 @@ impl BackboneShell { } // Executed whenever the last ReaLearn instance goes away. - pub fn go_to_sleep(&self) { + pub fn go_to_sleep(&self) -> anyhow::Result<()> { let prev_state = self.state.replace(AppState::GoingToSleep); - let awake_state = if let AppState::Awake(s) = prev_state { - s - } else { - panic!("App was not awake when trying to go to sleep"); + let AppState::Awake(awake_state) = prev_state else { + bail!("App was not awake when trying to go to sleep"); }; let mut session = Reaper::get().medium_session(); debug!("Unregistering ReaLearn control surface and audio hook..."); let (accelerator, mut control_surface, audio_hook) = unsafe { let accelerator = session .plugin_register_remove_accelerator(awake_state.accelerator_handle) - .expect("accelerator was not registered"); + .context("accelerator was not registered")?; let control_surface = session .plugin_register_remove_csurf_inst(awake_state.control_surface_handle) - .expect("control surface was not registered"); + .context("control surface was not registered")?; let audio_hook = session .audio_reg_hardware_hook_remove(awake_state.audio_hook_handle) - .expect("control surface was not registered"); + .context("control surface was not registered")?; (accelerator, control_surface, audio_hook) }; // Close OSC connections @@ -813,7 +828,7 @@ impl BackboneShell { let async_deallocation_receiver = awake_state .async_deallocation_thread .join() - .expect("couldn't join deallocation thread"); + .map_err(|_| anyhow!("couldn't join deallocation thread"))?; // Finally go to sleep let sleeping_state = SleepingState { control_surface, @@ -822,6 +837,7 @@ impl BackboneShell { async_deallocation_receiver, }; self.state.replace(AppState::Sleeping(sleeping_state)); + Ok(()) } /// Attention: The real-time processor is removed *async*! That means it can still be called @@ -1006,7 +1022,8 @@ impl BackboneShell { where R: Send + 'static, { - self.async_runtime().spawn(f) + self.with_async_runtime(|runtime| runtime.spawn(f)) + .expect("async runtime not available") } pub fn license_manager(&self) -> &SharedLicenseManager { @@ -1071,12 +1088,15 @@ impl BackboneShell { } pub fn start_server_persistently(&self) -> Result<(), String> { - let start_result = self - .server - .borrow_mut() - .start(self.async_runtime(), self.create_services()); - self.change_config(BackboneConfig::enable_server); - start_result + let res = self.with_async_runtime(|runtime| { + let start_result = self + .server + .borrow_mut() + .start(runtime, self.create_services()); + self.change_config(BackboneConfig::enable_server); + start_result + }); + res.unwrap_or_else(|e| Err(e.to_string())) } pub fn stop_server_persistently(&self) { @@ -1088,7 +1108,9 @@ impl BackboneShell { // Persistence self.change_config(|c| c.set_send_errors_to_dev(value)); // Actual behavior - set_send_errors_to_dev_internal(value, self.async_runtime()); + let _ = self.with_async_runtime(|runtime| { + set_send_errors_to_dev_internal(value, runtime); + }); } pub fn set_show_errors_in_console_persistently(&self, value: bool) { diff --git a/main/src/infrastructure/plugin/helgobox_plugin.rs b/main/src/infrastructure/plugin/helgobox_plugin.rs index 06dd618ae..4aa54e634 100644 --- a/main/src/infrastructure/plugin/helgobox_plugin.rs +++ b/main/src/infrastructure/plugin/helgobox_plugin.rs @@ -322,7 +322,7 @@ impl HelgoboxPlugin { let _ = BackboneShell::get().wake_up(); || { if BackboneShell::is_loaded() { - BackboneShell::get().go_to_sleep(); + let _ = BackboneShell::get().go_to_sleep(); } } }, diff --git a/main/src/infrastructure/proto/ext.rs b/main/src/infrastructure/proto/ext.rs index ed9322731..f9a9d5314 100644 --- a/main/src/infrastructure/proto/ext.rs +++ b/main/src/infrastructure/proto/ext.rs @@ -1,6 +1,6 @@ use enumflags2::BitFlags; use reaper_high::{FxChainContext, Reaper}; -use reaper_medium::{EnumPitchShiftModesResult, PlayState, ReaperStr, ReaperString}; +use reaper_medium::{EnumPitchShiftModesResult, PlayState, ReaperStr}; use helgobox_api::runtime::{ControllerPreset, LicenseInfo, MainPreset, ValidatedLicense}; diff --git a/playtime-clip-engine b/playtime-clip-engine index 8877d77cd..7e4d3c23c 160000 --- a/playtime-clip-engine +++ b/playtime-clip-engine @@ -1 +1 @@ -Subproject commit 8877d77cd8f3477166cc15383bae7bed9823a7b8 +Subproject commit 7e4d3c23ca1863e7c6c6be9237f97acab41e0056