1
0
Fork 0

Drop Sync requirement for Editor

This commit is contained in:
Robbert van der Helm 2022-10-20 14:31:48 +02:00
parent eed5a62abb
commit a2a52e0ff1
7 changed files with 59 additions and 32 deletions

View file

@ -8,6 +8,8 @@ code then it will not be listed here.
## [2022-10-20] ## [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 - The `create_egui_editor()` function from `nih_plug_egui` now also takes a
build closure to apply initialization logic to the egui context. build closure to apply initialization logic to the egui context.
- The `nih_plug::param` module has been renamed to `nih_plug::params`. Code that - The `nih_plug::param` module has been renamed to `nih_plug::params`. Code that

View file

@ -253,7 +253,7 @@ const fn swap_vst3_uid_byte_order(mut uid: [u8; 16]) -> [u8; 16] {
} }
/// An editor for a [`Plugin`]. /// 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 /// 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`] /// [`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 /// object, and modifying the values can be done using the functions on the

View file

@ -59,7 +59,7 @@ use clap_sys::stream::{clap_istream, clap_ostream};
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
use crossbeam::channel::{self, SendTimeoutError}; use crossbeam::channel::{self, SendTimeoutError};
use crossbeam::queue::ArrayQueue; use crossbeam::queue::ArrayQueue;
use parking_lot::RwLock; use parking_lot::{Mutex, RwLock};
use raw_window_handle::RawWindowHandle; use raw_window_handle::RawWindowHandle;
use std::any::Any; use std::any::Any;
use std::cmp; use std::cmp;
@ -112,7 +112,7 @@ pub struct Wrapper<P: ClapPlugin> {
/// The plugin's editor, if it has one. This object does not do anything on its own, but we need /// 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 /// to instantiate this in advance so we don't need to lock the entire [`Plugin`] object when
/// creating an editor. /// creating an editor.
editor: Option<Box<dyn Editor>>, editor: Option<Mutex<Box<dyn Editor>>>,
/// A handle for the currently active editor instance. The plugin should implement `Drop` on /// A handle for the currently active editor instance. The plugin should implement `Drop` on
/// this handle for its closing behavior. /// this handle for its closing behavior.
editor_handle: RwLock<Option<Box<dyn Any + Send + Sync>>>, editor_handle: RwLock<Option<Box<dyn Any + Send + Sync>>>,
@ -381,7 +381,7 @@ impl<P: ClapPlugin> MainThreadExecutor<Task> for Wrapper<P> {
impl<P: ClapPlugin> Wrapper<P> { impl<P: ClapPlugin> Wrapper<P> {
pub fn new(host_callback: *const clap_host) -> Arc<Self> { pub fn new(host_callback: *const clap_host) -> Arc<Self> {
let plugin = P::default(); 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 // This is used to allow the plugin to restore preset data from its editor, see the comment
// on `Self::updated_state_sender` // on `Self::updated_state_sender`
@ -713,10 +713,18 @@ impl<P: ClapPlugin> Wrapper<P> {
/// If there's an editor open, let it know that parameter values have changed. This should be /// 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 /// 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) { pub fn notify_param_values_changed(&self) {
if let Some(editor) = &self.editor { 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<P: ClapPlugin> Wrapper<P> {
pub fn request_resize(&self) -> bool { pub fn request_resize(&self) -> bool {
match (&*self.host_gui.borrow(), &self.editor) { match (&*self.host_gui.borrow(), &self.editor) {
(Some(host_gui), Some(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); let scaling_factor = self.editor_scaling_factor.load(Ordering::Relaxed);
unsafe_clap_call! { unsafe_clap_call! {
@ -2672,6 +2680,7 @@ impl<P: ClapPlugin> Wrapper<P> {
.editor .editor
.as_ref() .as_ref()
.unwrap() .unwrap()
.lock()
.set_scale_factor(scale as f32) .set_scale_factor(scale as f32)
{ {
wrapper wrapper
@ -2692,7 +2701,7 @@ impl<P: ClapPlugin> Wrapper<P> {
let wrapper = &*(plugin as *const Self); let wrapper = &*(plugin as *const Self);
// For macOS the scaling factor is always 1 // 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); let scaling_factor = wrapper.editor_scaling_factor.load(Ordering::Relaxed);
(*width, *height) = ( (*width, *height) = (
(unscaled_width as f32 * scaling_factor).round() as u32, (unscaled_width as f32 * scaling_factor).round() as u32,
@ -2734,7 +2743,7 @@ impl<P: ClapPlugin> Wrapper<P> {
check_null_ptr!(false, plugin); check_null_ptr!(false, plugin);
let wrapper = &*(plugin as *const Self); 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 scaling_factor = wrapper.editor_scaling_factor.load(Ordering::Relaxed);
let (editor_width, editor_height) = ( let (editor_width, editor_height) = (
(unscaled_width as f32 * scaling_factor).round() as u32, (unscaled_width as f32 * scaling_factor).round() as u32,
@ -2776,7 +2785,7 @@ impl<P: ClapPlugin> Wrapper<P> {
}; };
// This extension is only exposed when we have an editor // 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 }, ParentWindowHandle { handle },
wrapper.clone().make_gui_context(), wrapper.clone().make_gui_context(),
)); ));

View file

@ -47,7 +47,7 @@ impl<P: Plugin, B: Backend> GuiContext for WrapperGuiContext<P, B> {
} }
fn request_resize(&self) -> bool { 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 // This will cause the editor to be resized at the start of the next frame
let push_successful = self let push_successful = self

View file

@ -2,7 +2,7 @@ use atomic_refcell::AtomicRefCell;
use baseview::{EventStatus, Window, WindowHandler, WindowOpenOptions}; use baseview::{EventStatus, Window, WindowHandler, WindowOpenOptions};
use crossbeam::channel; use crossbeam::channel;
use crossbeam::queue::ArrayQueue; use crossbeam::queue::ArrayQueue;
use parking_lot::RwLock; use parking_lot::{Mutex, RwLock};
use raw_window_handle::HasRawWindowHandle; use raw_window_handle::HasRawWindowHandle;
use std::any::Any; use std::any::Any;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@ -47,7 +47,7 @@ pub struct Wrapper<P: Plugin, B: Backend> {
/// The plugin's editor, if it has one. This object does not do anything on its own, but we need /// 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 /// to instantiate this in advance so we don't need to lock the entire [`Plugin`] object when
/// creating an editor. /// creating an editor.
pub editor: Option<Arc<dyn Editor>>, pub editor: Option<Arc<Mutex<Box<dyn Editor>>>>,
config: WrapperConfig, config: WrapperConfig,
@ -132,7 +132,7 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
pub fn new(backend: B, config: WrapperConfig) -> Result<Arc<Self>, WrapperError> { pub fn new(backend: B, config: WrapperConfig) -> Result<Arc<Self>, WrapperError> {
let plugin = P::default(); let plugin = P::default();
let params = plugin.params(); 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 // This is used to allow the plugin to restore preset data from its editor, see the comment
// on `Self::updated_state_sender` // on `Self::updated_state_sender`
@ -262,11 +262,11 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
let scaling_policy = baseview::WindowScalePolicy::SystemScaleFactor; let scaling_policy = baseview::WindowScalePolicy::SystemScaleFactor;
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
let scaling_policy = { 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) baseview::WindowScalePolicy::ScaleFactor(self.config.dpi_scale as f64)
}; };
let (width, height) = editor.size(); let (width, height) = editor.lock().size();
Window::open_blocking( Window::open_blocking(
WindowOpenOptions { WindowOpenOptions {
title: String::from(P::NAME), title: String::from(P::NAME),
@ -282,7 +282,7 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
// baseview does not support this yet. Once this is added, we should // baseview does not support this yet. Once this is added, we should
// immediately close the parent window when this happens so the loop // immediately close the parent window when this happens so the loop
// can exit. // can exit.
let editor_handle = editor.spawn( let editor_handle = editor.lock().spawn(
ParentWindowHandle { ParentWindowHandle {
handle: window.raw_window_handle(), handle: window.raw_window_handle(),
}, },
@ -477,10 +477,18 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
); );
} }
/// 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) { fn notify_param_values_changed(&self) {
if let Some(editor) = &self.editor { 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"
),
}
} }
} }

View file

@ -1,7 +1,7 @@
use atomic_refcell::AtomicRefCell; use atomic_refcell::AtomicRefCell;
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
use crossbeam::channel::{self, SendTimeoutError}; use crossbeam::channel::{self, SendTimeoutError};
use parking_lot::RwLock; use parking_lot::{Mutex, RwLock};
use std::collections::{HashMap, HashSet, VecDeque}; use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::Arc; use std::sync::Arc;
@ -37,7 +37,7 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
/// The plugin's editor, if it has one. This object does not do anything on its own, but we need /// 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 /// to instantiate this in advance so we don't need to lock the entire [`Plugin`] object when
/// creating an editor. /// creating an editor.
pub editor: Option<Arc<dyn Editor>>, pub editor: Option<Arc<Mutex<Box<dyn Editor>>>>,
/// The host's [`IComponentHandler`] instance, if passed through /// The host's [`IComponentHandler`] instance, if passed through
/// [`IEditController::set_component_handler`]. /// [`IEditController::set_component_handler`].
@ -190,7 +190,7 @@ impl<P: Vst3Plugin> WrapperInner<P> {
#[allow(unused_unsafe)] #[allow(unused_unsafe)]
pub fn new() -> Arc<Self> { pub fn new() -> Arc<Self> {
let plugin = P::default(); 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 // This is used to allow the plugin to restore preset data from its editor, see the comment
// on `Self::updated_state_sender` // on `Self::updated_state_sender`
@ -372,10 +372,18 @@ impl<P: Vst3Plugin> WrapperInner<P> {
/// If there's an editor open, let it know that parameter values have changed. This should be /// 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 /// 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) { pub fn notify_param_values_changed(&self) {
if let Some(editor) = &self.editor { 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"
),
}
} }
} }

View file

@ -1,5 +1,5 @@
use atomic_float::AtomicF32; use atomic_float::AtomicF32;
use parking_lot::RwLock; use parking_lot::{Mutex, RwLock};
use raw_window_handle::RawWindowHandle; use raw_window_handle::RawWindowHandle;
use std::any::Any; use std::any::Any;
use std::ffi::{c_void, CStr}; use std::ffi::{c_void, CStr};
@ -52,7 +52,7 @@ struct RunLoopEventHandlerWrapper<P: Vst3Plugin>(std::marker::PhantomData<P>);
#[VST3(implements(IPlugView, IPlugViewContentScaleSupport))] #[VST3(implements(IPlugView, IPlugViewContentScaleSupport))]
pub(crate) struct WrapperView<P: Vst3Plugin> { pub(crate) struct WrapperView<P: Vst3Plugin> {
inner: Arc<WrapperInner<P>>, inner: Arc<WrapperInner<P>>,
editor: Arc<dyn Editor>, editor: Arc<Mutex<Box<dyn Editor>>>,
editor_handle: RwLock<Option<Box<dyn Any>>>, editor_handle: RwLock<Option<Box<dyn Any>>>,
/// The `IPlugFrame` instance passed by the host during [IPlugView::set_frame()]. /// The `IPlugFrame` instance passed by the host during [IPlugView::set_frame()].
@ -99,7 +99,7 @@ struct RunLoopEventHandler<P: Vst3Plugin> {
} }
impl<P: Vst3Plugin> WrapperView<P> { impl<P: Vst3Plugin> WrapperView<P> {
pub fn new(inner: Arc<WrapperInner<P>>, editor: Arc<dyn Editor>) -> Box<Self> { pub fn new(inner: Arc<WrapperInner<P>>, editor: Arc<Mutex<Box<dyn Editor>>>) -> Box<Self> {
Self::allocate( Self::allocate(
inner, inner,
editor, editor,
@ -132,7 +132,7 @@ impl<P: Vst3Plugin> WrapperView<P> {
match &*self.plug_frame.read() { match &*self.plug_frame.read() {
Some(plug_frame) => { 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 scaling_factor = self.scaling_factor.load(Ordering::Relaxed);
let mut size = ViewRect { let mut size = ViewRect {
right: (unscaled_width as f32 * scaling_factor).round() as i32, right: (unscaled_width as f32 * scaling_factor).round() as i32,
@ -314,7 +314,7 @@ impl<P: Vst3Plugin> IPlugView for WrapperView<P> {
} }
}; };
*editor_handle = Some(self.editor.spawn( *editor_handle = Some(self.editor.lock().spawn(
ParentWindowHandle { handle }, ParentWindowHandle { handle },
self.inner.clone().make_gui_context(), self.inner.clone().make_gui_context(),
)); ));
@ -376,7 +376,7 @@ impl<P: Vst3Plugin> IPlugView for WrapperView<P> {
// TODO: This is technically incorrect during resizing, this should still report the old // 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 // size until `.on_size()` has been called. We should probably only bother fixing this
// if it turns out to be an issue. // 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 scaling_factor = self.scaling_factor.load(Ordering::Relaxed);
let size = &mut *size; let size = &mut *size;
size.left = 0; size.left = 0;
@ -391,7 +391,7 @@ impl<P: Vst3Plugin> IPlugView for WrapperView<P> {
check_null_ptr!(new_size); check_null_ptr!(new_size);
// TODO: Implement Host->Plugin resizing // 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 scaling_factor = self.scaling_factor.load(Ordering::Relaxed);
let (editor_width, editor_height) = ( let (editor_width, editor_height) = (
(unscaled_width as f32 * scaling_factor).round() as i32, (unscaled_width as f32 * scaling_factor).round() as i32,
@ -470,7 +470,7 @@ impl<P: Vst3Plugin> IPlugViewContentScaleSupport for WrapperView<P> {
return kResultFalse; return kResultFalse;
} }
if self.editor.set_scale_factor(factor) { if self.editor.lock().set_scale_factor(factor) {
self.scaling_factor.store(factor, Ordering::Relaxed); self.scaling_factor.store(factor, Ordering::Relaxed);
kResultOk kResultOk
} else { } else {