diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index 38b3319f..569030c9 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -8,6 +8,8 @@ code then it will not be listed here. ## [2022-10-20] +- `Editor` now only requires `Send` and no longer needs `Sync`. This is not a + breaking change, but it might be worth being aware of. - The `create_egui_editor()` function from `nih_plug_egui` now also takes a build closure to apply initialization logic to the egui context. - The `nih_plug::param` module has been renamed to `nih_plug::params`. Code that diff --git a/src/plugin.rs b/src/plugin.rs index 6d8580b1..d3b2b294 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -253,7 +253,7 @@ const fn swap_vst3_uid_byte_order(mut uid: [u8; 16]) -> [u8; 16] { } /// An editor for a [`Plugin`]. -pub trait Editor: Send + Sync { +pub trait Editor: Send { /// Create an instance of the plugin's editor and embed it in the parent window. As explained in /// [`Plugin::editor()`], you can then read the parameter values directly from your [`Params`] /// object, and modifying the values can be done using the functions on the diff --git a/src/wrapper/clap/wrapper.rs b/src/wrapper/clap/wrapper.rs index f4538d79..10709435 100644 --- a/src/wrapper/clap/wrapper.rs +++ b/src/wrapper/clap/wrapper.rs @@ -59,7 +59,7 @@ use clap_sys::stream::{clap_istream, clap_ostream}; use crossbeam::atomic::AtomicCell; use crossbeam::channel::{self, SendTimeoutError}; use crossbeam::queue::ArrayQueue; -use parking_lot::RwLock; +use parking_lot::{Mutex, RwLock}; use raw_window_handle::RawWindowHandle; use std::any::Any; use std::cmp; @@ -112,7 +112,7 @@ pub struct Wrapper { /// The plugin's editor, if it has one. This object does not do anything on its own, but we need /// to instantiate this in advance so we don't need to lock the entire [`Plugin`] object when /// creating an editor. - editor: Option>, + editor: Option>>, /// A handle for the currently active editor instance. The plugin should implement `Drop` on /// this handle for its closing behavior. editor_handle: RwLock>>, @@ -381,7 +381,7 @@ impl MainThreadExecutor for Wrapper

{ impl Wrapper

{ pub fn new(host_callback: *const clap_host) -> Arc { let plugin = P::default(); - let editor = plugin.editor(); + let editor = plugin.editor().map(Mutex::new); // This is used to allow the plugin to restore preset data from its editor, see the comment // on `Self::updated_state_sender` @@ -713,10 +713,18 @@ impl Wrapper

{ /// If there's an editor open, let it know that parameter values have changed. This should be /// called whenever there's been a call or multiple calls to - /// [`update_plain_value_by_hash()[Self::update_plain_value_by_hash()`]. + /// [`update_plain_value_by_hash()[Self::update_plain_value_by_hash()`]. In the off-chance that + /// the editor instance is currently locked then nothing will happen, and the request can safely + /// be ignored. pub fn notify_param_values_changed(&self) { if let Some(editor) = &self.editor { - editor.param_values_changed(); + match editor.try_lock() { + Some(editor) => editor.param_values_changed(), + None => nih_debug_assert_failure!( + "The editor was locked when sending a parameter value change notification, \ + ignoring" + ), + } } } @@ -726,7 +734,7 @@ impl Wrapper

{ pub fn request_resize(&self) -> bool { match (&*self.host_gui.borrow(), &self.editor) { (Some(host_gui), Some(editor)) => { - let (unscaled_width, unscaled_height) = editor.size(); + let (unscaled_width, unscaled_height) = editor.lock().size(); let scaling_factor = self.editor_scaling_factor.load(Ordering::Relaxed); unsafe_clap_call! { @@ -2672,6 +2680,7 @@ impl Wrapper

{ .editor .as_ref() .unwrap() + .lock() .set_scale_factor(scale as f32) { wrapper @@ -2692,7 +2701,7 @@ impl Wrapper

{ let wrapper = &*(plugin as *const Self); // For macOS the scaling factor is always 1 - let (unscaled_width, unscaled_height) = wrapper.editor.as_ref().unwrap().size(); + let (unscaled_width, unscaled_height) = wrapper.editor.as_ref().unwrap().lock().size(); let scaling_factor = wrapper.editor_scaling_factor.load(Ordering::Relaxed); (*width, *height) = ( (unscaled_width as f32 * scaling_factor).round() as u32, @@ -2734,7 +2743,7 @@ impl Wrapper

{ check_null_ptr!(false, plugin); let wrapper = &*(plugin as *const Self); - let (unscaled_width, unscaled_height) = wrapper.editor.as_ref().unwrap().size(); + let (unscaled_width, unscaled_height) = wrapper.editor.as_ref().unwrap().lock().size(); let scaling_factor = wrapper.editor_scaling_factor.load(Ordering::Relaxed); let (editor_width, editor_height) = ( (unscaled_width as f32 * scaling_factor).round() as u32, @@ -2776,7 +2785,7 @@ impl Wrapper

{ }; // This extension is only exposed when we have an editor - *editor_handle = Some(wrapper.editor.as_ref().unwrap().spawn( + *editor_handle = Some(wrapper.editor.as_ref().unwrap().lock().spawn( ParentWindowHandle { handle }, wrapper.clone().make_gui_context(), )); diff --git a/src/wrapper/standalone/context.rs b/src/wrapper/standalone/context.rs index 2667581c..390585a6 100644 --- a/src/wrapper/standalone/context.rs +++ b/src/wrapper/standalone/context.rs @@ -47,7 +47,7 @@ impl GuiContext for WrapperGuiContext { } fn request_resize(&self) -> bool { - let (unscaled_width, unscaled_height) = self.wrapper.editor.as_ref().unwrap().size(); + let (unscaled_width, unscaled_height) = self.wrapper.editor.as_ref().unwrap().lock().size(); // This will cause the editor to be resized at the start of the next frame let push_successful = self diff --git a/src/wrapper/standalone/wrapper.rs b/src/wrapper/standalone/wrapper.rs index c997cf1b..11552431 100644 --- a/src/wrapper/standalone/wrapper.rs +++ b/src/wrapper/standalone/wrapper.rs @@ -2,7 +2,7 @@ use atomic_refcell::AtomicRefCell; use baseview::{EventStatus, Window, WindowHandler, WindowOpenOptions}; use crossbeam::channel; use crossbeam::queue::ArrayQueue; -use parking_lot::RwLock; +use parking_lot::{Mutex, RwLock}; use raw_window_handle::HasRawWindowHandle; use std::any::Any; use std::collections::{HashMap, HashSet}; @@ -47,7 +47,7 @@ pub struct Wrapper { /// The plugin's editor, if it has one. This object does not do anything on its own, but we need /// to instantiate this in advance so we don't need to lock the entire [`Plugin`] object when /// creating an editor. - pub editor: Option>, + pub editor: Option>>>, config: WrapperConfig, @@ -132,7 +132,7 @@ impl Wrapper { pub fn new(backend: B, config: WrapperConfig) -> Result, WrapperError> { let plugin = P::default(); let params = plugin.params(); - let editor = plugin.editor().map(Arc::from); + let editor = plugin.editor().map(|editor| Arc::new(Mutex::new(editor))); // This is used to allow the plugin to restore preset data from its editor, see the comment // on `Self::updated_state_sender` @@ -262,11 +262,11 @@ impl Wrapper { let scaling_policy = baseview::WindowScalePolicy::SystemScaleFactor; #[cfg(not(target_os = "macos"))] let scaling_policy = { - editor.set_scale_factor(self.config.dpi_scale); + editor.lock().set_scale_factor(self.config.dpi_scale); baseview::WindowScalePolicy::ScaleFactor(self.config.dpi_scale as f64) }; - let (width, height) = editor.size(); + let (width, height) = editor.lock().size(); Window::open_blocking( WindowOpenOptions { title: String::from(P::NAME), @@ -282,7 +282,7 @@ impl Wrapper { // baseview does not support this yet. Once this is added, we should // immediately close the parent window when this happens so the loop // can exit. - let editor_handle = editor.spawn( + let editor_handle = editor.lock().spawn( ParentWindowHandle { handle: window.raw_window_handle(), }, @@ -477,10 +477,18 @@ impl Wrapper { ); } - /// Tell the editor that the parameter values have changed, if the plugin has an editor. + /// Tell the editor that the parameter values have changed, if the plugin has an editor. In the + /// off-chance that the editor instance is currently locked then nothing will happen, and the + /// request can safely be ignored. fn notify_param_values_changed(&self) { if let Some(editor) = &self.editor { - editor.param_values_changed(); + match editor.try_lock() { + Some(editor) => editor.param_values_changed(), + None => nih_debug_assert_failure!( + "The editor was locked when sending a parameter value change notification, \ + ignoring" + ), + } } } diff --git a/src/wrapper/vst3/inner.rs b/src/wrapper/vst3/inner.rs index 55205fb6..728d9ff6 100644 --- a/src/wrapper/vst3/inner.rs +++ b/src/wrapper/vst3/inner.rs @@ -1,7 +1,7 @@ use atomic_refcell::AtomicRefCell; use crossbeam::atomic::AtomicCell; use crossbeam::channel::{self, SendTimeoutError}; -use parking_lot::RwLock; +use parking_lot::{Mutex, RwLock}; use std::collections::{HashMap, HashSet, VecDeque}; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::Arc; @@ -37,7 +37,7 @@ pub(crate) struct WrapperInner { /// The plugin's editor, if it has one. This object does not do anything on its own, but we need /// to instantiate this in advance so we don't need to lock the entire [`Plugin`] object when /// creating an editor. - pub editor: Option>, + pub editor: Option>>>, /// The host's [`IComponentHandler`] instance, if passed through /// [`IEditController::set_component_handler`]. @@ -190,7 +190,7 @@ impl WrapperInner

{ #[allow(unused_unsafe)] pub fn new() -> Arc { let plugin = P::default(); - let editor = plugin.editor().map(Arc::from); + let editor = plugin.editor().map(|editor| Arc::new(Mutex::new(editor))); // This is used to allow the plugin to restore preset data from its editor, see the comment // on `Self::updated_state_sender` @@ -372,10 +372,18 @@ impl WrapperInner

{ /// If there's an editor open, let it know that parameter values have changed. This should be /// called whenever there's been a call or multiple calls to - /// [`set_normalized_value_by_hash()[Self::set_normalized_value_by_hash()`]. + /// [`set_normalized_value_by_hash()[Self::set_normalized_value_by_hash()`]. In the off-chance + /// that the editor instance is currently locked then nothing will happen, and the request can + /// safely be ignored. pub fn notify_param_values_changed(&self) { if let Some(editor) = &self.editor { - editor.param_values_changed(); + match editor.try_lock() { + Some(editor) => editor.param_values_changed(), + None => nih_debug_assert_failure!( + "The editor was locked when sending a parameter value change notification, \ + ignoring" + ), + } } } diff --git a/src/wrapper/vst3/view.rs b/src/wrapper/vst3/view.rs index cdb88560..937032c5 100644 --- a/src/wrapper/vst3/view.rs +++ b/src/wrapper/vst3/view.rs @@ -1,5 +1,5 @@ use atomic_float::AtomicF32; -use parking_lot::RwLock; +use parking_lot::{Mutex, RwLock}; use raw_window_handle::RawWindowHandle; use std::any::Any; use std::ffi::{c_void, CStr}; @@ -52,7 +52,7 @@ struct RunLoopEventHandlerWrapper(std::marker::PhantomData

); #[VST3(implements(IPlugView, IPlugViewContentScaleSupport))] pub(crate) struct WrapperView { inner: Arc>, - editor: Arc, + editor: Arc>>, editor_handle: RwLock>>, /// The `IPlugFrame` instance passed by the host during [IPlugView::set_frame()]. @@ -99,7 +99,7 @@ struct RunLoopEventHandler { } impl WrapperView

{ - pub fn new(inner: Arc>, editor: Arc) -> Box { + pub fn new(inner: Arc>, editor: Arc>>) -> Box { Self::allocate( inner, editor, @@ -132,7 +132,7 @@ impl WrapperView

{ match &*self.plug_frame.read() { Some(plug_frame) => { - let (unscaled_width, unscaled_height) = self.editor.size(); + let (unscaled_width, unscaled_height) = self.editor.lock().size(); let scaling_factor = self.scaling_factor.load(Ordering::Relaxed); let mut size = ViewRect { right: (unscaled_width as f32 * scaling_factor).round() as i32, @@ -314,7 +314,7 @@ impl IPlugView for WrapperView

{ } }; - *editor_handle = Some(self.editor.spawn( + *editor_handle = Some(self.editor.lock().spawn( ParentWindowHandle { handle }, self.inner.clone().make_gui_context(), )); @@ -376,7 +376,7 @@ impl IPlugView for WrapperView

{ // TODO: This is technically incorrect during resizing, this should still report the old // size until `.on_size()` has been called. We should probably only bother fixing this // if it turns out to be an issue. - let (unscaled_width, unscaled_height) = self.editor.size(); + let (unscaled_width, unscaled_height) = self.editor.lock().size(); let scaling_factor = self.scaling_factor.load(Ordering::Relaxed); let size = &mut *size; size.left = 0; @@ -391,7 +391,7 @@ impl IPlugView for WrapperView

{ check_null_ptr!(new_size); // TODO: Implement Host->Plugin resizing - let (unscaled_width, unscaled_height) = self.editor.size(); + let (unscaled_width, unscaled_height) = self.editor.lock().size(); let scaling_factor = self.scaling_factor.load(Ordering::Relaxed); let (editor_width, editor_height) = ( (unscaled_width as f32 * scaling_factor).round() as i32, @@ -470,7 +470,7 @@ impl IPlugViewContentScaleSupport for WrapperView

{ return kResultFalse; } - if self.editor.set_scale_factor(factor) { + if self.editor.lock().set_scale_factor(factor) { self.scaling_factor.store(factor, Ordering::Relaxed); kResultOk } else {