Add stable IDs for enum parameters
This commit is contained in:
parent
d72bd56fe7
commit
449adb8bfc
|
@ -7,10 +7,6 @@ pub fn derive_enum(input: TokenStream) -> TokenStream {
|
||||||
|
|
||||||
let struct_name = &ast.ident;
|
let struct_name = &ast.ident;
|
||||||
let variants = match ast.data {
|
let variants = match ast.data {
|
||||||
// syn::Data::Struct(syn::DataStruct {
|
|
||||||
// fields: syn::Fields::Named(named_fields),
|
|
||||||
// ..
|
|
||||||
// }) => named_fields,
|
|
||||||
syn::Data::Enum(syn::DataEnum { variants, .. }) => variants,
|
syn::Data::Enum(syn::DataEnum { variants, .. }) => variants,
|
||||||
_ => {
|
_ => {
|
||||||
return syn::Error::new(ast.span(), "Deriving Enum is only supported on enums")
|
return syn::Error::new(ast.span(), "Deriving Enum is only supported on enums")
|
||||||
|
@ -23,6 +19,8 @@ pub fn derive_enum(input: TokenStream) -> TokenStream {
|
||||||
// order, and the names are either just the variant name or a `#[name = "..."]` attribute in
|
// order, and the names are either just the variant name or a `#[name = "..."]` attribute in
|
||||||
// case the name should contain a space.
|
// case the name should contain a space.
|
||||||
let mut variant_names = Vec::new();
|
let mut variant_names = Vec::new();
|
||||||
|
// IDs are optional, but they must either be set for all variants or for none of them
|
||||||
|
let mut variant_ids = Vec::new();
|
||||||
let mut to_index_tokens = Vec::new();
|
let mut to_index_tokens = Vec::new();
|
||||||
let mut from_index_tokens = Vec::new();
|
let mut from_index_tokens = Vec::new();
|
||||||
for (variant_idx, variant) in variants.iter().enumerate() {
|
for (variant_idx, variant) in variants.iter().enumerate() {
|
||||||
|
@ -33,6 +31,7 @@ pub fn derive_enum(input: TokenStream) -> TokenStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut name_attr: Option<String> = None;
|
let mut name_attr: Option<String> = None;
|
||||||
|
let mut id_attr: Option<String> = None;
|
||||||
for attr in &variant.attrs {
|
for attr in &variant.attrs {
|
||||||
if attr.path.is_ident("name") {
|
if attr.path.is_ident("name") {
|
||||||
match attr.parse_meta() {
|
match attr.parse_meta() {
|
||||||
|
@ -57,6 +56,45 @@ pub fn derive_enum(input: TokenStream) -> TokenStream {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
} else if attr.path.is_ident("id") {
|
||||||
|
match attr.parse_meta() {
|
||||||
|
Ok(syn::Meta::NameValue(syn::MetaNameValue {
|
||||||
|
lit: syn::Lit::Str(s),
|
||||||
|
..
|
||||||
|
})) => {
|
||||||
|
if id_attr.is_none() {
|
||||||
|
id_attr = Some(s.value());
|
||||||
|
} else {
|
||||||
|
return syn::Error::new(attr.span(), "Duplicate id attribute")
|
||||||
|
.to_compile_error()
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return syn::Error::new(
|
||||||
|
attr.span(),
|
||||||
|
"The id attribute should be a key-value pair with a string argument: #[id = \"foo-bar\"]",
|
||||||
|
)
|
||||||
|
.to_compile_error()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDs must either be set for all variants or for none of them
|
||||||
|
match (id_attr, variant_idx == 0, variant_ids.is_empty()) {
|
||||||
|
(Some(id), true, true) | (Some(id), false, false) => {
|
||||||
|
variant_ids.push(id);
|
||||||
|
}
|
||||||
|
(None, _, true) => (),
|
||||||
|
_ => {
|
||||||
|
return syn::Error::new(
|
||||||
|
variant.span(),
|
||||||
|
"ID attributes must either be set for all variants or for none of them",
|
||||||
|
)
|
||||||
|
.to_compile_error()
|
||||||
|
.into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +108,12 @@ pub fn derive_enum(input: TokenStream) -> TokenStream {
|
||||||
from_index_tokens.push(quote! { #variant_idx => #struct_name::#variant_ident, });
|
from_index_tokens.push(quote! { #variant_idx => #struct_name::#variant_ident, });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let ids_tokens = if variant_ids.is_empty() {
|
||||||
|
quote! { None }
|
||||||
|
} else {
|
||||||
|
quote! { Some(&[#(#variant_ids),*]) }
|
||||||
|
};
|
||||||
|
|
||||||
let from_index_default_tokens = variants.first().map(|v| {
|
let from_index_default_tokens = variants.first().map(|v| {
|
||||||
let variant_ident = &v.ident;
|
let variant_ident = &v.ident;
|
||||||
quote! { _ => #struct_name::#variant_ident, }
|
quote! { _ => #struct_name::#variant_ident, }
|
||||||
|
@ -81,6 +125,10 @@ pub fn derive_enum(input: TokenStream) -> TokenStream {
|
||||||
&[#(#variant_names),*]
|
&[#(#variant_names),*]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ids() -> Option<&'static [&'static str]> {
|
||||||
|
#ids_tokens
|
||||||
|
}
|
||||||
|
|
||||||
fn to_index(self) -> usize {
|
fn to_index(self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
#(#to_index_tokens)*
|
#(#to_index_tokens)*
|
||||||
|
|
|
@ -3,8 +3,8 @@ use proc_macro::TokenStream;
|
||||||
mod enums;
|
mod enums;
|
||||||
mod params;
|
mod params;
|
||||||
|
|
||||||
/// Derive the `Enum` trait for your simple enum parameters. See `EnumParam` for more information.
|
/// Derive the `Enum` trait for simple enum parameters. See `EnumParam` for more information.
|
||||||
#[proc_macro_derive(Enum, attributes(name))]
|
#[proc_macro_derive(Enum, attributes(name, id))]
|
||||||
pub fn derive_enum(input: TokenStream) -> TokenStream {
|
pub fn derive_enum(input: TokenStream) -> TokenStream {
|
||||||
enums::derive_enum(input)
|
enums::derive_enum(input)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,12 @@ use super::{IntParam, Param, ParamFlags, ParamMut};
|
||||||
// Re-export the derive macro
|
// Re-export the derive macro
|
||||||
pub use nih_plug_derive::Enum;
|
pub use nih_plug_derive::Enum;
|
||||||
|
|
||||||
/// An enum usable with `EnumParam`. This trait can be derived. Variants are identified by their
|
/// An enum usable with `EnumParam`. This trait can be derived. Variants are identified either by a
|
||||||
/// **declaration order**. You can freely rename the variant names, but reordering them will break
|
/// stable _id_ (see below), or if those are not set then they are identifier by their **declaration
|
||||||
/// compatibility with existing presets. The variatn's name is used as the display name by default.
|
/// order**. If you don't provide IDs then you can freely rename the variant names, but reordering
|
||||||
/// If you want to override this, for instance, because it needs to contain spaces, then yo ucan use
|
/// them will break compatibility with existing presets. The variant's name is used as the display
|
||||||
/// the `$[name = "..."]` attribute:
|
/// name by default. If you want to override this, for instance, because it needs to contain spaces,
|
||||||
|
/// then you can use the `#[name = "..."]` attribute:
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// #[derive(Enum)]
|
/// #[derive(Enum)]
|
||||||
|
@ -26,12 +27,35 @@ pub use nih_plug_derive::Enum;
|
||||||
/// ContainsSpaces,
|
/// ContainsSpaces,
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// IDs can be added by adding the `#[id = "..."]` attribute to each variant:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// #[derive(Enum)]
|
||||||
|
/// enum Foo {
|
||||||
|
/// #[id = "bar"],
|
||||||
|
/// Bar,
|
||||||
|
/// #[id = "baz"],
|
||||||
|
/// Baz,
|
||||||
|
/// #[id = "contains-spaces"],
|
||||||
|
/// #[name = "Contains Spaces"]
|
||||||
|
/// ContainsSpaces,
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// You can safely move from not using IDs to using IDs without breaking patches, but you cannot go
|
||||||
|
/// back to not using IDs after that.
|
||||||
pub trait Enum {
|
pub trait Enum {
|
||||||
/// The human readable names for the variants. These are displayed in the GUI or parameter list,
|
/// The human readable names for the variants. These are displayed in the GUI or parameter list,
|
||||||
/// and also used for parsing text back to a parameter value. The length of this slice
|
/// and also used for parsing text back to a parameter value. The length of this slice
|
||||||
/// determines how many variants there are.
|
/// determines how many variants there are.
|
||||||
fn variants() -> &'static [&'static str];
|
fn variants() -> &'static [&'static str];
|
||||||
|
|
||||||
|
/// Optional identifiers for each variant. This makes it possible to reorder variants while
|
||||||
|
/// maintaining save compatibility (automation will still break of course). The length of this
|
||||||
|
/// slice needs to be equal to [`variants()`][Self::variants()].
|
||||||
|
fn ids() -> Option<&'static [&'static str]>;
|
||||||
|
|
||||||
/// Get the variant index (which may not be the same as the discriminator) corresponding to the
|
/// Get the variant index (which may not be the same as the discriminator) corresponding to the
|
||||||
/// active variant. The index needs to correspond to the name in
|
/// active variant. The index needs to correspond to the name in
|
||||||
/// [`variants()`][Self::variants()].
|
/// [`variants()`][Self::variants()].
|
||||||
|
@ -63,6 +87,11 @@ pub struct EnumParamInner {
|
||||||
pub(crate) inner: IntParam,
|
pub(crate) inner: IntParam,
|
||||||
/// The human readable variant names, obtained from [Enum::variants()].
|
/// The human readable variant names, obtained from [Enum::variants()].
|
||||||
variants: &'static [&'static str],
|
variants: &'static [&'static str],
|
||||||
|
/// Stable identifiers for the enum variants, obtained from [Enum::ids()]. These are optional,
|
||||||
|
/// but if they are set (they're either not set for any variant, or set for all variants) then
|
||||||
|
/// these identifiers are used when saving enum parameter values to the state. Otherwise the
|
||||||
|
/// index is used.
|
||||||
|
ids: Option<&'static [&'static str]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Enum + PartialEq> Display for EnumParam<T> {
|
impl<T: Enum + PartialEq> Display for EnumParam<T> {
|
||||||
|
@ -278,6 +307,7 @@ impl<T: Enum + PartialEq + 'static> EnumParam<T> {
|
||||||
/// parameter.
|
/// parameter.
|
||||||
pub fn new(name: impl Into<String>, default: T) -> Self {
|
pub fn new(name: impl Into<String>, default: T) -> Self {
|
||||||
let variants = T::variants();
|
let variants = T::variants();
|
||||||
|
let ids = T::ids();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
inner: EnumParamInner {
|
inner: EnumParamInner {
|
||||||
|
@ -290,6 +320,7 @@ impl<T: Enum + PartialEq + 'static> EnumParam<T> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
variants,
|
variants,
|
||||||
|
ids,
|
||||||
},
|
},
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
}
|
}
|
||||||
|
@ -341,4 +372,29 @@ impl EnumParamInner {
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.variants.len()
|
self.variants.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the stable ID for the parameter's current value according to
|
||||||
|
/// [`unmodulated_plain_value()`][Param::unmodulated_plain_value()]. Returns `None` if this enum
|
||||||
|
/// parameter doesn't have any stable IDs.
|
||||||
|
pub fn unmodulated_plain_id(&self) -> Option<&'static str> {
|
||||||
|
let ids = &self.ids?;
|
||||||
|
|
||||||
|
// The `Enum` trait is supposed to make sure this contains enough values
|
||||||
|
Some(ids[self.unmodulated_plain_value() as usize])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the parameter based on a serialized stable string identifier. Return whether the ID was
|
||||||
|
/// known and the parameter was set.
|
||||||
|
pub fn set_from_id(&mut self, id: &str) -> bool {
|
||||||
|
match self
|
||||||
|
.ids
|
||||||
|
.and_then(|ids| ids.iter().position(|candidate| *candidate == id))
|
||||||
|
{
|
||||||
|
Some(index) => {
|
||||||
|
self.set_plain_value(index as i32);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ pub enum ParamValue {
|
||||||
F32(f32),
|
F32(f32),
|
||||||
I32(i32),
|
I32(i32),
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
|
/// Only used for enum parameters that have the `#[id = "..."]` attribute set.
|
||||||
|
String(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A plugin's state so it can be restored at a later point. This object can be serialized and
|
/// A plugin's state so it can be restored at a later point. This object can be serialized and
|
||||||
|
@ -91,10 +93,14 @@ pub(crate) unsafe fn serialize_object<'a>(
|
||||||
ParamValue::Bool((*p).unmodulated_plain_value()),
|
ParamValue::Bool((*p).unmodulated_plain_value()),
|
||||||
),
|
),
|
||||||
ParamPtr::EnumParam(p) => (
|
ParamPtr::EnumParam(p) => (
|
||||||
// Enums are serialized based on the active variant's index (which may not be
|
// Enums are either serialized based on the active variant's index (which may not be
|
||||||
// the same as the discriminator)
|
// the same as the discriminator), or a custom set stable string ID. The latter
|
||||||
|
// allows the variants to be reordered.
|
||||||
param_id_str.clone(),
|
param_id_str.clone(),
|
||||||
ParamValue::I32((*p).unmodulated_plain_value()),
|
match (*p).unmodulated_plain_id() {
|
||||||
|
Some(id) => ParamValue::String(id.to_owned()),
|
||||||
|
None => ParamValue::I32((*p).unmodulated_plain_value()),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -145,11 +151,21 @@ pub(crate) unsafe fn deserialize_object(
|
||||||
(ParamPtr::FloatParam(p), ParamValue::F32(v)) => (*p).set_plain_value(*v),
|
(ParamPtr::FloatParam(p), ParamValue::F32(v)) => (*p).set_plain_value(*v),
|
||||||
(ParamPtr::IntParam(p), ParamValue::I32(v)) => (*p).set_plain_value(*v),
|
(ParamPtr::IntParam(p), ParamValue::I32(v)) => (*p).set_plain_value(*v),
|
||||||
(ParamPtr::BoolParam(p), ParamValue::Bool(v)) => (*p).set_plain_value(*v),
|
(ParamPtr::BoolParam(p), ParamValue::Bool(v)) => (*p).set_plain_value(*v),
|
||||||
// Enums are serialized based on the active variant's index (which may not be the same
|
// Enums are either serialized based on the active variant's index (which may not be the
|
||||||
// as the discriminator)
|
// same as the discriminator), or a custom set stable string ID. The latter allows the
|
||||||
|
// variants to be reordered.
|
||||||
(ParamPtr::EnumParam(p), ParamValue::I32(variant_idx)) => {
|
(ParamPtr::EnumParam(p), ParamValue::I32(variant_idx)) => {
|
||||||
(*p).set_plain_value(*variant_idx)
|
(*p).set_plain_value(*variant_idx)
|
||||||
}
|
}
|
||||||
|
(ParamPtr::EnumParam(p), ParamValue::String(id)) => {
|
||||||
|
let deserialized_enum = (*p).set_from_id(id);
|
||||||
|
nih_debug_assert!(
|
||||||
|
deserialized_enum,
|
||||||
|
"Unknown ID {:?} for enum parameter \"{}\"",
|
||||||
|
id,
|
||||||
|
param_id_str,
|
||||||
|
);
|
||||||
|
}
|
||||||
(param_ptr, param_value) => {
|
(param_ptr, param_value) => {
|
||||||
nih_debug_assert_failure!(
|
nih_debug_assert_failure!(
|
||||||
"Invalid serialized value {:?} for parameter \"{}\" ({:?})",
|
"Invalid serialized value {:?} for parameter \"{}\" ({:?})",
|
||||||
|
|
Loading…
Reference in a new issue