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:
parent
bd295b7380
commit
a9b1dd61fd
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue