1
0
Fork 0

Add state filter method for performing migrations

This allows plugins to migrate old state, for instance when parameter
meanings change, new parameters are introduced, or parameter IDs have
been changed.
This commit is contained in:
Robbert van der Helm 2022-10-20 12:41:48 +02:00
parent bd295b7380
commit a9b1dd61fd
6 changed files with 48 additions and 21 deletions

View file

@ -9,6 +9,7 @@ use crate::context::{GuiContext, InitContext, ProcessContext};
use crate::midi::MidiConfig;
use crate::params::Params;
use crate::wrapper::clap::features::ClapFeature;
use crate::wrapper::state::PluginState;
/// Basic functionality that needs to be implemented by a plugin. The wrappers will use this to
/// expose the plugin in a particular plugin format.
@ -100,6 +101,18 @@ pub trait Plugin: Default + Send + Sync + 'static {
None
}
/// This function is always called just before a [`PluginState`] is loaded. This lets you
/// directly modify old plugin state to perform migrations based on the [`PluginState::version`]
/// field. Some examples of use cases for this are renaming parameter indices, remapping
/// parameter values, and preserving old preset compatibility when introducing new parameters
/// with default values that would otherwise change the sound of a preset. Keep in mind that
/// automation may still be broken in the first two use cases.
///
/// # Note
///
/// This is an advanced feature that the vast majority of plugins won't need to implement.
fn filter_state(state: &mut PluginState) {}
//
// The following functions follow the lifetime of the plugin.
//

View file

@ -1588,8 +1588,8 @@ impl<P: ClapPlugin> Wrapper<P> {
// Otherwise we'll set the state right here and now, since this function should be
// called from a GUI thread
unsafe {
state::deserialize_object(
&state,
state::deserialize_object::<P>(
&mut state,
self.params.clone(),
state::make_params_getter(&self.param_by_hash, &self.param_id_to_hash),
self.current_buffer_config.load().as_ref(),
@ -2231,9 +2231,9 @@ impl<P: ClapPlugin> Wrapper<P> {
// FIXME: Zero capacity channels allocate on receiving, find a better alternative that
// doesn't do that
let updated_state = permit_alloc(|| wrapper.updated_state_receiver.try_recv());
if let Ok(state) = updated_state {
state::deserialize_object(
&state,
if let Ok(mut state) = updated_state {
state::deserialize_object::<P>(
&mut state,
wrapper.params.clone(),
state::make_params_getter(&wrapper.param_by_hash, &wrapper.param_id_to_hash),
wrapper.current_buffer_config.load().as_ref(),
@ -3130,7 +3130,7 @@ impl<P: ClapPlugin> Wrapper<P> {
}
read_buffer.set_len(length as usize);
let success = state::deserialize_json(
let success = state::deserialize_json::<P>(
&read_buffer,
wrapper.params.clone(),
state::make_params_getter(&wrapper.param_by_hash, &wrapper.param_id_to_hash),

View file

@ -435,10 +435,10 @@ impl<P: Plugin, B: Backend> Wrapper<P, B> {
// FIXME: Zero capacity channels allocate on receiving, find a better
// alternative that doesn't do that
let updated_state = permit_alloc(|| self.updated_state_receiver.try_recv());
if let Ok(state) = updated_state {
if let Ok(mut state) = updated_state {
unsafe {
state::deserialize_object(
&state,
state::deserialize_object::<P>(
&mut state,
self.params.clone(),
|param_id| self.param_map.get(param_id).copied(),
Some(&self.buffer_config),

View file

@ -159,12 +159,18 @@ pub(crate) unsafe fn serialize_json<'a, P: Plugin>(
///
/// Make sure to reinitialize plugin after deserializing the state so it can react to the new
/// parameter values. The smoothers have already been reset by this function.
pub(crate) unsafe fn deserialize_object(
state: &PluginState,
///
/// The [`Plugin`] argument is used to call [`Plugin::filter_state()`] just before loading the
/// state.
pub(crate) unsafe fn deserialize_object<P: Plugin>(
state: &mut PluginState,
plugin_params: Arc<dyn Params>,
params_getter: impl Fn(&str) -> Option<ParamPtr>,
current_buffer_config: Option<&BufferConfig>,
) -> bool {
// This lets the plugin perform migrations on old state if needed
P::filter_state(state);
let sample_rate = current_buffer_config.map(|c| c.sample_rate);
for (param_id_str, param_value) in &state.params {
let param_ptr = match params_getter(param_id_str.as_str()) {
@ -223,14 +229,17 @@ pub(crate) unsafe fn deserialize_object(
///
/// Make sure to reinitialize plugin after deserializing the state so it can react to the new
/// parameter values. The smoothers have already been reset by this function.
pub(crate) unsafe fn deserialize_json(
///
/// The [`Plugin`] argument is used to call [`Plugin::filter_state()`] just before loading the
/// state.
pub(crate) unsafe fn deserialize_json<P: Plugin>(
state: &[u8],
plugin_params: Arc<dyn Params>,
params_getter: impl Fn(&str) -> Option<ParamPtr>,
current_buffer_config: Option<&BufferConfig>,
) -> bool {
#[cfg(feature = "zstd")]
let state: PluginState = match zstd::decode_all(state) {
let mut state: PluginState = match zstd::decode_all(state) {
Ok(decompressed) => match serde_json::from_slice(decompressed.as_slice()) {
Ok(s) => {
nih_log!("Deserialized compressed");
@ -261,7 +270,7 @@ pub(crate) unsafe fn deserialize_json(
};
#[cfg(not(feature = "zstd"))]
let state: PluginState = match serde_json::from_slice(state) {
let mut state: PluginState = match serde_json::from_slice(state) {
Ok(s) => s,
Err(err) => {
nih_debug_assert_failure!("Error while deserializing state: {}", err);
@ -269,5 +278,10 @@ pub(crate) unsafe fn deserialize_json(
}
};
deserialize_object(&state, plugin_params, params_getter, current_buffer_config)
deserialize_object::<P>(
&mut state,
plugin_params,
params_getter,
current_buffer_config,
)
}

View file

@ -458,8 +458,8 @@ impl<P: Vst3Plugin> WrapperInner<P> {
// Otherwise we'll set the state right here and now, since this function should be
// called from a GUI thread
unsafe {
state::deserialize_object(
&state,
state::deserialize_object::<P>(
&mut state,
self.params.clone(),
state::make_params_getter(&self.param_by_hash, &self.param_id_to_hash),
self.current_buffer_config.load().as_ref(),

View file

@ -504,7 +504,7 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
return kResultFalse;
}
let success = state::deserialize_json(
let success = state::deserialize_json::<P>(
&read_buffer,
self.inner.params.clone(),
state::make_params_getter(&self.inner.param_by_hash, &self.inner.param_id_to_hash),
@ -1769,9 +1769,9 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
// FIXME: Zero capacity channels allocate on receiving, find a better alternative that
// doesn't do that
let updated_state = permit_alloc(|| self.inner.updated_state_receiver.try_recv());
if let Ok(state) = updated_state {
state::deserialize_object(
&state,
if let Ok(mut state) = updated_state {
state::deserialize_object::<P>(
&mut state,
self.inner.params.clone(),
state::make_params_getter(
&self.inner.param_by_hash,