Add optional Zstandard compression for state
This can be particularly useful when using the persistent fields feature to store JSON or other large textual documents.
This commit is contained in:
parent
c412d3cca6
commit
1bb1cde913
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
|
@ -51,7 +51,7 @@ jobs:
|
|||
# conflicting iced features. We also don't want to use `--workspace`
|
||||
# here because that would also document our plugins and binary crates.
|
||||
args: >-
|
||||
--features docs,simd,standalone --no-deps
|
||||
--features docs,simd,standalone,zstd --no-deps
|
||||
-p nih_plug
|
||||
-p nih_plug_derive
|
||||
-p nih_plug_egui
|
||||
|
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -60,7 +60,7 @@ jobs:
|
|||
command: build
|
||||
# Don't use --all-features as that will enable a whole bunch of
|
||||
# conflicting iced features
|
||||
args: --workspace --features "simd,standalone"
|
||||
args: --workspace --features "simd,standalone,zstd"
|
||||
- name: Run the tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
|
|
42
Cargo.lock
generated
42
Cargo.lock
generated
|
@ -449,6 +449,9 @@ name = "cc"
|
|||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -1992,6 +1995,15 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
version = "0.1.22"
|
||||
|
@ -2397,6 +2409,7 @@ dependencies = [
|
|||
"widestring",
|
||||
"win_dbg_logger",
|
||||
"windows",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4686,3 +4699,32 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"nih_plug_xtask",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.11.2+zstd.1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "5.0.2+zstd.1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.1+zstd.1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -49,7 +49,7 @@ assert_process_allocs = ["dep:assert_no_alloc"]
|
|||
# Enables an export target for standalone binaries through the
|
||||
# `nih_export_standalone()` function. Disabled by default as this requires
|
||||
# building additional dependencies for audio and MIDI handling.
|
||||
standalone = ["dep:anyhow", "dep:baseview", "dep:clap", "dep:jack"]
|
||||
standalone = ["dep:baseview", "dep:clap", "dep:jack"]
|
||||
# Enables the `nih_export_vst3!()` macro. Enabled by default. This feature
|
||||
# exists mostly for GPL-compliance reasons, since even if you don't use the VST3
|
||||
# wrapper you might otherwise still include a couple (unused) symbols from the
|
||||
|
@ -58,12 +58,19 @@ vst3 = ["dep:vst3-sys"]
|
|||
# Add adapters to the Buffer object for reading the channel data to and from
|
||||
# `std::simd` vectors. Requires a nightly compiler.
|
||||
simd = []
|
||||
# Compress plugin state using the Zstandard algorithm. Loading uncompressed
|
||||
# state is still supported so existing state will still load after enabling this
|
||||
# feature for a plugin, but it can not be disabled again without losing state
|
||||
# compatibility.
|
||||
zstd = ["dep:zstd"]
|
||||
|
||||
# Only relevant when generating docs, adds the `doc_auto_cfg` nightly feature
|
||||
docs = []
|
||||
|
||||
[dependencies]
|
||||
nih_plug_derive = { path = "nih_plug_derive" }
|
||||
|
||||
anyhow = "1.0"
|
||||
atomic_float = "0.1"
|
||||
atomic_refcell = "0.1"
|
||||
backtrace = "0.3.65"
|
||||
|
@ -85,7 +92,6 @@ widestring = "1.0.0-beta.1"
|
|||
assert_no_alloc = { version = "1.1", optional = true }
|
||||
|
||||
# Used for the `standalone` feature
|
||||
anyhow = { version = "1.0", optional = true }
|
||||
# NOTE: OpenGL support is not needed here, but rust-analyzer gets confused when
|
||||
# some crates do use it and others don't
|
||||
baseview = { git = "https://github.com/robbert-vdh/baseview.git", branch = "feature/resize", features = ["opengl"], optional = true }
|
||||
|
@ -97,6 +103,9 @@ jack = { git = "https://github.com/robbert-vdh/rust-jack.git", branch = "feature
|
|||
# Used for the `vst3` feature
|
||||
vst3-sys = { git = "https://github.com/robbert-vdh/vst3-sys.git", branch = "fix/note-off-event", optional = true }
|
||||
|
||||
# Used for the `zstd` feature
|
||||
zstd = { version = "0.11.2", optional = true }
|
||||
|
||||
[target.'cfg(all(target_family = "unix", not(target_os = "macos")))'.dependencies]
|
||||
libc = "0.2.124"
|
||||
|
||||
|
|
|
@ -116,6 +116,8 @@ Scroll down for more information on the plugin framework.
|
|||
enabled by setting the `Plugin::SAMPLE_ACCURATE_AUTOMATION` constant to
|
||||
`true`.
|
||||
- Support for CLAP's polyphonic modulation on a per-parameter basis.
|
||||
- Optional support for compressing the human readable JSON state files using
|
||||
[Zstandard](https://en.wikipedia.org/wiki/Zstd).
|
||||
- Comes with adapters for popular Rust GUI frameworks as well as some basic
|
||||
widgets for them that integrate with NIH-plug's parameter system. Currently
|
||||
there's support for [egui](nih_plug_egui), [iced](nih_plug_iced) and
|
||||
|
|
|
@ -9,6 +9,6 @@ license = "ISC"
|
|||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
nih_plug = { path = "../../../", features = ["assert_process_allocs"] }
|
||||
nih_plug = { path = "../../../", features = ["assert_process_allocs", "zstd"] }
|
||||
|
||||
parking_lot = "0.12"
|
||||
|
|
|
@ -3139,7 +3139,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
true
|
||||
}
|
||||
Err(err) => {
|
||||
nih_debug_assert_failure!("Could not save state: {}", err);
|
||||
nih_debug_assert_failure!("Could not save state: {:#}", err);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! Utilities for saving a [crate::plugin::Plugin]'s state. The actual state object is also exposed
|
||||
//! to plugins through the [`GuiContext`][crate::prelude::GuiContext].
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::sync::Arc;
|
||||
|
@ -115,13 +116,24 @@ pub(crate) unsafe fn serialize_object<'a>(
|
|||
}
|
||||
|
||||
/// Serialize a plugin's state to a vector containing JSON data. This can (and should) be shared
|
||||
/// across plugin formats.
|
||||
/// across plugin formats. If the `zstd` feature is enabled, then the state will be compressed using
|
||||
/// Zstandard.
|
||||
pub(crate) unsafe fn serialize_json<'a>(
|
||||
plugin_params: Arc<dyn Params>,
|
||||
params_iter: impl IntoIterator<Item = (&'a String, ParamPtr)>,
|
||||
) -> serde_json::Result<Vec<u8>> {
|
||||
) -> Result<Vec<u8>> {
|
||||
let plugin_state = serialize_object(plugin_params, params_iter);
|
||||
serde_json::to_vec(&plugin_state)
|
||||
let json = serde_json::to_vec(&plugin_state).context("Could not format as JSON")?;
|
||||
|
||||
#[cfg(feature = "zstd")]
|
||||
{
|
||||
zstd::encode_all(json.as_slice(), zstd::DEFAULT_COMPRESSION_LEVEL)
|
||||
.context("Could not compress state")
|
||||
}
|
||||
#[cfg(not(feature = "zstd"))]
|
||||
{
|
||||
Ok(json)
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserialize a plugin's state from a [`PluginState`] object. This is used to allow the plugin to
|
||||
|
@ -191,8 +203,9 @@ pub(crate) unsafe fn deserialize_object(
|
|||
true
|
||||
}
|
||||
|
||||
/// Deserialize a plugin's state from a vector containing JSON data. This can (and should) be shared
|
||||
/// across plugin formats. Returns `false` and logs an error if the state could not be deserialized.
|
||||
/// Deserialize a plugin's state from a vector containing (compressed) JSON data. This can (and
|
||||
/// should) be shared across plugin formats. Returns `false` and logs an error if the state could
|
||||
/// not be deserialized. If the `zstd` feature is enabled, then this can
|
||||
///
|
||||
/// 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.
|
||||
|
@ -202,6 +215,38 @@ pub(crate) unsafe fn deserialize_json(
|
|||
params_getter: impl Fn(&str) -> Option<ParamPtr>,
|
||||
current_buffer_config: Option<&BufferConfig>,
|
||||
) -> bool {
|
||||
#[cfg(feature = "zstd")]
|
||||
let state: PluginState = match zstd::decode_all(state) {
|
||||
Ok(decompressed) => match serde_json::from_slice(decompressed.as_slice()) {
|
||||
Ok(s) => {
|
||||
nih_log!("Deserialized compressed");
|
||||
s
|
||||
}
|
||||
Err(err) => {
|
||||
nih_debug_assert_failure!("Error while deserializing state: {}", err);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
// Uncompressed state files can still be loaded after enabling this feature to prevent
|
||||
// breaking existing plugin instances
|
||||
Err(zstd_err) => match serde_json::from_slice(state) {
|
||||
Ok(s) => {
|
||||
nih_log!("Deserialized uncompressed");
|
||||
s
|
||||
}
|
||||
Err(json_err) => {
|
||||
nih_debug_assert_failure!(
|
||||
"Error while deserializing state as either compressed or uncompressed state: \
|
||||
{}, {}",
|
||||
zstd_err,
|
||||
json_err
|
||||
);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "zstd"))]
|
||||
let state: PluginState = match serde_json::from_slice(state) {
|
||||
Ok(s) => s,
|
||||
Err(err) => {
|
||||
|
|
|
@ -555,7 +555,7 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
|
|||
kResultOk
|
||||
}
|
||||
Err(err) => {
|
||||
nih_debug_assert_failure!("Could not save state: {}", err);
|
||||
nih_debug_assert_failure!("Could not save state: {:#}", err);
|
||||
kResultFalse
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue