From 5e6e920418fff472c21a109634fee2378eb4770b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 2 Jul 2022 19:10:56 +0200 Subject: [PATCH] Use BTreeMaps in the state This ensures that the order is consistent when saving the same state file multiple times. --- BREAKING_CHANGES.md | 8 ++++++++ nih_plug_derive/src/params.rs | 6 +++--- src/param/internals.rs | 8 ++++---- src/wrapper/state.rs | 10 ++++++---- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index 04d1ec7d..cbd31bcb 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -6,6 +6,14 @@ new and what's changed, this document lists all breaking changes in reverse chronological order. If a new feature did not require any changes to existing code then it will not be listed here. +## [2022-07-02] + +- The `Params::serialize_fields()` and `Params::deserialize_fields()` methods + and the `State` struct now use `BTreeMap`s instead of `HashMap`s so the order + is consistent the plugin's state to JSON multiple times. These things are part + of NIH-plug's internals, so unless you're implementing the `Params` trait by + hand you will not notice any changes. + ## [2022-06-01] - The `ClapPlugin::CLAP_FEATURES` field now uses an array of `ClapFeature` diff --git a/nih_plug_derive/src/params.rs b/nih_plug_derive/src/params.rs index 17a39289..077c7138 100644 --- a/nih_plug_derive/src/params.rs +++ b/nih_plug_derive/src/params.rs @@ -255,8 +255,8 @@ pub fn derive_params(input: TokenStream) -> TokenStream { param_map } - fn serialize_fields(&self) -> ::std::collections::HashMap { - let mut serialized = ::std::collections::HashMap::new(); + fn serialize_fields(&self) -> ::std::collections::BTreeMap { + let mut serialized = ::std::collections::BTreeMap::new(); #(#field_serialize_tokens)* let nested_params_fields: &[&dyn Params] = &[#(&self.#nested_params_field_idents),*]; @@ -267,7 +267,7 @@ pub fn derive_params(input: TokenStream) -> TokenStream { serialized } - fn deserialize_fields(&self, serialized: &::std::collections::HashMap) { + fn deserialize_fields(&self, serialized: &::std::collections::BTreeMap) { for (field_name, data) in serialized { match field_name.as_str() { #(#field_deserialize_tokens)* diff --git a/src/param/internals.rs b/src/param/internals.rs index 2e0aef39..e403d1f5 100644 --- a/src/param/internals.rs +++ b/src/param/internals.rs @@ -1,6 +1,6 @@ //! Implementation details for the parameter management. -use std::collections::HashMap; +use std::collections::BTreeMap; use super::{Param, ParamFlags, ParamMut}; @@ -59,8 +59,8 @@ pub unsafe trait Params: 'static + Send + Sync { /// 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 /// recalled later. This uses [`serialize_field()`] under the hood. - fn serialize_fields(&self) -> HashMap { - HashMap::new() + fn serialize_fields(&self) -> BTreeMap { + BTreeMap::new() } /// Restore all fields marked with `#[persist = "stable_name"]` from a hashmap created by @@ -69,7 +69,7 @@ pub unsafe trait Params: 'static + Send + Sync { /// This gets called when the plugin's state is being restored. This uses [deserialize_field()] /// under the hood. #[allow(unused_variables)] - fn deserialize_fields(&self, serialized: &HashMap) {} + fn deserialize_fields(&self, serialized: &BTreeMap) {} } /// Internal pointers to parameters. This is an implementation detail used by the wrappers for type diff --git a/src/wrapper/state.rs b/src/wrapper/state.rs index 917e21e5..8deec79c 100644 --- a/src/wrapper/state.rs +++ b/src/wrapper/state.rs @@ -2,7 +2,7 @@ //! to plugins through the [`GuiContext`][crate::prelude::GuiContext]. use serde::{Deserialize, Serialize}; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::sync::Arc; use crate::param::internals::{ParamPtr, Params}; @@ -25,19 +25,21 @@ pub enum ParamValue { /// A plugin's state so it can be restored at a later point. This object can be serialized and /// deserialized using serde. +/// +/// The fields are stored as `BTreeMap`s so the order in the serialized file is consistent. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PluginState { /// The plugin's parameter values. These are stored unnormalized. This mean sthe old values will /// be recalled when when the parameter's range gets increased. Doing so may still mess with /// parameter automation though, depending on how the host impelments that. - pub params: HashMap, + pub params: BTreeMap, /// Arbitrary fields that should be persisted together with the plugin's parameters. Any field /// on the [`Params`][crate::param::internals::Params] struct that's annotated with `#[persist = /// "stable_name"]` will be persisted this way. /// /// The individual fields are also serialized as JSON so they can safely be restored /// independently of the other fields. - pub fields: HashMap, + pub fields: BTreeMap, } /// Create a parameters iterator from the hashtables stored in the plugin wrappers. This avoids @@ -77,7 +79,7 @@ pub(crate) unsafe fn serialize_object<'a>( // We'll serialize parameter values as a simple `string_param_id: display_value` map. // NOTE: If the plugin is being modulated (and the plugin is a CLAP plugin in Bitwig Studio), // then this should save the values without any modulation applied to it - let params: HashMap<_, _> = params_iter + let params: BTreeMap<_, _> = params_iter .into_iter() .map(|(param_id_str, param_ptr)| match param_ptr { ParamPtr::FloatParam(p) => (