1
0
Fork 0

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.
This commit is contained in:
Robbert van der Helm 2022-04-07 15:31:46 +02:00
parent 7cc05fce9a
commit 083885a40c
24 changed files with 105 additions and 115 deletions

View file

@ -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);
}
}
}

View file

@ -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<impl Params>,
setter: &ParamSetter,
widget: impl ParamWidget,
) {

View file

@ -12,14 +12,14 @@
//! }
//!
//! pub(crate) fn create(
//! params: Pin<Arc<FooParams>>,
//! params: Arc<FooParams>,
//! editor_state: Arc<IcedState>,
//! ) -> Option<Box<dyn Editor>> {
//! create_iced_editor::<Foo>(editor_state, params)
//! }
//!
//! struct FooEditor {
//! params: Pin<Arc<FooParams>>,
//! params: Arc<FooParams>,
//! context: Arc<dyn GuiContext>,
//!
//! 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<Arc<FooParams>>;
//! type InitializationFlags = Arc<FooParams>;
//!
//! fn new(
//! params: Self::InitializationFlags,
@ -141,7 +141,7 @@ pub fn create_iced_editor<E: IcedEditor>(
/// A plugin editor using `iced`. This wraps around [`Application`] with the only change being that
/// the usual `new()` function now additionally takes a `Arc<dyn GuiContext>` that the editor can
/// store to interact with the parameters. The editor should have a `Pin<Arc<impl Params>>` as part
/// store to interact with the parameters. The editor should have a `Arc<impl Params>` 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 {

View file

@ -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<W>,
params: Pin<&'a dyn Params>,
params: Arc<dyn Params>,
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<W>, params: Pin<&'a dyn Params>) -> Self {
pub fn new(state: &'a mut State<W>, params: Arc<dyn Params>) -> Self {
Self {
state,

View file

@ -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<L, PsPtr, Ps>(cx: &mut Context, params: L) -> Handle<'_, GenericUi>
pub fn new<L, Ps>(cx: &mut Context, params: L) -> Handle<'_, GenericUi>
where
L: Lens<Target = Pin<PsPtr>> + Copy,
PsPtr: 'static + Deref<Target = Ps>,
Ps: Params,
L: Lens<Target = Arc<Ps>> + 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<L, PsPtr, Ps>(
pub fn new_custom<L, Ps>(
cx: &mut Context,
params: L,
mut make_widget: impl FnMut(&mut Context, ParamPtr),
) -> Handle<Self>
where
L: Lens<Target = Pin<PsPtr>> + Copy,
PsPtr: 'static + Deref<Target = Ps>,
Ps: Params,
L: Lens<Target = Arc<Ps>> + Copy,
Ps: Params + 'static,
{
// Basic styling is done in the `theme.css` style sheet
Self.build2(cx, |cx| {

View file

@ -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<Arc<CrispParams>>,
params: Arc<CrispParams>,
}
impl Model for Data {}
@ -39,7 +38,7 @@ pub(crate) fn default_state() -> Arc<ViziaState> {
}
pub(crate) fn create(
params: Pin<Arc<CrispParams>>,
params: Arc<CrispParams>,
editor_state: Arc<ViziaState>,
) -> Option<Box<dyn Editor>> {
create_vizia_editor(editor_state, move |cx| {

View file

@ -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<Arc<CrispParams>>,
params: Arc<CrispParams>,
editor_state: Arc<ViziaState>,
/// 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<dyn Params> {
self.params.clone()
}
fn editor(&self) -> Option<Box<dyn Editor>> {

View file

@ -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<Arc<DiopserParams>>,
params: Arc<DiopserParams>,
}
impl Model for Data {}
@ -39,7 +38,7 @@ pub(crate) fn default_state() -> Arc<ViziaState> {
}
pub(crate) fn create(
params: Pin<Arc<DiopserParams>>,
params: Arc<DiopserParams>,
editor_state: Arc<ViziaState>,
) -> Option<Box<dyn Editor>> {
create_vizia_editor(editor_state, move |cx| {

View file

@ -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<Arc<DiopserParams>>,
params: Arc<DiopserParams>,
editor_state: Arc<ViziaState>,
/// 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<dyn Params> {
self.params.clone()
}
fn editor(&self) -> Option<Box<dyn Editor>> {

View file

@ -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<Arc<GainParams>>,
params: Arc<GainParams>,
editor_state: Arc<EguiState>,
/// 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<dyn Params> {
self.params.clone()
}
fn editor(&self) -> Option<Box<dyn Editor>> {

View file

@ -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<IcedState> {
}
pub(crate) fn create(
params: Pin<Arc<GainParams>>,
params: Arc<GainParams>,
peak_meter: Arc<AtomicF32>,
editor_state: Arc<IcedState>,
) -> Option<Box<dyn Editor>> {
@ -22,7 +21,7 @@ pub(crate) fn create(
}
struct GainEditor {
params: Pin<Arc<GainParams>>,
params: Arc<GainParams>,
context: Arc<dyn GuiContext>,
peak_meter: Arc<AtomicF32>,
@ -40,7 +39,7 @@ enum Message {
impl IcedEditor for GainEditor {
type Executor = executor::Default;
type Message = Message;
type InitializationFlags = (Pin<Arc<GainParams>>, Arc<AtomicF32>);
type InitializationFlags = (Arc<GainParams>, Arc<AtomicF32>);
fn new(
(params, peak_meter): Self::InitializationFlags,

View file

@ -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<Arc<GainParams>>,
params: Arc<GainParams>,
editor_state: Arc<IcedState>,
/// 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<dyn Params> {
self.params.clone()
}
fn editor(&self) -> Option<Box<dyn Editor>> {

View file

@ -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<Arc<GainParams>>,
params: Arc<GainParams>,
peak_meter: Arc<AtomicF32>,
}
@ -29,7 +28,7 @@ pub(crate) fn default_state() -> Arc<ViziaState> {
}
pub(crate) fn create(
params: Pin<Arc<GainParams>>,
params: Arc<GainParams>,
peak_meter: Arc<AtomicF32>,
editor_state: Arc<ViziaState>,
) -> Option<Box<dyn Editor>> {

View file

@ -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<Arc<GainParams>>,
params: Arc<GainParams>,
editor_state: Arc<ViziaState>,
/// 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<dyn Params> {
self.params.clone()
}
fn editor(&self) -> Option<Box<dyn Editor>> {

View file

@ -1,9 +1,9 @@
use nih_plug::prelude::*;
use parking_lot::RwLock;
use std::pin::Pin;
use std::sync::Arc;
struct Gain {
params: Pin<Box<GainParams>>,
params: Arc<GainParams>,
}
/// 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<dyn Params> {
self.params.clone()
}
fn accepts_bus_config(&self, config: &BusConfig) -> bool {

View file

@ -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<Box<SineParams>>,
params: Arc<SineParams>,
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<dyn Params> {
self.params.clone()
}
fn accepts_bus_config(&self, config: &BusConfig) -> bool {

View file

@ -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<Box<StftParams>>,
params: Arc<StftParams>,
/// 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<dyn Params> {
self.params.clone()
}
fn accepts_bus_config(&self, config: &BusConfig) -> bool {

View file

@ -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<Box<PubertySimulatorParams>>,
params: Arc<PubertySimulatorParams>,
/// 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<dyn Params> {
self.params.clone()
}
fn accepts_bus_config(&self, config: &BusConfig) -> bool {

View file

@ -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<dyn Params>`, 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 {}

View file

@ -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<dyn Params>;
/// 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

View file

@ -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<P: ClapPlugin> {
/// The wrapped plugin instance.
plugin: RwLock<P>,
/// 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<dyn Params>,
/// 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<P: ClapPlugin> {
/// The keys from `param_map` in a stable order.
param_hashes: Vec<u32>,
/// 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<u32, ParamPtr>,
/// 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<P: ClapPlugin> Wrapper<P> {
// `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<P: ClapPlugin> Wrapper<P> {
})
.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<P: ClapPlugin> Wrapper<P> {
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<P: ClapPlugin> Wrapper<P> {
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<P: ClapPlugin> Wrapper<P> {
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(),

View file

@ -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<dyn Params>,
param_by_hash: &HashMap<u32, ParamPtr>,
param_id_to_hash: &HashMap<String, u32>,
) -> serde_json::Result<Vec<u8>> {
@ -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<dyn Params>,
param_by_hash: &HashMap<u32, ParamPtr>,
param_id_to_hash: &HashMap<String, u32>,
current_buffer_config: Option<&BufferConfig>,

View file

@ -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<P: Vst3Plugin> {
/// The wrapped plugin instance.
pub plugin: RwLock<P>,
/// 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<dyn Params>,
/// 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<P: Vst3Plugin> {
/// The keys from `param_map` in a stable order.
pub param_hashes: Vec<u32>,
/// 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<u32, ParamPtr>,
pub param_units: ParamUnits,
/// Mappings from string parameter indentifiers to parameter hashes. Useful for debug logging
@ -135,9 +139,8 @@ impl<P: Vst3Plugin> WrapperInner<P> {
// `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<P: Vst3Plugin> WrapperInner<P> {
})
.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<P: Vst3Plugin> WrapperInner<P> {
let wrapper = Self {
plugin,
params,
editor,
component_handler: AtomicRefCell::new(None),

View file

@ -222,7 +222,7 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
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<P: Vst3Plugin> IComponent for Wrapper<P> {
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,
);