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! { quote! {
unsafe impl #impl_generics Params for #struct_name #ty_generics #where_clause { unsafe impl #impl_generics Params for #struct_name #ty_generics #where_clause {
fn param_map( fn param_map(&self) -> Vec<(String, nih_plug::prelude::ParamPtr, String)> {
self: std::pin::Pin<&Self>,
) -> Vec<(String, nih_plug::prelude::ParamPtr, String)> {
// This may not be in scope otherwise, used to call .as_ptr() // This may not be in scope otherwise, used to call .as_ptr()
use ::nih_plug::param::Param; use ::nih_plug::param::Param;
@ -231,8 +229,7 @@ pub fn derive_params(input: TokenStream) -> TokenStream {
for (nested_params, group_name) in for (nested_params, group_name) in
nested_params_fields.into_iter().zip(nested_params_groups) nested_params_fields.into_iter().zip(nested_params_groups)
{ {
let nested_param_map = let nested_param_map = nested_params.param_map();
unsafe { std::pin::Pin::new_unchecked(*nested_params).param_map() };
let prefixed_nested_param_map = let prefixed_nested_param_map =
nested_param_map nested_param_map
.into_iter() .into_iter()
@ -260,7 +257,7 @@ pub fn derive_params(input: TokenStream) -> TokenStream {
let nested_params_fields: &[&dyn Params] = &[#(&self.#nested_params_field_idents),*]; let nested_params_fields: &[&dyn Params] = &[#(&self.#nested_params_field_idents),*];
for nested_params in nested_params_fields { for nested_params in nested_params_fields {
unsafe { serialized.extend(Pin::new_unchecked(*nested_params).serialize_fields()) }; serialized.extend(nested_params.serialize_fields());
} }
serialized serialized
@ -280,7 +277,7 @@ pub fn derive_params(input: TokenStream) -> TokenStream {
// once that gets stabilized. // once that gets stabilized.
let nested_params_fields: &[&dyn Params] = &[#(&self.#nested_params_field_idents),*]; let nested_params_fields: &[&dyn Params] = &[#(&self.#nested_params_field_idents),*];
for nested_params in nested_params_fields { 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 //! A simple generic UI widget that renders all parameters in a [`Params`] object as a scrollable
//! list of sliders and labels. //! list of sliders and labels.
use std::pin::Pin; use std::sync::Arc;
use egui::{TextStyle, Ui, Vec2}; use egui::{TextStyle, Ui, Vec2};
use nih_plug::prelude::{Param, ParamFlags, ParamPtr, ParamSetter, Params}; use nih_plug::prelude::{Param, ParamFlags, ParamPtr, ParamSetter, Params};
@ -35,7 +35,7 @@ pub struct GenericSlider;
/// space. /// space.
pub fn create( pub fn create(
ui: &mut Ui, ui: &mut Ui,
params: Pin<&dyn Params>, params: Arc<impl Params>,
setter: &ParamSetter, setter: &ParamSetter,
widget: impl ParamWidget, widget: impl ParamWidget,
) { ) {

View file

@ -12,14 +12,14 @@
//! } //! }
//! //!
//! pub(crate) fn create( //! pub(crate) fn create(
//! params: Pin<Arc<FooParams>>, //! params: Arc<FooParams>,
//! editor_state: Arc<IcedState>, //! editor_state: Arc<IcedState>,
//! ) -> Option<Box<dyn Editor>> { //! ) -> Option<Box<dyn Editor>> {
//! create_iced_editor::<Foo>(editor_state, params) //! create_iced_editor::<Foo>(editor_state, params)
//! } //! }
//! //!
//! struct FooEditor { //! struct FooEditor {
//! params: Pin<Arc<FooParams>>, //! params: Arc<FooParams>,
//! context: Arc<dyn GuiContext>, //! context: Arc<dyn GuiContext>,
//! //!
//! foo_slider_state: nih_widgets::param_slider::State, //! foo_slider_state: nih_widgets::param_slider::State,
@ -34,7 +34,7 @@
//! impl IcedEditor for FooEditor { //! impl IcedEditor for FooEditor {
//! type Executor = executor::Default; //! type Executor = executor::Default;
//! type Message = Message; //! type Message = Message;
//! type InitializationFlags = Pin<Arc<FooParams>>; //! type InitializationFlags = Arc<FooParams>;
//! //!
//! fn new( //! fn new(
//! params: Self::InitializationFlags, //! 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 /// 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 /// 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 /// of their [`InitializationFlags`][Self::InitializationFlags] so it can read the current parameter
/// values. See [`Application`] for more information. /// values. See [`Application`] for more information.
pub trait IcedEditor: 'static + Send + Sync + Sized { pub trait IcedEditor: 'static + Send + Sync + Sized {

View file

@ -5,7 +5,7 @@ use atomic_refcell::AtomicRefCell;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::collections::HashMap; use std::collections::HashMap;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin; use std::sync::Arc;
use nih_plug::prelude::{Param, ParamFlags, ParamPtr, Params}; use nih_plug::prelude::{Param, ParamFlags, ParamPtr, Params};
@ -58,7 +58,7 @@ pub struct GenericSlider;
pub struct GenericUi<'a, W: ParamWidget> { pub struct GenericUi<'a, W: ParamWidget> {
state: &'a mut State<W>, state: &'a mut State<W>,
params: Pin<&'a dyn Params>, params: Arc<dyn Params>,
width: Length, width: Length,
height: Length, height: Length,
@ -85,7 +85,7 @@ where
W: ParamWidget, W: ParamWidget,
{ {
/// Creates a new [`GenericUi`] for all provided parameters. /// 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 { Self {
state, state,

View file

@ -1,6 +1,6 @@
//! Generic UIs for NIH-plug using VIZIA. //! 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 nih_plug::prelude::{ParamFlags, ParamPtr, Params};
use vizia::*; use vizia::*;
@ -25,14 +25,12 @@ impl GenericUi {
/// }) /// })
/// .width(Percentage(100.0)); /// .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 where
L: Lens<Target = Pin<PsPtr>> + Copy, L: Lens<Target = Arc<Ps>> + Copy,
PsPtr: 'static + Deref<Target = Ps>, Ps: Params + 'static,
Ps: Params,
{ {
// Basic styling is done in the `theme.css` style sheet // Basic styling is done in the `theme.css` style sheet
// TODO: Scrolling
Self::new_custom(cx, params, |cx, param_ptr| { Self::new_custom(cx, params, |cx, param_ptr| {
HStack::new(cx, |cx| { HStack::new(cx, |cx| {
// Align this on the right // 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 /// Creates a new [`GenericUi`] for all provided parameters using a custom closure that receives
/// a function that should draw some widget for each parameter. /// 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, cx: &mut Context,
params: L, params: L,
mut make_widget: impl FnMut(&mut Context, ParamPtr), mut make_widget: impl FnMut(&mut Context, ParamPtr),
) -> Handle<Self> ) -> Handle<Self>
where where
L: Lens<Target = Pin<PsPtr>> + Copy, L: Lens<Target = Arc<Ps>> + Copy,
PsPtr: 'static + Deref<Target = Ps>, Ps: Params + 'static,
Ps: Params,
{ {
// Basic styling is done in the `theme.css` style sheet // Basic styling is done in the `theme.css` style sheet
Self.build2(cx, |cx| { Self.build2(cx, |cx| {

View file

@ -18,7 +18,6 @@ use nih_plug::prelude::Editor;
use nih_plug_vizia::vizia::*; use nih_plug_vizia::vizia::*;
use nih_plug_vizia::widgets::*; use nih_plug_vizia::widgets::*;
use nih_plug_vizia::{assets, create_vizia_editor, ViziaState}; use nih_plug_vizia::{assets, create_vizia_editor, ViziaState};
use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use crate::CrispParams; use crate::CrispParams;
@ -28,7 +27,7 @@ const POINT_SCALE: f32 = 0.75;
#[derive(Lens)] #[derive(Lens)]
struct Data { struct Data {
params: Pin<Arc<CrispParams>>, params: Arc<CrispParams>,
} }
impl Model for Data {} impl Model for Data {}
@ -39,7 +38,7 @@ pub(crate) fn default_state() -> Arc<ViziaState> {
} }
pub(crate) fn create( pub(crate) fn create(
params: Pin<Arc<CrispParams>>, params: Arc<CrispParams>,
editor_state: Arc<ViziaState>, editor_state: Arc<ViziaState>,
) -> Option<Box<dyn Editor>> { ) -> Option<Box<dyn Editor>> {
create_vizia_editor(editor_state, move |cx| { create_vizia_editor(editor_state, move |cx| {

View file

@ -20,7 +20,6 @@ extern crate nih_plug;
use nih_plug::prelude::*; use nih_plug::prelude::*;
use nih_plug_vizia::ViziaState; use nih_plug_vizia::ViziaState;
use pcg::Pcg32iState; use pcg::Pcg32iState;
use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
mod editor; 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 /// 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. /// since this effect just turns into literal noise at high frequencies.
struct Crisp { struct Crisp {
params: Pin<Arc<CrispParams>>, params: Arc<CrispParams>,
editor_state: Arc<ViziaState>, editor_state: Arc<ViziaState>,
/// Needed for computing the filter coefficients. /// Needed for computing the filter coefficients.
@ -126,7 +125,7 @@ enum StereoMode {
impl Default for Crisp { impl Default for Crisp {
fn default() -> Self { fn default() -> Self {
Self { Self {
params: Arc::pin(CrispParams::default()), params: Arc::new(CrispParams::default()),
editor_state: editor::default_state(), editor_state: editor::default_state(),
sample_rate: 1.0, sample_rate: 1.0,
@ -308,8 +307,8 @@ impl Plugin for Crisp {
const SAMPLE_ACCURATE_AUTOMATION: bool = true; const SAMPLE_ACCURATE_AUTOMATION: bool = true;
fn params(&self) -> Pin<&dyn Params> { fn params(&self) -> Arc<dyn Params> {
self.params.as_ref() self.params.clone()
} }
fn editor(&self) -> Option<Box<dyn Editor>> { 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::vizia::*;
use nih_plug_vizia::widgets::*; use nih_plug_vizia::widgets::*;
use nih_plug_vizia::{assets, create_vizia_editor, ViziaState}; use nih_plug_vizia::{assets, create_vizia_editor, ViziaState};
use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use crate::DiopserParams; use crate::DiopserParams;
@ -28,7 +27,7 @@ const POINT_SCALE: f32 = 0.75;
#[derive(Lens)] #[derive(Lens)]
struct Data { struct Data {
params: Pin<Arc<DiopserParams>>, params: Arc<DiopserParams>,
} }
impl Model for Data {} impl Model for Data {}
@ -39,7 +38,7 @@ pub(crate) fn default_state() -> Arc<ViziaState> {
} }
pub(crate) fn create( pub(crate) fn create(
params: Pin<Arc<DiopserParams>>, params: Arc<DiopserParams>,
editor_state: Arc<ViziaState>, editor_state: Arc<ViziaState>,
) -> Option<Box<dyn Editor>> { ) -> Option<Box<dyn Editor>> {
create_vizia_editor(editor_state, move |cx| { create_vizia_editor(editor_state, move |cx| {

View file

@ -24,7 +24,6 @@ extern crate nih_plug;
use nih_plug::prelude::*; use nih_plug::prelude::*;
use nih_plug_vizia::ViziaState; use nih_plug_vizia::ViziaState;
use std::pin::Pin;
use std::simd::f32x2; use std::simd::f32x2;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; 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 // - Briefly muting the output when changing the number of filters to get rid of the clicks
// - A proper GUI // - A proper GUI
struct Diopser { struct Diopser {
params: Pin<Arc<DiopserParams>>, params: Arc<DiopserParams>,
editor_state: Arc<ViziaState>, editor_state: Arc<ViziaState>,
/// Needed for computing the filter coefficients. /// Needed for computing the filter coefficients.
@ -125,7 +124,7 @@ impl Default for Diopser {
SpectrumInput::new(Self::DEFAULT_NUM_OUTPUTS as usize); SpectrumInput::new(Self::DEFAULT_NUM_OUTPUTS as usize);
Self { Self {
params: Arc::pin(DiopserParams::new(should_update_filters.clone())), params: Arc::new(DiopserParams::new(should_update_filters.clone())),
editor_state: editor::default_state(), editor_state: editor::default_state(),
sample_rate: 1.0, sample_rate: 1.0,
@ -240,8 +239,8 @@ impl Plugin for Diopser {
const SAMPLE_ACCURATE_AUTOMATION: bool = true; const SAMPLE_ACCURATE_AUTOMATION: bool = true;
fn params(&self) -> Pin<&dyn Params> { fn params(&self) -> Arc<dyn Params> {
self.params.as_ref() self.params.clone()
} }
fn editor(&self) -> Option<Box<dyn Editor>> { fn editor(&self) -> Option<Box<dyn Editor>> {

View file

@ -1,12 +1,11 @@
use atomic_float::AtomicF32; use atomic_float::AtomicF32;
use nih_plug::prelude::*; use nih_plug::prelude::*;
use nih_plug_egui::{create_egui_editor, egui, widgets, EguiState}; use nih_plug_egui::{create_egui_editor, egui, widgets, EguiState};
use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
/// This is mostly identical to the gain example, minus some fluff, and with a GUI. /// This is mostly identical to the gain example, minus some fluff, and with a GUI.
struct Gain { struct Gain {
params: Pin<Arc<GainParams>>, params: Arc<GainParams>,
editor_state: Arc<EguiState>, editor_state: Arc<EguiState>,
/// Needed to normalize the peak meter's response based on the sample rate. /// Needed to normalize the peak meter's response based on the sample rate.
@ -32,7 +31,7 @@ struct GainParams {
impl Default for Gain { impl Default for Gain {
fn default() -> Self { fn default() -> Self {
Self { Self {
params: Arc::pin(GainParams::default()), params: Arc::new(GainParams::default()),
editor_state: EguiState::from_size(300, 180), editor_state: EguiState::from_size(300, 180),
peak_meter_decay_weight: 1.0, peak_meter_decay_weight: 1.0,
@ -74,8 +73,8 @@ impl Plugin for Gain {
const ACCEPTS_MIDI: bool = false; const ACCEPTS_MIDI: bool = false;
const SAMPLE_ACCURATE_AUTOMATION: bool = true; const SAMPLE_ACCURATE_AUTOMATION: bool = true;
fn params(&self) -> Pin<&dyn Params> { fn params(&self) -> Arc<dyn Params> {
self.params.as_ref() self.params.clone()
} }
fn editor(&self) -> Option<Box<dyn Editor>> { 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::prelude::{util, Editor, GuiContext};
use nih_plug_iced::widgets as nih_widgets; use nih_plug_iced::widgets as nih_widgets;
use nih_plug_iced::*; use nih_plug_iced::*;
use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
@ -14,7 +13,7 @@ pub(crate) fn default_state() -> Arc<IcedState> {
} }
pub(crate) fn create( pub(crate) fn create(
params: Pin<Arc<GainParams>>, params: Arc<GainParams>,
peak_meter: Arc<AtomicF32>, peak_meter: Arc<AtomicF32>,
editor_state: Arc<IcedState>, editor_state: Arc<IcedState>,
) -> Option<Box<dyn Editor>> { ) -> Option<Box<dyn Editor>> {
@ -22,7 +21,7 @@ pub(crate) fn create(
} }
struct GainEditor { struct GainEditor {
params: Pin<Arc<GainParams>>, params: Arc<GainParams>,
context: Arc<dyn GuiContext>, context: Arc<dyn GuiContext>,
peak_meter: Arc<AtomicF32>, peak_meter: Arc<AtomicF32>,
@ -40,7 +39,7 @@ enum Message {
impl IcedEditor for GainEditor { impl IcedEditor for GainEditor {
type Executor = executor::Default; type Executor = executor::Default;
type Message = Message; type Message = Message;
type InitializationFlags = (Pin<Arc<GainParams>>, Arc<AtomicF32>); type InitializationFlags = (Arc<GainParams>, Arc<AtomicF32>);
fn new( fn new(
(params, peak_meter): Self::InitializationFlags, (params, peak_meter): Self::InitializationFlags,

View file

@ -1,14 +1,13 @@
use atomic_float::AtomicF32; use atomic_float::AtomicF32;
use nih_plug::prelude::*; use nih_plug::prelude::*;
use nih_plug_iced::IcedState; use nih_plug_iced::IcedState;
use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
mod editor; mod editor;
/// This is mostly identical to the gain example, minus some fluff, and with a GUI. /// This is mostly identical to the gain example, minus some fluff, and with a GUI.
struct Gain { struct Gain {
params: Pin<Arc<GainParams>>, params: Arc<GainParams>,
editor_state: Arc<IcedState>, editor_state: Arc<IcedState>,
/// Needed to normalize the peak meter's response based on the sample rate. /// Needed to normalize the peak meter's response based on the sample rate.
@ -30,7 +29,7 @@ struct GainParams {
impl Default for Gain { impl Default for Gain {
fn default() -> Self { fn default() -> Self {
Self { Self {
params: Arc::pin(GainParams::default()), params: Arc::new(GainParams::default()),
editor_state: editor::default_state(), editor_state: editor::default_state(),
peak_meter_decay_weight: 1.0, peak_meter_decay_weight: 1.0,
@ -71,8 +70,8 @@ impl Plugin for Gain {
const ACCEPTS_MIDI: bool = false; const ACCEPTS_MIDI: bool = false;
const SAMPLE_ACCURATE_AUTOMATION: bool = true; const SAMPLE_ACCURATE_AUTOMATION: bool = true;
fn params(&self) -> Pin<&dyn Params> { fn params(&self) -> Arc<dyn Params> {
self.params.as_ref() self.params.clone()
} }
fn editor(&self) -> Option<Box<dyn Editor>> { 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::vizia::*;
use nih_plug_vizia::widgets::*; use nih_plug_vizia::widgets::*;
use nih_plug_vizia::{assets, create_vizia_editor, ViziaState}; use nih_plug_vizia::{assets, create_vizia_editor, ViziaState};
use std::pin::Pin;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
@ -17,7 +16,7 @@ const STYLE: &str = r#""#;
#[derive(Lens)] #[derive(Lens)]
struct Data { struct Data {
params: Pin<Arc<GainParams>>, params: Arc<GainParams>,
peak_meter: Arc<AtomicF32>, peak_meter: Arc<AtomicF32>,
} }
@ -29,7 +28,7 @@ pub(crate) fn default_state() -> Arc<ViziaState> {
} }
pub(crate) fn create( pub(crate) fn create(
params: Pin<Arc<GainParams>>, params: Arc<GainParams>,
peak_meter: Arc<AtomicF32>, peak_meter: Arc<AtomicF32>,
editor_state: Arc<ViziaState>, editor_state: Arc<ViziaState>,
) -> Option<Box<dyn Editor>> { ) -> Option<Box<dyn Editor>> {

View file

@ -1,14 +1,13 @@
use atomic_float::AtomicF32; use atomic_float::AtomicF32;
use nih_plug::prelude::*; use nih_plug::prelude::*;
use nih_plug_vizia::ViziaState; use nih_plug_vizia::ViziaState;
use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
mod editor; mod editor;
/// This is mostly identical to the gain example, minus some fluff, and with a GUI. /// This is mostly identical to the gain example, minus some fluff, and with a GUI.
struct Gain { struct Gain {
params: Pin<Arc<GainParams>>, params: Arc<GainParams>,
editor_state: Arc<ViziaState>, editor_state: Arc<ViziaState>,
/// Needed to normalize the peak meter's response based on the sample rate. /// Needed to normalize the peak meter's response based on the sample rate.
@ -30,7 +29,7 @@ struct GainParams {
impl Default for Gain { impl Default for Gain {
fn default() -> Self { fn default() -> Self {
Self { Self {
params: Arc::pin(GainParams::default()), params: Arc::new(GainParams::default()),
editor_state: editor::default_state(), editor_state: editor::default_state(),
peak_meter_decay_weight: 1.0, peak_meter_decay_weight: 1.0,
@ -71,8 +70,8 @@ impl Plugin for Gain {
const ACCEPTS_MIDI: bool = false; const ACCEPTS_MIDI: bool = false;
const SAMPLE_ACCURATE_AUTOMATION: bool = true; const SAMPLE_ACCURATE_AUTOMATION: bool = true;
fn params(&self) -> Pin<&dyn Params> { fn params(&self) -> Arc<dyn Params> {
self.params.as_ref() self.params.clone()
} }
fn editor(&self) -> Option<Box<dyn Editor>> { fn editor(&self) -> Option<Box<dyn Editor>> {

View file

@ -1,9 +1,9 @@
use nih_plug::prelude::*; use nih_plug::prelude::*;
use parking_lot::RwLock; use parking_lot::RwLock;
use std::pin::Pin; use std::sync::Arc;
struct Gain { 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 /// 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 { impl Default for Gain {
fn default() -> Self { fn default() -> Self {
Self { Self {
params: Box::pin(GainParams::default()), params: Arc::new(GainParams::default()),
} }
} }
} }
@ -111,8 +111,8 @@ impl Plugin for Gain {
// splits. // splits.
const SAMPLE_ACCURATE_AUTOMATION: bool = true; const SAMPLE_ACCURATE_AUTOMATION: bool = true;
fn params(&self) -> Pin<&dyn Params> { fn params(&self) -> Arc<dyn Params> {
self.params.as_ref() self.params.clone()
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool { fn accepts_bus_config(&self, config: &BusConfig) -> bool {

View file

@ -1,11 +1,11 @@
use nih_plug::prelude::*; use nih_plug::prelude::*;
use std::f32::consts; 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 /// A test tone generator that can either generate a sine wave based on the plugin's parameters or
/// based on the current MIDI input. /// based on the current MIDI input.
struct Sine { struct Sine {
params: Pin<Box<SineParams>>, params: Arc<SineParams>,
sample_rate: f32, sample_rate: f32,
/// The current phase of the sine wave, always kept between in `[0, 1]`. /// The current phase of the sine wave, always kept between in `[0, 1]`.
@ -35,7 +35,7 @@ struct SineParams {
impl Default for Sine { impl Default for Sine {
fn default() -> Self { fn default() -> Self {
Self { Self {
params: Box::pin(SineParams::default()), params: Arc::new(SineParams::default()),
sample_rate: 1.0, sample_rate: 1.0,
phase: 0.0, phase: 0.0,
@ -106,8 +106,8 @@ impl Plugin for Sine {
const ACCEPTS_MIDI: bool = true; const ACCEPTS_MIDI: bool = true;
const SAMPLE_ACCURATE_AUTOMATION: bool = true; const SAMPLE_ACCURATE_AUTOMATION: bool = true;
fn params(&self) -> Pin<&dyn Params> { fn params(&self) -> Arc<dyn Params> {
self.params.as_ref() self.params.clone()
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool { 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::num_complex::Complex32;
use realfft::{ComplexToReal, RealFftPlanner, RealToComplex}; use realfft::{ComplexToReal, RealFftPlanner, RealToComplex};
use std::f32; use std::f32;
use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
const WINDOW_SIZE: usize = 2048; const WINDOW_SIZE: usize = 2048;
const OVERLAP_TIMES: usize = 4; const OVERLAP_TIMES: usize = 4;
struct Stft { struct Stft {
params: Pin<Box<StftParams>>, params: Arc<StftParams>,
/// An adapter that performs most of the overlap-add algorithm for us. /// An adapter that performs most of the overlap-add algorithm for us.
stft: util::StftHelper, stft: util::StftHelper,
@ -56,7 +55,7 @@ impl Default for Stft {
.unwrap(); .unwrap();
Self { Self {
params: Box::pin(StftParams::default()), params: Arc::new(StftParams::default()),
stft: util::StftHelper::new(2, WINDOW_SIZE), stft: util::StftHelper::new(2, WINDOW_SIZE),
window_function: util::window::hann(WINDOW_SIZE), window_function: util::window::hann(WINDOW_SIZE),
@ -91,8 +90,8 @@ impl Plugin for Stft {
const ACCEPTS_MIDI: bool = false; const ACCEPTS_MIDI: bool = false;
const SAMPLE_ACCURATE_AUTOMATION: bool = true; const SAMPLE_ACCURATE_AUTOMATION: bool = true;
fn params(&self) -> Pin<&dyn Params> { fn params(&self) -> Arc<dyn Params> {
self.params.as_ref() self.params.clone()
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool { 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::num_complex::Complex32;
use realfft::{ComplexToReal, RealFftPlanner, RealToComplex}; use realfft::{ComplexToReal, RealFftPlanner, RealToComplex};
use std::f32; use std::f32;
use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
const MIN_WINDOW_ORDER: usize = 6; 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 const MAX_OVERLAP_TIMES: usize = 1 << MAX_OVERLAP_ORDER; // 32
struct PubertySimulator { struct PubertySimulator {
params: Pin<Box<PubertySimulatorParams>>, params: Arc<PubertySimulatorParams>,
/// An adapter that performs most of the overlap-add algorithm for us. /// An adapter that performs most of the overlap-add algorithm for us.
stft: util::StftHelper, stft: util::StftHelper,
@ -82,7 +81,7 @@ struct PubertySimulatorParams {
impl Default for PubertySimulator { impl Default for PubertySimulator {
fn default() -> Self { fn default() -> Self {
Self { Self {
params: Box::pin(PubertySimulatorParams::default()), params: Arc::new(PubertySimulatorParams::default()),
stft: util::StftHelper::new(2, MAX_WINDOW_SIZE), stft: util::StftHelper::new(2, MAX_WINDOW_SIZE),
window_function: Vec::with_capacity(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_INPUTS: u32 = 2;
const DEFAULT_NUM_OUTPUTS: u32 = 2; const DEFAULT_NUM_OUTPUTS: u32 = 2;
fn params(&self) -> Pin<&dyn Params> { fn params(&self) -> Arc<dyn Params> {
self.params.as_ref() self.params.clone()
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool { fn accepts_bus_config(&self, config: &BusConfig) -> bool {

View file

@ -1,7 +1,6 @@
//! Implementation details for the parameter management. //! Implementation details for the parameter management.
use std::collections::HashMap; use std::collections::HashMap;
use std::pin::Pin;
use super::{Param, ParamFlags}; use super::{Param, ParamFlags};
@ -37,9 +36,9 @@ pub use serde_json::to_string as serialize_field;
/// ///
/// # Safety /// # Safety
/// ///
/// This implementation is safe when using from the wrapper because the plugin object needs to be /// This implementation is safe when using from the wrapper because the plugin's returned `Params`
/// pinned, and it can never outlive the wrapper. /// object lives in an `Arc`, and the wrapper also holds a reference to this `Arc`.
pub unsafe trait Params { pub unsafe trait Params: 'static + Send + Sync {
/// Create a mapping from unique parameter IDs to parameters along with the name of the /// 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 /// 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 /// (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 /// 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 /// 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 /// 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 /// # Note
/// ///
/// This uses `String` even though for the `Params` derive macro `&'static str` would have been /// 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. /// 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 /// 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 /// 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), EnumParam(*mut super::enums::EnumParamInner),
} }
// These pointers only point to fields on pinned structs, and the caller always needs to make sure // These pointers only point to fields on structs kept in an `Arc<dyn Params>`, and the caller
// that dereferencing them is safe // 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 Send for ParamPtr {}
unsafe impl Sync for ParamPtr {} unsafe impl Sync for ParamPtr {}

View file

@ -2,7 +2,6 @@
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
use std::any::Any; use std::any::Any;
use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use crate::buffer::Buffer; 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 /// 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 /// `process()`. These parameters are identified by strings that should never change when the
/// plugin receives an update. /// 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 /// 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 /// [`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::buffer::Buffer;
use crate::context::Transport; use crate::context::Transport;
use crate::event_loop::{EventLoop, MainThreadExecutor, TASK_QUEUE_CAPACITY}; 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::param::ParamFlags;
use crate::plugin::{ use crate::plugin::{
BufferConfig, BusConfig, ClapPlugin, Editor, NoteEvent, ParentWindowHandle, ProcessStatus, BufferConfig, BusConfig, ClapPlugin, Editor, NoteEvent, ParentWindowHandle, ProcessStatus,
@ -90,6 +90,10 @@ pub struct Wrapper<P: ClapPlugin> {
/// The wrapped plugin instance. /// The wrapped plugin instance.
plugin: RwLock<P>, 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 /// 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.
@ -162,8 +166,8 @@ pub struct Wrapper<P: ClapPlugin> {
/// The keys from `param_map` in a stable order. /// The keys from `param_map` in a stable order.
param_hashes: Vec<u32>, param_hashes: Vec<u32>,
/// A mapping from parameter ID hashes (obtained from the string parameter IDs) to pointers to /// 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 /// parameters belonging to the plugin. These addresses will remain stable as long as the
/// addresses will remain stable, as they are obtained from a pinned object. /// `params` object does not get deallocated.
param_by_hash: HashMap<u32, ParamPtr>, param_by_hash: HashMap<u32, ParamPtr>,
/// The group name of a parameter, indexed by the parameter's hash. Nested groups are delimited /// 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 /// 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 // `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 // 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. // remain in the same order as the one returned by the plugin.
let param_id_hashes_ptrs_groups: Vec<_> = plugin let params = plugin.read().params();
.read() let param_id_hashes_ptrs_groups: Vec<_> = params
.params()
.param_map() .param_map()
.into_iter() .into_iter()
.map(|(id, ptr, group)| { .map(|(id, ptr, group)| {
@ -329,7 +332,7 @@ impl<P: ClapPlugin> Wrapper<P> {
}) })
.collect(); .collect();
if cfg!(debug_assertions) { 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 let param_ids: HashSet<_> = param_id_hashes_ptrs_groups
.iter() .iter()
.map(|(id, _, _, _)| id.clone()) .map(|(id, _, _, _)| id.clone())
@ -430,6 +433,7 @@ impl<P: ClapPlugin> Wrapper<P> {
this: AtomicRefCell::new(Weak::new()), this: AtomicRefCell::new(Weak::new()),
plugin, plugin,
params,
editor, editor,
editor_handle: RwLock::new(None), editor_handle: RwLock::new(None),
editor_scaling_factor: AtomicF32::new(1.0), editor_scaling_factor: AtomicF32::new(1.0),
@ -1916,7 +1920,7 @@ impl<P: ClapPlugin> Wrapper<P> {
let wrapper = &*(plugin as *const Self); let wrapper = &*(plugin as *const Self);
let serialized = state::serialize( let serialized = state::serialize(
wrapper.plugin.read().params(), wrapper.params.clone(),
&wrapper.param_by_hash, &wrapper.param_by_hash,
&wrapper.param_id_to_hash, &wrapper.param_id_to_hash,
); );
@ -1976,7 +1980,7 @@ impl<P: ClapPlugin> Wrapper<P> {
let success = state::deserialize( let success = state::deserialize(
&read_buffer, &read_buffer,
wrapper.plugin.read().params(), wrapper.params.clone(),
&wrapper.param_by_hash, &wrapper.param_by_hash,
&wrapper.param_id_to_hash, &wrapper.param_id_to_hash,
wrapper.current_buffer_config.load().as_ref(), wrapper.current_buffer_config.load().as_ref(),

View file

@ -3,7 +3,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::pin::Pin; use std::sync::Arc;
use crate::param::internals::{ParamPtr, Params}; use crate::param::internals::{ParamPtr, Params};
use crate::param::Param; 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 /// Serialize a plugin's state to a vector containing JSON data. This can (and should) be shared
/// across plugin formats. /// across plugin formats.
pub(crate) unsafe fn serialize( pub(crate) unsafe fn serialize(
plugin_params: Pin<&dyn Params>, plugin_params: Arc<dyn Params>,
param_by_hash: &HashMap<u32, ParamPtr>, param_by_hash: &HashMap<u32, ParamPtr>,
param_id_to_hash: &HashMap<String, u32>, param_id_to_hash: &HashMap<String, u32>,
) -> serde_json::Result<Vec<u8>> { ) -> 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. /// parameter values. The smoothers have already been reset by this function.
pub(crate) unsafe fn deserialize( pub(crate) unsafe fn deserialize(
state: &[u8], state: &[u8],
plugin_params: Pin<&dyn Params>, plugin_params: Arc<dyn Params>,
param_by_hash: &HashMap<u32, ParamPtr>, param_by_hash: &HashMap<u32, ParamPtr>,
param_id_to_hash: &HashMap<String, u32>, param_id_to_hash: &HashMap<String, u32>,
current_buffer_config: Option<&BufferConfig>, current_buffer_config: Option<&BufferConfig>,

View file

@ -16,7 +16,7 @@ use super::view::WrapperView;
use crate::buffer::Buffer; use crate::buffer::Buffer;
use crate::context::Transport; use crate::context::Transport;
use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop}; use crate::event_loop::{EventLoop, MainThreadExecutor, OsEventLoop};
use crate::param::internals::ParamPtr; use crate::param::internals::{ParamPtr, Params};
use crate::param::ParamFlags; use crate::param::ParamFlags;
use crate::plugin::{BufferConfig, BusConfig, Editor, NoteEvent, ProcessStatus, Vst3Plugin}; use crate::plugin::{BufferConfig, BusConfig, Editor, NoteEvent, ProcessStatus, Vst3Plugin};
use crate::wrapper::util::hash_param_id; use crate::wrapper::util::hash_param_id;
@ -27,6 +27,10 @@ use crate::wrapper::util::hash_param_id;
pub(crate) struct WrapperInner<P: Vst3Plugin> { pub(crate) struct WrapperInner<P: Vst3Plugin> {
/// The wrapped plugin instance. /// The wrapped plugin instance.
pub plugin: RwLock<P>, 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 /// 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.
@ -80,8 +84,8 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
/// The keys from `param_map` in a stable order. /// The keys from `param_map` in a stable order.
pub param_hashes: Vec<u32>, pub param_hashes: Vec<u32>,
/// A mapping from parameter ID hashes (obtained from the string parameter IDs) to pointers to /// 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 /// parameters belonging to the plugin. These addresses will remain stable as long as the
/// addresses will remain stable, as they are obtained from a pinned object. /// `params` object does not get deallocated.
pub param_by_hash: HashMap<u32, ParamPtr>, pub param_by_hash: HashMap<u32, ParamPtr>,
pub param_units: ParamUnits, pub param_units: ParamUnits,
/// Mappings from string parameter indentifiers to parameter hashes. Useful for debug logging /// 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 // `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 // 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. // remain in the same order as the one returned by the plugin.
let param_id_hashes_ptrs_groups: Vec<_> = plugin let params = plugin.read().params();
.read() let param_id_hashes_ptrs_groups: Vec<_> = params
.params()
.param_map() .param_map()
.into_iter() .into_iter()
.map(|(id, ptr, group)| { .map(|(id, ptr, group)| {
@ -146,7 +149,7 @@ impl<P: Vst3Plugin> WrapperInner<P> {
}) })
.collect(); .collect();
if cfg!(debug_assertions) { 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 let param_ids: HashSet<_> = param_id_hashes_ptrs_groups
.iter() .iter()
.map(|(id, _, _, _)| id.clone()) .map(|(id, _, _, _)| id.clone())
@ -199,6 +202,7 @@ impl<P: Vst3Plugin> WrapperInner<P> {
let wrapper = Self { let wrapper = Self {
plugin, plugin,
params,
editor, editor,
component_handler: AtomicRefCell::new(None), component_handler: AtomicRefCell::new(None),

View file

@ -222,7 +222,7 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
let success = state::deserialize( let success = state::deserialize(
&read_buffer, &read_buffer,
self.inner.plugin.read().params(), self.inner.params.clone(),
&self.inner.param_by_hash, &self.inner.param_by_hash,
&self.inner.param_id_to_hash, &self.inner.param_id_to_hash,
self.inner.current_buffer_config.load().as_ref(), 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 state = state.upgrade().unwrap();
let serialized = state::serialize( let serialized = state::serialize(
self.inner.plugin.read().params(), self.inner.params.clone(),
&self.inner.param_by_hash, &self.inner.param_by_hash,
&self.inner.param_id_to_hash, &self.inner.param_id_to_hash,
); );