From 221e424f787a33d996137d79d0a1ec8b859134b3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 30 Jan 2022 17:07:50 +0100 Subject: [PATCH] Use thread safe interior mutability for persist Sadly there's not really a safe way to do this otherwise, but if you really want to have persistence this way and absolutely need it to be lock-free (because you're going to use it from the GUI thread), then you can implement your own PersistentField. --- nih_plug_derive/src/lib.rs | 46 ++++++++++++++------------- plugins/gain/src/lib.rs | 5 +-- src/params.rs | 65 ++++++++++++++++++++++++++++++++------ 3 files changed, 83 insertions(+), 33 deletions(-) diff --git a/nih_plug_derive/src/lib.rs b/nih_plug_derive/src/lib.rs index d859dd0b..a2bb0455 100644 --- a/nih_plug_derive/src/lib.rs +++ b/nih_plug_derive/src/lib.rs @@ -102,34 +102,36 @@ pub fn derive_params(input: TokenStream) -> TokenStream { // function we get type erasure for free since we only need to worry about byte // vectors field_serialize_tokens.push(quote! { - match ::nih_plug::params::serialize_field(&self.#field_name) { + match ::nih_plug::params::PersistentField::map( + &self.#field_name, + ::nih_plug::params::serialize_field, + ) { Ok(data) => { serialized.insert(String::from(#stable_name), data); } Err(err) => { - ::nih_plug::nih_log!( - "Could not serialize '{}': {}", - #stable_name, - err - ) + ::nih_plug::nih_log!("Could not serialize '{}': {}", #stable_name, err) } }; }); field_deserialize_tokens.push(quote! { - #stable_name => { - match ::nih_plug::params::deserialize_field(&data) { - Ok(deserialized) => { - self.#field_name = deserialized; - } - Err(err) => { - ::nih_plug::nih_log!( - "Could not deserialize '{}': {}", - #stable_name, - err - ) - } - }; - } + #stable_name => { + match ::nih_plug::params::deserialize_field(&data) { + Ok(deserialized) => { + ::nih_plug::params::PersistentField::set( + &self.#field_name, + deserialized, + ); + } + Err(err) => { + ::nih_plug::nih_log!( + "Could not deserialize '{}': {}", + #stable_name, + err + ) + } + }; + } }); } (Some(_), Some(_)) => { @@ -164,9 +166,9 @@ pub fn derive_params(input: TokenStream) -> TokenStream { serialized } - fn deserialize_fields(&mut self, serialized: ::std::collections::HashMap<&str, Vec>) { + fn deserialize_fields(&self, serialized: &::std::collections::HashMap>) { for (field_name, data) in serialized { - match field_name { + match field_name.as_str() { #(#field_deserialize_tokens)* _ => nih_log!("Unknown field name: {}", field_name), } diff --git a/plugins/gain/src/lib.rs b/plugins/gain/src/lib.rs index 02a7a298..8b544274 100644 --- a/plugins/gain/src/lib.rs +++ b/plugins/gain/src/lib.rs @@ -24,6 +24,7 @@ use nih_plug::{ util, }; use std::pin::Pin; +use std::sync::RwLock; struct Gain { params: Pin>, @@ -38,7 +39,7 @@ struct GainParams { /// together with a preset/state file saved for this plugin. This can be useful for storign /// things like sample data. #[persist = "industry_secrets"] - pub random_data: Vec, + pub random_data: RwLock>, } impl Default for Gain { @@ -63,7 +64,7 @@ impl Default for GainParams { value_to_string: formatters::f32_rounded(2), string_to_value: None, }, - random_data: Vec::new(), + random_data: RwLock::new(Vec::new()), } } } diff --git a/src/params.rs b/src/params.rs index 3416030b..e3a82708 100644 --- a/src/params.rs +++ b/src/params.rs @@ -17,6 +17,7 @@ use std::collections::HashMap; use std::fmt::Display; use std::pin::Pin; +use std::sync::{Mutex, RwLock}; pub type FloatParam = PlainParam; pub type IntParam = PlainParam; @@ -26,6 +27,48 @@ pub use serde_json::from_slice as deserialize_field; /// Re-export for use in the [Params] proc-macro. pub use serde_json::to_vec as serialize_field; +/// The functinoality needed for persisting a field to the plugin's state, and for restoring values +/// when loading old state. +pub trait PersistentField<'a, T>: Send + Sync +where + T: serde::Serialize + serde::Deserialize<'a>, +{ + fn set(&self, new_value: T); + fn map(&self, f: F) -> R + where + F: Fn(&T) -> R; +} + +impl<'a, T> PersistentField<'a, T> for RwLock +where + T: serde::Serialize + serde::Deserialize<'a> + Send + Sync, +{ + fn set(&self, new_value: T) { + *self.write().expect("Poisoned RwLock on write") = new_value; + } + fn map(&self, f: F) -> R + where + F: Fn(&T) -> R, + { + f(&self.read().expect("Poisoned RwLock on read")) + } +} + +impl<'a, T> PersistentField<'a, T> for Mutex +where + T: serde::Serialize + serde::Deserialize<'a> + Send + Sync, +{ + fn set(&self, new_value: T) { + *self.lock().expect("Poisoned Mutex") = new_value; + } + fn map(&self, f: F) -> R + where + F: Fn(&T) -> R, + { + f(&self.lock().expect("Poisoned Mutex")) + } +} + /// A distribution for a parameter's range. Probably need to add some forms of skewed ranges and /// maybe a callback based implementation at some point. #[derive(Debug)] @@ -285,12 +328,15 @@ impl NormalizebleRange for Range { } } -/// Describes a struct containing parameters. The idea is that we can have a normal struct -/// containing [FloatParam] and other parameter types with attributes describing a unique identifier -/// for each parameter. We can then build a mapping from those parameter IDs to the parameters using -/// the [Params::param_map] function. That way we can have easy to work with JUCE-style parameter -/// objects in the plugin without needing to manually register each parameter, like you would in -/// JUCE. +/// Describes a struct containing parameters and other persistent fields. The idea is that we can +/// have a normal struct containing [FloatParam] and other parameter types with attributes assigning +/// a unique identifier to each parameter. We can then build a mapping from those parameter IDs to +/// the parameters using the [Params::param_map] function. That way we can have easy to work with +/// JUCE-style parameter objects in the plugin without needing to manually register each parameter, +/// like you would in JUCE. +/// +/// The other persistent parameters should be [PersistentField]s containing types that can be +/// serialized and deserialized with Serde. /// /// # Safety /// @@ -308,9 +354,10 @@ pub trait Params { fn serialize_fields(&self) -> HashMap>; /// Restore all fields marked with `#[persist = "stable_name"]` from a hashmap created by - /// [Self::serialize_fields]. This gets called when the plugin's state is being restored. This - /// uses [deserialize_field] under the hood. - fn deserialize_fields(&mut self, serialized: HashMap<&str, Vec>); + /// [Self::serialize_fields]. All of thse fields should be wrapped in a [PersistentFieldq] with + /// thread safe interior mutability, like an `RwLock` or a `Mutex`. This gets called when the + /// plugin's state is being restored. This uses [deserialize_field] under the hood. + fn deserialize_fields(&self, serialized: &HashMap>); } /// Internal pointers to parameters. This is an implementation detail used by the wrappers.