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 variants = match ast.data {
|
||||
// syn::Data::Struct(syn::DataStruct {
|
||||
// fields: syn::Fields::Named(named_fields),
|
||||
// ..
|
||||
// }) => named_fields,
|
||||
syn::Data::Enum(syn::DataEnum { variants, .. }) => variants,
|
||||
_ => {
|
||||
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
|
||||
// case the name should contain a space.
|
||||
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 from_index_tokens = Vec::new();
|
||||
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 id_attr: Option<String> = None;
|
||||
for attr in &variant.attrs {
|
||||
if attr.path.is_ident("name") {
|
||||
match attr.parse_meta() {
|
||||
|
@ -57,6 +56,45 @@ pub fn derive_enum(input: TokenStream) -> TokenStream {
|
|||
.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, });
|
||||
}
|
||||
|
||||
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 variant_ident = &v.ident;
|
||||
quote! { _ => #struct_name::#variant_ident, }
|
||||
|
@ -81,6 +125,10 @@ pub fn derive_enum(input: TokenStream) -> TokenStream {
|
|||
&[#(#variant_names),*]
|
||||
}
|
||||
|
||||
fn ids() -> Option<&'static [&'static str]> {
|
||||
#ids_tokens
|
||||
}
|
||||
|
||||
fn to_index(self) -> usize {
|
||||
match self {
|
||||
#(#to_index_tokens)*
|
||||
|
|
|
@ -3,8 +3,8 @@ use proc_macro::TokenStream;
|
|||
mod enums;
|
||||
mod params;
|
||||
|
||||
/// Derive the `Enum` trait for your simple enum parameters. See `EnumParam` for more information.
|
||||
#[proc_macro_derive(Enum, attributes(name))]
|
||||
/// Derive the `Enum` trait for simple enum parameters. See `EnumParam` for more information.
|
||||
#[proc_macro_derive(Enum, attributes(name, id))]
|
||||
pub fn derive_enum(input: TokenStream) -> TokenStream {
|
||||
enums::derive_enum(input)
|
||||
}
|
||||
|
|
|
@ -11,11 +11,12 @@ use super::{IntParam, Param, ParamFlags, ParamMut};
|
|||
// Re-export the derive macro
|
||||
pub use nih_plug_derive::Enum;
|
||||
|
||||
/// An enum usable with `EnumParam`. This trait can be derived. Variants are identified by their
|
||||
/// **declaration order**. You can freely rename the variant names, but reordering them will break
|
||||
/// compatibility with existing presets. The variatn's name is used as the display name by default.
|
||||
/// If you want to override this, for instance, because it needs to contain spaces, then yo ucan use
|
||||
/// the `$[name = "..."]` attribute:
|
||||
/// An enum usable with `EnumParam`. This trait can be derived. Variants are identified either by a
|
||||
/// stable _id_ (see below), or if those are not set then they are identifier by their **declaration
|
||||
/// order**. If you don't provide IDs then you can freely rename the variant names, but reordering
|
||||
/// them will break compatibility with existing presets. The variant's name is used as the display
|
||||
/// 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
|
||||
/// #[derive(Enum)]
|
||||
|
@ -26,12 +27,35 @@ pub use nih_plug_derive::Enum;
|
|||
/// 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 {
|
||||
/// 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
|
||||
/// determines how many variants there are.
|
||||
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
|
||||
/// active variant. The index needs to correspond to the name in
|
||||
/// [`variants()`][Self::variants()].
|
||||
|
@ -63,6 +87,11 @@ pub struct EnumParamInner {
|
|||
pub(crate) inner: IntParam,
|
||||
/// The human readable variant names, obtained from [Enum::variants()].
|
||||
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> {
|
||||
|
@ -278,6 +307,7 @@ impl<T: Enum + PartialEq + 'static> EnumParam<T> {
|
|||
/// parameter.
|
||||
pub fn new(name: impl Into<String>, default: T) -> Self {
|
||||
let variants = T::variants();
|
||||
let ids = T::ids();
|
||||
|
||||
Self {
|
||||
inner: EnumParamInner {
|
||||
|
@ -290,6 +320,7 @@ impl<T: Enum + PartialEq + 'static> EnumParam<T> {
|
|||
},
|
||||
),
|
||||
variants,
|
||||
ids,
|
||||
},
|
||||
_marker: PhantomData,
|
||||
}
|
||||
|
@ -341,4 +372,29 @@ impl EnumParamInner {
|
|||
pub fn len(&self) -> usize {
|
||||
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),
|
||||
I32(i32),
|
||||
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
|
||||
|
@ -91,10 +93,14 @@ pub(crate) unsafe fn serialize_object<'a>(
|
|||
ParamValue::Bool((*p).unmodulated_plain_value()),
|
||||
),
|
||||
ParamPtr::EnumParam(p) => (
|
||||
// Enums are serialized based on the active variant's index (which may not be
|
||||
// the same as the discriminator)
|
||||
// Enums are either serialized based on the active variant's index (which may not be
|
||||
// the same as the discriminator), or a custom set stable string ID. The latter
|
||||
// allows the variants to be reordered.
|
||||
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();
|
||||
|
@ -145,11 +151,21 @@ pub(crate) unsafe fn deserialize_object(
|
|||
(ParamPtr::FloatParam(p), ParamValue::F32(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),
|
||||
// Enums are serialized based on the active variant's index (which may not be the same
|
||||
// as the discriminator)
|
||||
// Enums are either serialized based on the active variant's index (which may not be the
|
||||
// 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)) => {
|
||||
(*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) => {
|
||||
nih_debug_assert_failure!(
|
||||
"Invalid serialized value {:?} for parameter \"{}\" ({:?})",
|
||||
|
|
Loading…
Reference in a new issue