From 753ca201a64133f16675f37f9ccdfd8612fd43d4 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Nov 2022 00:17:13 +0100 Subject: [PATCH] Support #[nested] prefixes/suffixes w/ #[persist] This makes nested fields behave the same as nested parameters, allowing multiple copies of a persistent field to exist. --- BREAKING_CHANGES.md | 8 +++++ nih_plug_derive/src/params.rs | 56 +++++++++++++++++++++++++++++------ src/params.rs | 14 +++------ 3 files changed, 59 insertions(+), 19 deletions(-) diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index 09a9895d..045b3415 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -8,6 +8,14 @@ code then it will not be listed here. ## [2022-11-17] +- The `Params` derive macro now also properly supports persistent fields in + `#[nested]` parameter structs. This takes `#[nested(id_prefix = "...")]` and + `#[nested(array)]` into account to allow multiple copies of a persistent + field. This may break existing usages as serialized field data without a + matching preffix or suffix is no longer passed to the child object. + +## [2022-11-17] + - The order of `#[nested]` parameters in the parameter list now always follows the declaration order instead of nested parameters being ordered below regular parameters. diff --git a/nih_plug_derive/src/params.rs b/nih_plug_derive/src/params.rs index 7564be5b..955d8a62 100644 --- a/nih_plug_derive/src/params.rs +++ b/nih_plug_derive/src/params.rs @@ -313,6 +313,7 @@ pub fn derive_params(input: TokenStream) -> TokenStream { }) .unzip(); + // ID prefixes are also added for nested objects let (serialize_fields_nested_tokens, deserialize_fields_nested_tokens): (Vec<_>, Vec<_>) = params .iter() @@ -321,23 +322,60 @@ pub fn derive_params(input: TokenStream) -> TokenStream { Param::Nested(nested) => Some(nested), }) .map(|nested| match nested { - NestedParams::Inline { field, .. } | NestedParams::Prefixed { field, .. } => ( - // TODO: For some reason the macro won't parse correctly if you inline this + NestedParams::Inline { field, .. } => ( + quote! { serialized.extend(self.#field.serialize_fields()); }, + quote! { self.#field.deserialize_fields(serialized); }, + ), + NestedParams::Prefixed { + field, id_prefix, .. + } => ( quote! { - let inlineme = self.#field.serialize_fields(); - serialized.extend(inlineme); + let prefixed = self + .#field + .serialize_fields() + .into_iter() + .map(|(key, value)| (format!("{}_{}", #id_prefix, key), value)); + + serialized.extend(prefixed); + }, + quote! { + let prefix = format!("{}_", #id_prefix); + let matching_fields = serialized + .iter() + .filter_map(|(key, value)| { + let original_key = key.strip_prefix(&prefix)?; + Some((original_key.to_owned(), value.to_owned())) + }) + .collect(); + + self.#field.deserialize_fields(&matching_fields); }, - quote! { self.#field.deserialize_fields(serialized) }, ), NestedParams::Array { field, .. } => ( quote! { - for field in self.#field.iter() { - serialized.extend(field.serialize_fields()); + for (field_idx, field) in self.#field.iter().enumerate() { + let idx = field_idx + 1; + let suffixed = field + .serialize_fields() + .into_iter() + .map(|(key, value)| (format!("{}_{}", key, idx), value)); + + serialized.extend(suffixed); } }, quote! { - for field in self.#field.iter() { - field.deserialize_fields(serialized); + for (field_idx, field) in self.#field.iter().enumerate() { + let idx = field_idx + 1; + let suffix = format!("_{}", idx); + let matching_fields = serialized + .iter() + .filter_map(|(key, value)| { + let original_key = key.strip_suffix(&suffix)?; + Some((original_key.to_owned(), value.to_owned())) + }) + .collect(); + + field.deserialize_fields(&matching_fields); } }, ), diff --git a/src/params.rs b/src/params.rs index 836f2293..44a3838c 100644 --- a/src/params.rs +++ b/src/params.rs @@ -230,10 +230,7 @@ pub(crate) trait ParamMut: Param { /// Finally, the `Params` object may include parameters from other objects. Setting a group name is /// optional, but some hosts can use this information to display the parameters in a tree structure. /// Parameter IDs and persisting keys still need to be **unique** when using nested parameter -/// structs. This currently has the following caveats: -/// -/// - Enforcing that parameter IDs and persist keys are unique does not work across nested structs. -/// - Deserializing persisted fields will give false positives about fields not existing. +/// structs. /// /// Take a look at the example gain example plugin to see how this is used. /// @@ -242,20 +239,17 @@ pub(crate) trait ParamMut: Param { /// Adding this attribute to a `Params` sub-object works similarly to the regular `#[nested]` /// attribute, but it also adds an ID to all parameters from the nested object. If a parameter in /// the nested nested object normally has parameter ID `bar`, the parameter's ID will now be renamed -/// to `foo_bar`. _This makes it possible to reuse same parameter struct with different names and +/// to `foo_bar`. The same thing happens with persistent field keys to support multiple copies of +/// the field. _This makes it possible to reuse the same parameter struct with different names and /// parameter indices._ /// -/// This does **not** support persistent fields. -/// /// ## `#[nested(array, group_name = "Foo")]` /// /// This can be applied to an array-like data structure and it works similar to a `nested` attribute /// with an `id_name`, except that it will iterate over the array and create unique indices for all /// nested parameters. If the nested parameters object has a parameter called `bar`, then that /// parameter will belong to the group `Foo {array_index + 1}`, and it will have the renamed -/// parameter ID `bar_{array_index + 1}`. -/// -/// This does **not** support persistent fields. +/// parameter ID `bar_{array_index + 1}`. The same thing applies to persistent field keys. /// /// # Safety ///