From 083885a40c396f8180dfea18c4e53867dfe2f713 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 7 Apr 2022 15:31:46 +0200 Subject: [PATCH] Rework Params trait API with Arc instead of Pin This is a breaking change requiring a small change to plugin implementations. The reason why `Pin<&dyn Params>` was used was more as a hint to indicate that the object must last for the plugin's lifetime, but `Pin` doesn't enforce that. It also makes the APIs a lot more awkward. Requiring the use of `Arc` fixes the following problems: - When storing the params object in the wrapper, the `ParamPtr`s are guaranteed to be stable. - This makes it possible to access the `Params` object without acquiring a lock on the plugin, this is very important for implementing plugin-side preset management. - It enforces immutability on the `Params` object. - And of course the API is much nicer without a bunch of unsafe code to work around Pin's limitations. --- nih_plug_derive/src/params.rs | 11 ++++------ nih_plug_egui/src/widgets/generic_ui.rs | 4 ++-- nih_plug_iced/src/lib.rs | 8 +++---- nih_plug_iced/src/widgets/generic_ui.rs | 6 ++--- nih_plug_vizia/src/widgets/generic_ui.rs | 17 ++++++-------- plugins/crisp/src/editor.rs | 5 ++--- plugins/crisp/src/lib.rs | 9 ++++---- plugins/diopser/src/editor.rs | 5 ++--- plugins/diopser/src/lib.rs | 9 ++++---- plugins/examples/gain-gui-egui/src/lib.rs | 9 ++++---- plugins/examples/gain-gui-iced/src/editor.rs | 7 +++--- plugins/examples/gain-gui-iced/src/lib.rs | 9 ++++---- plugins/examples/gain-gui-vizia/src/editor.rs | 5 ++--- plugins/examples/gain-gui-vizia/src/lib.rs | 9 ++++---- plugins/examples/gain/src/lib.rs | 10 ++++----- plugins/examples/sine/src/lib.rs | 10 ++++----- plugins/examples/stft/src/lib.rs | 9 ++++---- plugins/puberty_simulator/src/lib.rs | 9 ++++---- src/param/internals.rs | 16 +++++++------- src/plugin.rs | 3 +-- src/wrapper/clap/wrapper.rs | 22 +++++++++++-------- src/wrapper/state.rs | 6 ++--- src/wrapper/vst3/inner.rs | 18 +++++++++------ src/wrapper/vst3/wrapper.rs | 4 ++-- 24 files changed, 105 insertions(+), 115 deletions(-) diff --git a/nih_plug_derive/src/params.rs b/nih_plug_derive/src/params.rs index 2ac1657c..51e6b1de 100644 --- a/nih_plug_derive/src/params.rs +++ b/nih_plug_derive/src/params.rs @@ -218,9 +218,7 @@ pub fn derive_params(input: TokenStream) -> TokenStream { quote! { unsafe impl #impl_generics Params for #struct_name #ty_generics #where_clause { - fn param_map( - self: std::pin::Pin<&Self>, - ) -> Vec<(String, nih_plug::prelude::ParamPtr, String)> { + fn param_map(&self) -> Vec<(String, nih_plug::prelude::ParamPtr, String)> { // This may not be in scope otherwise, used to call .as_ptr() use ::nih_plug::param::Param; @@ -231,8 +229,7 @@ pub fn derive_params(input: TokenStream) -> TokenStream { for (nested_params, group_name) in nested_params_fields.into_iter().zip(nested_params_groups) { - let nested_param_map = - unsafe { std::pin::Pin::new_unchecked(*nested_params).param_map() }; + let nested_param_map = nested_params.param_map(); let prefixed_nested_param_map = nested_param_map .into_iter() @@ -260,7 +257,7 @@ pub fn derive_params(input: TokenStream) -> TokenStream { let nested_params_fields: &[&dyn Params] = &[#(&self.#nested_params_field_idents),*]; for nested_params in nested_params_fields { - unsafe { serialized.extend(Pin::new_unchecked(*nested_params).serialize_fields()) }; + serialized.extend(nested_params.serialize_fields()); } serialized @@ -280,7 +277,7 @@ pub fn derive_params(input: TokenStream) -> TokenStream { // once that gets stabilized. let nested_params_fields: &[&dyn Params] = &[#(&self.#nested_params_field_idents),*]; for nested_params in nested_params_fields { - unsafe { Pin::new_unchecked(*nested_params).deserialize_fields(serialized) }; + nested_params.deserialize_fields(serialized); } } } diff --git a/nih_plug_egui/src/widgets/generic_ui.rs b/nih_plug_egui/src/widgets/generic_ui.rs index fc71975f..d9b16cf1 100644 --- a/nih_plug_egui/src/widgets/generic_ui.rs +++ b/nih_plug_egui/src/widgets/generic_ui.rs @@ -1,7 +1,7 @@ //! A simple generic UI widget that renders all parameters in a [`Params`] object as a scrollable //! list of sliders and labels. -use std::pin::Pin; +use std::sync::Arc; use egui::{TextStyle, Ui, Vec2}; use nih_plug::prelude::{Param, ParamFlags, ParamPtr, ParamSetter, Params}; @@ -35,7 +35,7 @@ pub struct GenericSlider; /// space. pub fn create( ui: &mut Ui, - params: Pin<&dyn Params>, + params: Arc, setter: &ParamSetter, widget: impl ParamWidget, ) { diff --git a/nih_plug_iced/src/lib.rs b/nih_plug_iced/src/lib.rs index d6af5dec..0e41f831 100644 --- a/nih_plug_iced/src/lib.rs +++ b/nih_plug_iced/src/lib.rs @@ -12,14 +12,14 @@ //! } //! //! pub(crate) fn create( -//! params: Pin>, +//! params: Arc, //! editor_state: Arc, //! ) -> Option> { //! create_iced_editor::(editor_state, params) //! } //! //! struct FooEditor { -//! params: Pin>, +//! params: Arc, //! context: Arc, //! //! foo_slider_state: nih_widgets::param_slider::State, @@ -34,7 +34,7 @@ //! impl IcedEditor for FooEditor { //! type Executor = executor::Default; //! type Message = Message; -//! type InitializationFlags = Pin>; +//! type InitializationFlags = Arc; //! //! fn new( //! params: Self::InitializationFlags, @@ -141,7 +141,7 @@ pub fn create_iced_editor( /// A plugin editor using `iced`. This wraps around [`Application`] with the only change being that /// the usual `new()` function now additionally takes a `Arc` that the editor can -/// store to interact with the parameters. The editor should have a `Pin>` as part +/// store to interact with the parameters. The editor should have a `Arc` as part /// of their [`InitializationFlags`][Self::InitializationFlags] so it can read the current parameter /// values. See [`Application`] for more information. pub trait IcedEditor: 'static + Send + Sync + Sized { diff --git a/nih_plug_iced/src/widgets/generic_ui.rs b/nih_plug_iced/src/widgets/generic_ui.rs index 36ca057b..8e975a67 100644 --- a/nih_plug_iced/src/widgets/generic_ui.rs +++ b/nih_plug_iced/src/widgets/generic_ui.rs @@ -5,7 +5,7 @@ use atomic_refcell::AtomicRefCell; use std::borrow::Borrow; use std::collections::HashMap; use std::marker::PhantomData; -use std::pin::Pin; +use std::sync::Arc; use nih_plug::prelude::{Param, ParamFlags, ParamPtr, Params}; @@ -58,7 +58,7 @@ pub struct GenericSlider; pub struct GenericUi<'a, W: ParamWidget> { state: &'a mut State, - params: Pin<&'a dyn Params>, + params: Arc, width: Length, height: Length, @@ -85,7 +85,7 @@ where W: ParamWidget, { /// Creates a new [`GenericUi`] for all provided parameters. - pub fn new(state: &'a mut State, params: Pin<&'a dyn Params>) -> Self { + pub fn new(state: &'a mut State, params: Arc) -> Self { Self { state, diff --git a/nih_plug_vizia/src/widgets/generic_ui.rs b/nih_plug_vizia/src/widgets/generic_ui.rs index ebc6a15c..21167acc 100644 --- a/nih_plug_vizia/src/widgets/generic_ui.rs +++ b/nih_plug_vizia/src/widgets/generic_ui.rs @@ -1,6 +1,6 @@ //! Generic UIs for NIH-plug using VIZIA. -use std::{ops::Deref, pin::Pin}; +use std::sync::Arc; use nih_plug::prelude::{ParamFlags, ParamPtr, Params}; use vizia::*; @@ -25,14 +25,12 @@ impl GenericUi { /// }) /// .width(Percentage(100.0)); ///``` - pub fn new(cx: &mut Context, params: L) -> Handle<'_, GenericUi> + pub fn new(cx: &mut Context, params: L) -> Handle<'_, GenericUi> where - L: Lens> + Copy, - PsPtr: 'static + Deref, - Ps: Params, + L: Lens> + Copy, + Ps: Params + 'static, { // Basic styling is done in the `theme.css` style sheet - // TODO: Scrolling Self::new_custom(cx, params, |cx, param_ptr| { HStack::new(cx, |cx| { // Align this on the right @@ -70,15 +68,14 @@ impl GenericUi { /// Creates a new [`GenericUi`] for all provided parameters using a custom closure that receives /// a function that should draw some widget for each parameter. - pub fn new_custom( + pub fn new_custom( cx: &mut Context, params: L, mut make_widget: impl FnMut(&mut Context, ParamPtr), ) -> Handle where - L: Lens> + Copy, - PsPtr: 'static + Deref, - Ps: Params, + L: Lens> + Copy, + Ps: Params + 'static, { // Basic styling is done in the `theme.css` style sheet Self.build2(cx, |cx| { diff --git a/plugins/crisp/src/editor.rs b/plugins/crisp/src/editor.rs index 7aaf26ba..1cfdad3f 100644 --- a/plugins/crisp/src/editor.rs +++ b/plugins/crisp/src/editor.rs @@ -18,7 +18,6 @@ use nih_plug::prelude::Editor; use nih_plug_vizia::vizia::*; use nih_plug_vizia::widgets::*; use nih_plug_vizia::{assets, create_vizia_editor, ViziaState}; -use std::pin::Pin; use std::sync::Arc; use crate::CrispParams; @@ -28,7 +27,7 @@ const POINT_SCALE: f32 = 0.75; #[derive(Lens)] struct Data { - params: Pin>, + params: Arc, } impl Model for Data {} @@ -39,7 +38,7 @@ pub(crate) fn default_state() -> Arc { } pub(crate) fn create( - params: Pin>, + params: Arc, editor_state: Arc, ) -> Option> { create_vizia_editor(editor_state, move |cx| { diff --git a/plugins/crisp/src/lib.rs b/plugins/crisp/src/lib.rs index 18774d7d..07574a2d 100644 --- a/plugins/crisp/src/lib.rs +++ b/plugins/crisp/src/lib.rs @@ -20,7 +20,6 @@ extern crate nih_plug; use nih_plug::prelude::*; use nih_plug_vizia::ViziaState; use pcg::Pcg32iState; -use std::pin::Pin; use std::sync::Arc; mod editor; @@ -44,7 +43,7 @@ const MAX_FILTER_FREQUENCY: f32 = 22_000.0; /// white (or filtered) noise. That other copy of the sound may have a low-pass filter applied to it /// since this effect just turns into literal noise at high frequencies. struct Crisp { - params: Pin>, + params: Arc, editor_state: Arc, /// Needed for computing the filter coefficients. @@ -126,7 +125,7 @@ enum StereoMode { impl Default for Crisp { fn default() -> Self { Self { - params: Arc::pin(CrispParams::default()), + params: Arc::new(CrispParams::default()), editor_state: editor::default_state(), sample_rate: 1.0, @@ -308,8 +307,8 @@ impl Plugin for Crisp { const SAMPLE_ACCURATE_AUTOMATION: bool = true; - fn params(&self) -> Pin<&dyn Params> { - self.params.as_ref() + fn params(&self) -> Arc { + self.params.clone() } fn editor(&self) -> Option> { diff --git a/plugins/diopser/src/editor.rs b/plugins/diopser/src/editor.rs index 6829343b..7ec2430d 100644 --- a/plugins/diopser/src/editor.rs +++ b/plugins/diopser/src/editor.rs @@ -18,7 +18,6 @@ use nih_plug::prelude::Editor; use nih_plug_vizia::vizia::*; use nih_plug_vizia::widgets::*; use nih_plug_vizia::{assets, create_vizia_editor, ViziaState}; -use std::pin::Pin; use std::sync::Arc; use crate::DiopserParams; @@ -28,7 +27,7 @@ const POINT_SCALE: f32 = 0.75; #[derive(Lens)] struct Data { - params: Pin>, + params: Arc, } impl Model for Data {} @@ -39,7 +38,7 @@ pub(crate) fn default_state() -> Arc { } pub(crate) fn create( - params: Pin>, + params: Arc, editor_state: Arc, ) -> Option> { create_vizia_editor(editor_state, move |cx| { diff --git a/plugins/diopser/src/lib.rs b/plugins/diopser/src/lib.rs index 804ea310..5c4243b9 100644 --- a/plugins/diopser/src/lib.rs +++ b/plugins/diopser/src/lib.rs @@ -24,7 +24,6 @@ extern crate nih_plug; use nih_plug::prelude::*; use nih_plug_vizia::ViziaState; -use std::pin::Pin; use std::simd::f32x2; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -49,7 +48,7 @@ const MAX_AUTOMATION_STEP_SIZE: u32 = 512; // - Briefly muting the output when changing the number of filters to get rid of the clicks // - A proper GUI struct Diopser { - params: Pin>, + params: Arc, editor_state: Arc, /// Needed for computing the filter coefficients. @@ -125,7 +124,7 @@ impl Default for Diopser { SpectrumInput::new(Self::DEFAULT_NUM_OUTPUTS as usize); Self { - params: Arc::pin(DiopserParams::new(should_update_filters.clone())), + params: Arc::new(DiopserParams::new(should_update_filters.clone())), editor_state: editor::default_state(), sample_rate: 1.0, @@ -240,8 +239,8 @@ impl Plugin for Diopser { const SAMPLE_ACCURATE_AUTOMATION: bool = true; - fn params(&self) -> Pin<&dyn Params> { - self.params.as_ref() + fn params(&self) -> Arc { + self.params.clone() } fn editor(&self) -> Option> { diff --git a/plugins/examples/gain-gui-egui/src/lib.rs b/plugins/examples/gain-gui-egui/src/lib.rs index d03faef7..780b6341 100644 --- a/plugins/examples/gain-gui-egui/src/lib.rs +++ b/plugins/examples/gain-gui-egui/src/lib.rs @@ -1,12 +1,11 @@ use atomic_float::AtomicF32; use nih_plug::prelude::*; use nih_plug_egui::{create_egui_editor, egui, widgets, EguiState}; -use std::pin::Pin; use std::sync::Arc; /// This is mostly identical to the gain example, minus some fluff, and with a GUI. struct Gain { - params: Pin>, + params: Arc, editor_state: Arc, /// Needed to normalize the peak meter's response based on the sample rate. @@ -32,7 +31,7 @@ struct GainParams { impl Default for Gain { fn default() -> Self { Self { - params: Arc::pin(GainParams::default()), + params: Arc::new(GainParams::default()), editor_state: EguiState::from_size(300, 180), peak_meter_decay_weight: 1.0, @@ -74,8 +73,8 @@ impl Plugin for Gain { const ACCEPTS_MIDI: bool = false; const SAMPLE_ACCURATE_AUTOMATION: bool = true; - fn params(&self) -> Pin<&dyn Params> { - self.params.as_ref() + fn params(&self) -> Arc { + self.params.clone() } fn editor(&self) -> Option> { diff --git a/plugins/examples/gain-gui-iced/src/editor.rs b/plugins/examples/gain-gui-iced/src/editor.rs index 2fb9d4ff..db2c31b7 100644 --- a/plugins/examples/gain-gui-iced/src/editor.rs +++ b/plugins/examples/gain-gui-iced/src/editor.rs @@ -2,7 +2,6 @@ use atomic_float::AtomicF32; use nih_plug::prelude::{util, Editor, GuiContext}; use nih_plug_iced::widgets as nih_widgets; use nih_plug_iced::*; -use std::pin::Pin; use std::sync::Arc; use std::time::Duration; @@ -14,7 +13,7 @@ pub(crate) fn default_state() -> Arc { } pub(crate) fn create( - params: Pin>, + params: Arc, peak_meter: Arc, editor_state: Arc, ) -> Option> { @@ -22,7 +21,7 @@ pub(crate) fn create( } struct GainEditor { - params: Pin>, + params: Arc, context: Arc, peak_meter: Arc, @@ -40,7 +39,7 @@ enum Message { impl IcedEditor for GainEditor { type Executor = executor::Default; type Message = Message; - type InitializationFlags = (Pin>, Arc); + type InitializationFlags = (Arc, Arc); fn new( (params, peak_meter): Self::InitializationFlags, diff --git a/plugins/examples/gain-gui-iced/src/lib.rs b/plugins/examples/gain-gui-iced/src/lib.rs index 231ef05c..4ae8b473 100644 --- a/plugins/examples/gain-gui-iced/src/lib.rs +++ b/plugins/examples/gain-gui-iced/src/lib.rs @@ -1,14 +1,13 @@ use atomic_float::AtomicF32; use nih_plug::prelude::*; use nih_plug_iced::IcedState; -use std::pin::Pin; use std::sync::Arc; mod editor; /// This is mostly identical to the gain example, minus some fluff, and with a GUI. struct Gain { - params: Pin>, + params: Arc, editor_state: Arc, /// Needed to normalize the peak meter's response based on the sample rate. @@ -30,7 +29,7 @@ struct GainParams { impl Default for Gain { fn default() -> Self { Self { - params: Arc::pin(GainParams::default()), + params: Arc::new(GainParams::default()), editor_state: editor::default_state(), peak_meter_decay_weight: 1.0, @@ -71,8 +70,8 @@ impl Plugin for Gain { const ACCEPTS_MIDI: bool = false; const SAMPLE_ACCURATE_AUTOMATION: bool = true; - fn params(&self) -> Pin<&dyn Params> { - self.params.as_ref() + fn params(&self) -> Arc { + self.params.clone() } fn editor(&self) -> Option> { diff --git a/plugins/examples/gain-gui-vizia/src/editor.rs b/plugins/examples/gain-gui-vizia/src/editor.rs index a2c76c5a..eace12ff 100644 --- a/plugins/examples/gain-gui-vizia/src/editor.rs +++ b/plugins/examples/gain-gui-vizia/src/editor.rs @@ -3,7 +3,6 @@ use nih_plug::prelude::{util, Editor}; use nih_plug_vizia::vizia::*; use nih_plug_vizia::widgets::*; use nih_plug_vizia::{assets, create_vizia_editor, ViziaState}; -use std::pin::Pin; use std::sync::atomic::Ordering; use std::sync::Arc; use std::time::Duration; @@ -17,7 +16,7 @@ const STYLE: &str = r#""#; #[derive(Lens)] struct Data { - params: Pin>, + params: Arc, peak_meter: Arc, } @@ -29,7 +28,7 @@ pub(crate) fn default_state() -> Arc { } pub(crate) fn create( - params: Pin>, + params: Arc, peak_meter: Arc, editor_state: Arc, ) -> Option> { diff --git a/plugins/examples/gain-gui-vizia/src/lib.rs b/plugins/examples/gain-gui-vizia/src/lib.rs index ed2f3a10..a3249ed3 100644 --- a/plugins/examples/gain-gui-vizia/src/lib.rs +++ b/plugins/examples/gain-gui-vizia/src/lib.rs @@ -1,14 +1,13 @@ use atomic_float::AtomicF32; use nih_plug::prelude::*; use nih_plug_vizia::ViziaState; -use std::pin::Pin; use std::sync::Arc; mod editor; /// This is mostly identical to the gain example, minus some fluff, and with a GUI. struct Gain { - params: Pin>, + params: Arc, editor_state: Arc, /// Needed to normalize the peak meter's response based on the sample rate. @@ -30,7 +29,7 @@ struct GainParams { impl Default for Gain { fn default() -> Self { Self { - params: Arc::pin(GainParams::default()), + params: Arc::new(GainParams::default()), editor_state: editor::default_state(), peak_meter_decay_weight: 1.0, @@ -71,8 +70,8 @@ impl Plugin for Gain { const ACCEPTS_MIDI: bool = false; const SAMPLE_ACCURATE_AUTOMATION: bool = true; - fn params(&self) -> Pin<&dyn Params> { - self.params.as_ref() + fn params(&self) -> Arc { + self.params.clone() } fn editor(&self) -> Option> { diff --git a/plugins/examples/gain/src/lib.rs b/plugins/examples/gain/src/lib.rs index 6d77cc83..7daedaa3 100644 --- a/plugins/examples/gain/src/lib.rs +++ b/plugins/examples/gain/src/lib.rs @@ -1,9 +1,9 @@ use nih_plug::prelude::*; use parking_lot::RwLock; -use std::pin::Pin; +use std::sync::Arc; struct Gain { - params: Pin>, + params: Arc, } /// The [`Params`] derive macro gathers all of the information needed for the wrapepr to know about @@ -48,7 +48,7 @@ struct SubSubParams { impl Default for Gain { fn default() -> Self { Self { - params: Box::pin(GainParams::default()), + params: Arc::new(GainParams::default()), } } } @@ -111,8 +111,8 @@ impl Plugin for Gain { // splits. const SAMPLE_ACCURATE_AUTOMATION: bool = true; - fn params(&self) -> Pin<&dyn Params> { - self.params.as_ref() + fn params(&self) -> Arc { + self.params.clone() } fn accepts_bus_config(&self, config: &BusConfig) -> bool { diff --git a/plugins/examples/sine/src/lib.rs b/plugins/examples/sine/src/lib.rs index 1a51b5a9..cd62da8c 100644 --- a/plugins/examples/sine/src/lib.rs +++ b/plugins/examples/sine/src/lib.rs @@ -1,11 +1,11 @@ use nih_plug::prelude::*; use std::f32::consts; -use std::pin::Pin; +use std::sync::Arc; /// A test tone generator that can either generate a sine wave based on the plugin's parameters or /// based on the current MIDI input. struct Sine { - params: Pin>, + params: Arc, sample_rate: f32, /// The current phase of the sine wave, always kept between in `[0, 1]`. @@ -35,7 +35,7 @@ struct SineParams { impl Default for Sine { fn default() -> Self { Self { - params: Box::pin(SineParams::default()), + params: Arc::new(SineParams::default()), sample_rate: 1.0, phase: 0.0, @@ -106,8 +106,8 @@ impl Plugin for Sine { const ACCEPTS_MIDI: bool = true; const SAMPLE_ACCURATE_AUTOMATION: bool = true; - fn params(&self) -> Pin<&dyn Params> { - self.params.as_ref() + fn params(&self) -> Arc { + self.params.clone() } fn accepts_bus_config(&self, config: &BusConfig) -> bool { diff --git a/plugins/examples/stft/src/lib.rs b/plugins/examples/stft/src/lib.rs index 20caf43b..8f2e2417 100644 --- a/plugins/examples/stft/src/lib.rs +++ b/plugins/examples/stft/src/lib.rs @@ -2,14 +2,13 @@ use nih_plug::prelude::*; use realfft::num_complex::Complex32; use realfft::{ComplexToReal, RealFftPlanner, RealToComplex}; use std::f32; -use std::pin::Pin; use std::sync::Arc; const WINDOW_SIZE: usize = 2048; const OVERLAP_TIMES: usize = 4; struct Stft { - params: Pin>, + params: Arc, /// An adapter that performs most of the overlap-add algorithm for us. stft: util::StftHelper, @@ -56,7 +55,7 @@ impl Default for Stft { .unwrap(); Self { - params: Box::pin(StftParams::default()), + params: Arc::new(StftParams::default()), stft: util::StftHelper::new(2, WINDOW_SIZE), window_function: util::window::hann(WINDOW_SIZE), @@ -91,8 +90,8 @@ impl Plugin for Stft { const ACCEPTS_MIDI: bool = false; const SAMPLE_ACCURATE_AUTOMATION: bool = true; - fn params(&self) -> Pin<&dyn Params> { - self.params.as_ref() + fn params(&self) -> Arc { + self.params.clone() } fn accepts_bus_config(&self, config: &BusConfig) -> bool { diff --git a/plugins/puberty_simulator/src/lib.rs b/plugins/puberty_simulator/src/lib.rs index 49a102c2..acff5fe0 100644 --- a/plugins/puberty_simulator/src/lib.rs +++ b/plugins/puberty_simulator/src/lib.rs @@ -18,7 +18,6 @@ use nih_plug::prelude::*; use realfft::num_complex::Complex32; use realfft::{ComplexToReal, RealFftPlanner, RealToComplex}; use std::f32; -use std::pin::Pin; use std::sync::Arc; const MIN_WINDOW_ORDER: usize = 6; @@ -41,7 +40,7 @@ const MAX_OVERLAP_ORDER: usize = 5; const MAX_OVERLAP_TIMES: usize = 1 << MAX_OVERLAP_ORDER; // 32 struct PubertySimulator { - params: Pin>, + params: Arc, /// An adapter that performs most of the overlap-add algorithm for us. stft: util::StftHelper, @@ -82,7 +81,7 @@ struct PubertySimulatorParams { impl Default for PubertySimulator { fn default() -> Self { Self { - params: Box::pin(PubertySimulatorParams::default()), + params: Arc::new(PubertySimulatorParams::default()), stft: util::StftHelper::new(2, MAX_WINDOW_SIZE), window_function: Vec::with_capacity(MAX_WINDOW_SIZE), @@ -150,8 +149,8 @@ impl Plugin for PubertySimulator { const DEFAULT_NUM_INPUTS: u32 = 2; const DEFAULT_NUM_OUTPUTS: u32 = 2; - fn params(&self) -> Pin<&dyn Params> { - self.params.as_ref() + fn params(&self) -> Arc { + self.params.clone() } fn accepts_bus_config(&self, config: &BusConfig) -> bool { diff --git a/src/param/internals.rs b/src/param/internals.rs index eca7b9df..bcd84fde 100644 --- a/src/param/internals.rs +++ b/src/param/internals.rs @@ -1,7 +1,6 @@ //! Implementation details for the parameter management. use std::collections::HashMap; -use std::pin::Pin; use super::{Param, ParamFlags}; @@ -37,9 +36,9 @@ pub use serde_json::to_string as serialize_field; /// /// # Safety /// -/// This implementation is safe when using from the wrapper because the plugin object needs to be -/// pinned, and it can never outlive the wrapper. -pub unsafe trait Params { +/// This implementation is safe when using from the wrapper because the plugin's returned `Params` +/// object lives in an `Arc`, and the wrapper also holds a reference to this `Arc`. +pub unsafe trait Params: 'static + Send + Sync { /// Create a mapping from unique parameter IDs to parameters along with the name of the /// group/unit/module they are in. The order of the `Vec` determines the display order in the /// (host's) generic UI. The group name is either an empty string for top level parameters, or a @@ -48,13 +47,13 @@ pub unsafe trait Params { /// for every parameter field marked with `#[id = "stable"]`, and it also inlines all fields /// from child `Params` structs marked with `#[nested = "Group Name"]`, prefixing that group /// name before the parameter's originanl group name. Dereferencing the pointers stored in the - /// values is only valid as long as this pinned object is valid. + /// values is only valid as long as this object is valid. /// /// # Note /// /// This uses `String` even though for the `Params` derive macro `&'static str` would have been /// fine to be able to support custom reusable Params implemnetations. - fn param_map(self: Pin<&Self>) -> Vec<(String, ParamPtr, String)>; + fn param_map(&self) -> Vec<(String, ParamPtr, String)>; /// Serialize all fields marked with `#[persist = "stable_name"]` into a hash map containing /// JSON-representations of those fields so they can be written to the plugin's state and @@ -84,8 +83,9 @@ pub enum ParamPtr { EnumParam(*mut super::enums::EnumParamInner), } -// These pointers only point to fields on pinned structs, and the caller always needs to make sure -// that dereferencing them is safe +// These pointers only point to fields on structs kept in an `Arc`, and the caller +// always needs to make sure that dereferencing them is safe. To do that the plugin wrappers will +// keep references to that `Arc` around for the entire lifetime of the plugin. unsafe impl Send for ParamPtr {} unsafe impl Sync for ParamPtr {} diff --git a/src/plugin.rs b/src/plugin.rs index bd7b36e8..e339080b 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -2,7 +2,6 @@ use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; use std::any::Any; -use std::pin::Pin; use std::sync::Arc; use crate::buffer::Buffer; @@ -55,7 +54,7 @@ pub trait Plugin: Default + Send + Sync + 'static { /// The plugin's parameters. The host will update the parameter values before calling /// `process()`. These parameters are identified by strings that should never change when the /// plugin receives an update. - fn params(&self) -> Pin<&dyn Params>; + fn params(&self) -> Arc; /// The plugin's editor, if it has one. The actual editor instance is created in /// [`Editor::spawn()`]. A plugin editor likely wants to interact with the plugin's parameters diff --git a/src/wrapper/clap/wrapper.rs b/src/wrapper/clap/wrapper.rs index 46935a63..8fcc8a39 100644 --- a/src/wrapper/clap/wrapper.rs +++ b/src/wrapper/clap/wrapper.rs @@ -67,7 +67,7 @@ use super::util::ClapPtr; use crate::buffer::Buffer; use crate::context::Transport; use crate::event_loop::{EventLoop, MainThreadExecutor, TASK_QUEUE_CAPACITY}; -use crate::param::internals::ParamPtr; +use crate::param::internals::{ParamPtr, Params}; use crate::param::ParamFlags; use crate::plugin::{ BufferConfig, BusConfig, ClapPlugin, Editor, NoteEvent, ParentWindowHandle, ProcessStatus, @@ -90,6 +90,10 @@ pub struct Wrapper { /// The wrapped plugin instance. plugin: RwLock

, + /// The plugin's parameters. These are fetched once during initialization. That way the + /// `ParamPtr`s are guaranteed to live at least as long as this object and we can interact with + /// the `Params` object without having to acquire a lock on `plugin`. + params: Arc, /// 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. @@ -162,8 +166,8 @@ pub struct Wrapper { /// The keys from `param_map` in a stable order. param_hashes: Vec, /// A mapping from parameter ID hashes (obtained from the string parameter IDs) to pointers to - /// parameters belonging to the plugin. As long as `plugin` does not get recreated, these - /// addresses will remain stable, as they are obtained from a pinned object. + /// parameters belonging to the plugin. These addresses will remain stable as long as the + /// `params` object does not get deallocated. param_by_hash: HashMap, /// The group name of a parameter, indexed by the parameter's hash. Nested groups are delimited /// by slashes, and they're only used to allow the DAW to display parameters in a tree @@ -318,9 +322,8 @@ impl Wrapper

{ // `wrapper.plugin` is alive. The plugin API identifiers these parameters by hashes, which // we'll calculate from the string ID specified by the plugin. These parameters should also // remain in the same order as the one returned by the plugin. - let param_id_hashes_ptrs_groups: Vec<_> = plugin - .read() - .params() + let params = plugin.read().params(); + let param_id_hashes_ptrs_groups: Vec<_> = params .param_map() .into_iter() .map(|(id, ptr, group)| { @@ -329,7 +332,7 @@ impl Wrapper

{ }) .collect(); if cfg!(debug_assertions) { - let param_map = plugin.read().params().param_map(); + let param_map = params.param_map(); let param_ids: HashSet<_> = param_id_hashes_ptrs_groups .iter() .map(|(id, _, _, _)| id.clone()) @@ -430,6 +433,7 @@ impl Wrapper

{ this: AtomicRefCell::new(Weak::new()), plugin, + params, editor, editor_handle: RwLock::new(None), editor_scaling_factor: AtomicF32::new(1.0), @@ -1916,7 +1920,7 @@ impl Wrapper

{ let wrapper = &*(plugin as *const Self); let serialized = state::serialize( - wrapper.plugin.read().params(), + wrapper.params.clone(), &wrapper.param_by_hash, &wrapper.param_id_to_hash, ); @@ -1976,7 +1980,7 @@ impl Wrapper

{ let success = state::deserialize( &read_buffer, - wrapper.plugin.read().params(), + wrapper.params.clone(), &wrapper.param_by_hash, &wrapper.param_id_to_hash, wrapper.current_buffer_config.load().as_ref(), diff --git a/src/wrapper/state.rs b/src/wrapper/state.rs index b686a465..fc142e6b 100644 --- a/src/wrapper/state.rs +++ b/src/wrapper/state.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::pin::Pin; +use std::sync::Arc; use crate::param::internals::{ParamPtr, Params}; use crate::param::Param; @@ -41,7 +41,7 @@ pub struct State { /// Serialize a plugin's state to a vector containing JSON data. This can (and should) be shared /// across plugin formats. pub(crate) unsafe fn serialize( - plugin_params: Pin<&dyn Params>, + plugin_params: Arc, param_by_hash: &HashMap, param_id_to_hash: &HashMap, ) -> serde_json::Result> { @@ -89,7 +89,7 @@ pub(crate) unsafe fn serialize( /// parameter values. The smoothers have already been reset by this function. pub(crate) unsafe fn deserialize( state: &[u8], - plugin_params: Pin<&dyn Params>, + plugin_params: Arc, param_by_hash: &HashMap, param_id_to_hash: &HashMap, current_buffer_config: Option<&BufferConfig>, diff --git a/src/wrapper/vst3/inner.rs b/src/wrapper/vst3/inner.rs index 13d20a84..c47af570 100644 --- a/src/wrapper/vst3/inner.rs +++ b/src/wrapper/vst3/inner.rs @@ -16,7 +16,7 @@ use super::view::WrapperView; use crate::buffer::Buffer; use crate::context::Transport; use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop}; -use crate::param::internals::ParamPtr; +use crate::param::internals::{ParamPtr, Params}; use crate::param::ParamFlags; use crate::plugin::{BufferConfig, BusConfig, Editor, NoteEvent, ProcessStatus, Vst3Plugin}; use crate::wrapper::util::hash_param_id; @@ -27,6 +27,10 @@ use crate::wrapper::util::hash_param_id; pub(crate) struct WrapperInner { /// The wrapped plugin instance. pub plugin: RwLock

, + /// The plugin's parameters. These are fetched once during initialization. That way the + /// `ParamPtr`s are guaranteed to live at least as long as this object and we can interact with + /// the `Params` object without having to acquire a lock on `plugin`. + pub params: Arc, /// 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. @@ -80,8 +84,8 @@ pub(crate) struct WrapperInner { /// The keys from `param_map` in a stable order. pub param_hashes: Vec, /// A mapping from parameter ID hashes (obtained from the string parameter IDs) to pointers to - /// parameters belonging to the plugin. As long as `plugin` does not get recreated, these - /// addresses will remain stable, as they are obtained from a pinned object. + /// parameters belonging to the plugin. These addresses will remain stable as long as the + /// `params` object does not get deallocated. pub param_by_hash: HashMap, pub param_units: ParamUnits, /// Mappings from string parameter indentifiers to parameter hashes. Useful for debug logging @@ -135,9 +139,8 @@ impl WrapperInner

{ // `wrapper.plugin` is alive. The plugin API identifiers these parameters by hashes, which // we'll calculate from the string ID specified by the plugin. These parameters should also // remain in the same order as the one returned by the plugin. - let param_id_hashes_ptrs_groups: Vec<_> = plugin - .read() - .params() + let params = plugin.read().params(); + let param_id_hashes_ptrs_groups: Vec<_> = params .param_map() .into_iter() .map(|(id, ptr, group)| { @@ -146,7 +149,7 @@ impl WrapperInner

{ }) .collect(); if cfg!(debug_assertions) { - let param_map = plugin.read().params().param_map(); + let param_map = params.param_map(); let param_ids: HashSet<_> = param_id_hashes_ptrs_groups .iter() .map(|(id, _, _, _)| id.clone()) @@ -199,6 +202,7 @@ impl WrapperInner

{ let wrapper = Self { plugin, + params, editor, component_handler: AtomicRefCell::new(None), diff --git a/src/wrapper/vst3/wrapper.rs b/src/wrapper/vst3/wrapper.rs index 8f5335ad..b63727e7 100644 --- a/src/wrapper/vst3/wrapper.rs +++ b/src/wrapper/vst3/wrapper.rs @@ -222,7 +222,7 @@ impl IComponent for Wrapper

{ let success = state::deserialize( &read_buffer, - self.inner.plugin.read().params(), + self.inner.params.clone(), &self.inner.param_by_hash, &self.inner.param_id_to_hash, self.inner.current_buffer_config.load().as_ref(), @@ -256,7 +256,7 @@ impl IComponent for Wrapper

{ let state = state.upgrade().unwrap(); let serialized = state::serialize( - self.inner.plugin.read().params(), + self.inner.params.clone(), &self.inner.param_by_hash, &self.inner.param_id_to_hash, );