Skip to main content

truce_core/
custom_state.rs

1//! Custom state serialization for plugin-specific persistent data.
2//!
3//! Use `#[derive(State)]` on a struct to auto-generate binary serialization:
4//!
5//! ```ignore
6//! #[derive(State, Default)]
7//! pub struct MyState {
8//!     pub instance_name: String,
9//!     pub view_mode: u8,
10//!     pub selected_ids: Vec<u32>,
11//! }
12//! ```
13//!
14//! Then persist it through your plugin's `snapshot_into`/`load_state` -
15//! the real-time-safe custom-state path. `snapshot_into` publishes into a
16//! lock-free slot the host reads without stalling audio, and CLAP / VST3
17//! / AU persist from it:
18//!
19//! ```ignore
20//! fn snapshot_into(state: &Self::DspState, buf: &mut Vec<u8>) -> bool {
21//!     buf.extend_from_slice(&state.persistent.serialize());
22//!     true
23//! }
24//! fn load_state(state: &mut Self::DspState, data: &[u8]) -> Result<(), StateLoadError> {
25//!     match MyState::deserialize(data) {
26//!         Some(s) => { state.persistent = s; Ok(()) }
27//!         None => Err(StateLoadError::Malformed("MyState")),
28//!     }
29//! }
30//! ```
31//!
32//! For MB-scale state (a sampler's audio, big wavetables), publish it off
33//! the audio thread via `InitContext::snapshot_publisher()` instead. The
34//! older `save_state` still works, but on CLAP / VST3 / AU it runs on the
35//! audio thread, so prefer `snapshot_into`.
36//!
37//! ## Schema evolution
38//!
39//! The codec is **keyed by field name** (like `#[persist]`), so evolving
40//! the struct is safe: adding, removing, *and* reordering fields all keep
41//! every surviving field's value. A field absent from an older blob loads
42//! as its `Default`; a field present in the blob but no longer declared is
43//! ignored. Two fields whose names collide under the key hash are rejected
44//! at compile time. Pre-keyed blobs (saved by an older truce) still load
45//! through a legacy positional path, so existing sessions are unaffected.
46
47/// Re-exported so plugin authors name the lock-free `#[persist]` cell
48/// as `AtomicCell` through the prelude, without depending on crossbeam.
49pub use crossbeam_utils::atomic::AtomicCell;
50
51/// Cursor for reading binary state data.
52pub struct StateCursor<'a> {
53    data: &'a [u8],
54    pos: usize,
55}
56
57impl<'a> StateCursor<'a> {
58    #[must_use]
59    pub fn new(data: &'a [u8]) -> Self {
60        Self { data, pos: 0 }
61    }
62
63    #[must_use]
64    pub fn remaining(&self) -> usize {
65        self.data.len().saturating_sub(self.pos)
66    }
67
68    pub fn read_bytes(&mut self, n: usize) -> Option<&'a [u8]> {
69        if self.pos + n > self.data.len() {
70            return None;
71        }
72        let slice = &self.data[self.pos..self.pos + n];
73        self.pos += n;
74        Some(slice)
75    }
76
77    /// Skip the next field (reads its encoded size and advances past it).
78    /// Returns false if the data is malformed.
79    ///
80    /// # Panics
81    ///
82    /// Does not panic - the `expect` inside is unreachable because
83    /// `read_bytes(4)` only returns `Some` when the slice is exactly
84    /// 4 bytes long.
85    pub fn skip_field(&mut self) -> bool {
86        // Fields are prefixed with a u32 byte length by the derive macro.
87        if let Some(bytes) = self.read_bytes(4) {
88            // `read_bytes(4)` returns `Some(slice of length 4)` or
89            // `None`, so the `try_into::<[u8; 4]>()` here cannot fail.
90            // The `expect` documents that invariant for readers.
91            let len = u32::from_le_bytes(
92                bytes
93                    .try_into()
94                    .expect("read_bytes(4) returned a slice of unexpected length"),
95            ) as usize;
96            if self.pos + len <= self.data.len() {
97                self.pos += len;
98                return true;
99            }
100        }
101        false
102    }
103}
104
105/// Trait for types that can be serialized as a single state field.
106///
107/// Implemented for primitives, `String`, `Vec<T>`, and `Option<T>`.
108pub trait StateField: Sized {
109    fn write_field(&self, buf: &mut Vec<u8>);
110    fn read_field(cursor: &mut StateCursor) -> Option<Self>;
111}
112
113/// Trait for custom plugin state structs.
114///
115/// Derive with `#[derive(State)]`. The struct must also implement `Default`
116/// so a field absent from an older (or reordered / trimmed) blob loads as
117/// its default - the codec matches fields by name, so schema evolution is
118/// safe in every direction (see the module docs).
119pub trait State: Sized + Default {
120    /// Serialize into `buf`. Clears `buf` first, then reuses its
121    /// capacity - calling this repeatedly with the same buffer is
122    /// allocation-free once warmed, so it is the form to use on the
123    /// audio thread (e.g. `PluginLogic::snapshot_into`).
124    fn serialize_into(&self, buf: &mut Vec<u8>);
125
126    /// Serialize to a fresh `Vec`. Convenience wrapper over
127    /// [`Self::serialize_into`]; allocates, so prefer `serialize_into`
128    /// on the real-time path.
129    #[must_use]
130    fn serialize(&self) -> Vec<u8> {
131        let mut buf = Vec::new();
132        self.serialize_into(&mut buf);
133        buf
134    }
135
136    fn deserialize(data: &[u8]) -> Option<Self>;
137}
138
139/// A `#[persist]` field on a `#[derive(Params)]` struct: an
140/// interior-mutable, `Sync` wrapper around a [`StateField`] value that
141/// the host saves alongside the parameter values (session / preset) and
142/// restores on load. Implemented for `RwLock<T>` / `Mutex<T>` where
143/// `T: StateField` (a primitive, `String`, `Vec`, `Option`, or a
144/// `#[derive(State)]` struct - the derive also emits a `StateField`
145/// impl). Interior mutability is required because a load reaches the
146/// field through `&Params` (the store is shared via `Arc<Params>`).
147pub trait PersistField {
148    /// Append the current value's bytes.
149    fn persist_write(&self, buf: &mut Vec<u8>);
150    /// Read a value from `cursor` and store it in place. A short or
151    /// malformed read leaves the current value untouched.
152    fn persist_read(&self, cursor: &mut StateCursor);
153}
154
155impl<T: StateField> PersistField for std::sync::RwLock<T> {
156    fn persist_write(&self, buf: &mut Vec<u8>) {
157        if let Ok(guard) = self.read() {
158            guard.write_field(buf);
159        }
160    }
161    fn persist_read(&self, cursor: &mut StateCursor) {
162        if let Some(value) = T::read_field(cursor)
163            && let Ok(mut guard) = self.write()
164        {
165            *guard = value;
166        }
167    }
168}
169
170impl<T: StateField> PersistField for std::sync::Mutex<T> {
171    fn persist_write(&self, buf: &mut Vec<u8>) {
172        if let Ok(guard) = self.lock() {
173            guard.write_field(buf);
174        }
175    }
176    fn persist_read(&self, cursor: &mut StateCursor) {
177        if let Some(value) = T::read_field(cursor)
178            && let Ok(mut guard) = self.lock()
179        {
180            *guard = value;
181        }
182    }
183}
184
185/// Lock-free-friendly cell for `Copy` config: a persisted `f32`, small
186/// enum, or index reads as `AtomicCell<T>` instead of sitting behind a
187/// `Mutex`. `AtomicCell` is genuinely atomic for word-sized types and
188/// falls back to an internal lock for larger ones.
189impl<T: StateField + Copy> PersistField for AtomicCell<T> {
190    fn persist_write(&self, buf: &mut Vec<u8>) {
191        self.load().write_field(buf);
192    }
193    fn persist_read(&self, cursor: &mut StateCursor) {
194        if let Some(value) = T::read_field(cursor) {
195            self.store(value);
196        }
197    }
198}
199
200// ---------------------------------------------------------------------------
201// StateField implementations for primitives
202// ---------------------------------------------------------------------------
203
204macro_rules! impl_state_field_int {
205    ($($ty:ty),*) => {
206        $(
207            impl StateField for $ty {
208                fn write_field(&self, buf: &mut Vec<u8>) {
209                    buf.extend_from_slice(&self.to_le_bytes());
210                }
211                fn read_field(cursor: &mut StateCursor) -> Option<Self> {
212                    let bytes = cursor.read_bytes(std::mem::size_of::<Self>())?;
213                    Some(Self::from_le_bytes(bytes.try_into().ok()?))
214                }
215            }
216        )*
217    };
218}
219
220impl_state_field_int!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64);
221
222impl StateField for bool {
223    fn write_field(&self, buf: &mut Vec<u8>) {
224        buf.push(u8::from(*self));
225    }
226    fn read_field(cursor: &mut StateCursor) -> Option<Self> {
227        let b = cursor.read_bytes(1)?;
228        Some(b[0] != 0)
229    }
230}
231
232impl StateField for String {
233    fn write_field(&self, buf: &mut Vec<u8>) {
234        let bytes = self.as_bytes();
235        crate::cast::len_u32(bytes.len()).write_field(buf);
236        buf.extend_from_slice(bytes);
237    }
238    fn read_field(cursor: &mut StateCursor) -> Option<Self> {
239        let len = u32::read_field(cursor)? as usize;
240        let bytes = cursor.read_bytes(len)?;
241        String::from_utf8(bytes.to_vec()).ok()
242    }
243}
244
245impl<T: StateField> StateField for Vec<T> {
246    fn write_field(&self, buf: &mut Vec<u8>) {
247        crate::cast::len_u32(self.len()).write_field(buf);
248        for item in self {
249            item.write_field(buf);
250        }
251    }
252    fn read_field(cursor: &mut StateCursor) -> Option<Self> {
253        let len = u32::read_field(cursor)? as usize;
254        let mut vec = Vec::with_capacity(len.min(1024));
255        for _ in 0..len {
256            vec.push(T::read_field(cursor)?);
257        }
258        Some(vec)
259    }
260}
261
262impl<T: StateField> StateField for Option<T> {
263    fn write_field(&self, buf: &mut Vec<u8>) {
264        match self {
265            Some(val) => {
266                1u8.write_field(buf);
267                val.write_field(buf);
268            }
269            None => {
270                0u8.write_field(buf);
271            }
272        }
273    }
274    fn read_field(cursor: &mut StateCursor) -> Option<Self> {
275        let tag = u8::read_field(cursor)?;
276        if tag == 0 {
277            Some(None)
278        } else {
279            Some(Some(T::read_field(cursor)?))
280        }
281    }
282}
283
284// ---------------------------------------------------------------------------
285// StateBinding - typed wrapper for editor state access
286// ---------------------------------------------------------------------------
287
288use crate::editor::PluginContext;
289use std::sync::Arc;
290
291/// Typed state binding for editors.
292///
293/// Wraps the `get_state`/`set_state` closures from `PluginContext` with
294/// typed serialization. Caches the deserialized state to avoid repeated
295/// deserialization each frame.
296///
297/// ```ignore
298/// struct MyEditor {
299///     state: StateBinding<PersistentState>,
300/// }
301///
302/// // In open():
303/// self.state = StateBinding::new(&context);
304///
305/// // In state_changed():
306/// self.state.sync();
307///
308/// // Reading:
309/// let name = &self.state.get().instance_name;
310///
311/// // Writing:
312/// self.state.update(|s| s.instance_name = new_name);
313/// ```
314pub struct StateBinding<T: State> {
315    cached: T,
316    get_state: Arc<dyn Fn() -> Vec<u8> + Send + Sync>,
317    set_state: Arc<dyn Fn(Vec<u8>) + Send + Sync>,
318}
319
320impl<T: State> StateBinding<T> {
321    /// Create a new binding from a [`PluginContext`]. Generic over the
322    /// context's `<P>` since `StateBinding` cares only about the
323    /// `get_state` / `set_state` channel on the underlying
324    /// `EditorBridge`, never about parameter typing.
325    #[must_use]
326    pub fn new<P: ?Sized>(context: &PluginContext<P>) -> Self {
327        let bridge_for_get = Arc::clone(context.bridge());
328        let bridge_for_set = Arc::clone(context.bridge());
329        let mut binding = Self {
330            cached: T::default(),
331            get_state: Arc::new(move || bridge_for_get.get_state()),
332            set_state: Arc::new(move |data| bridge_for_set.set_state(data)),
333        };
334        binding.sync();
335        binding
336    }
337
338    /// Re-read state from the plugin. Call this from `state_changed()`.
339    pub fn sync(&mut self) {
340        let data = (self.get_state)();
341        if !data.is_empty()
342            && let Some(s) = T::deserialize(&data)
343        {
344            self.cached = s;
345        }
346    }
347
348    /// Get the current cached state.
349    pub fn get(&self) -> &T {
350        &self.cached
351    }
352
353    /// Modify state and write it back to the plugin.
354    pub fn update(&mut self, f: impl FnOnce(&mut T)) {
355        f(&mut self.cached);
356        let data = self.cached.serialize();
357        (self.set_state)(data);
358    }
359}
360
361impl<T: State> Default for StateBinding<T> {
362    /// Construct an **unwired** binding: `get()` returns `T::default()`
363    /// and `update()` *silently discards* the new state. Only useful
364    /// as a placeholder before the editor is opened; replace with
365    /// [`StateBinding::new(&context)`](StateBinding::new) inside
366    /// `Editor::open` once a [`PluginContext`] is available. If you
367    /// see writes vanishing, check that the binding has been wired up
368    /// before you call `update`.
369    fn default() -> Self {
370        Self {
371            cached: T::default(),
372            get_state: Arc::new(Vec::new),
373            set_state: Arc::new(|_| {}),
374        }
375    }
376}
377
378// ---------------------------------------------------------------------------
379// Tests
380// ---------------------------------------------------------------------------
381
382#[cfg(test)]
383mod tests {
384    use super::*;
385
386    #[test]
387    fn primitives_round_trip() {
388        let mut buf = Vec::new();
389        42u32.write_field(&mut buf);
390        2.5f64.write_field(&mut buf);
391        true.write_field(&mut buf);
392
393        let mut cursor = StateCursor::new(&buf);
394        assert_eq!(u32::read_field(&mut cursor), Some(42));
395        assert_eq!(f64::read_field(&mut cursor), Some(2.5));
396        assert_eq!(bool::read_field(&mut cursor), Some(true));
397    }
398
399    #[test]
400    fn string_round_trip() {
401        let mut buf = Vec::new();
402        "hello world".to_string().write_field(&mut buf);
403
404        let mut cursor = StateCursor::new(&buf);
405        assert_eq!(
406            String::read_field(&mut cursor),
407            Some("hello world".to_string())
408        );
409    }
410
411    #[test]
412    fn vec_round_trip() {
413        let mut buf = Vec::new();
414        vec![1u32, 2, 3].write_field(&mut buf);
415
416        let mut cursor = StateCursor::new(&buf);
417        assert_eq!(Vec::<u32>::read_field(&mut cursor), Some(vec![1, 2, 3]));
418    }
419
420    #[test]
421    fn option_round_trip() {
422        let mut buf = Vec::new();
423        Some(42u32).write_field(&mut buf);
424        None::<u32>.write_field(&mut buf);
425
426        let mut cursor = StateCursor::new(&buf);
427        assert_eq!(Option::<u32>::read_field(&mut cursor), Some(Some(42)));
428        assert_eq!(Option::<u32>::read_field(&mut cursor), Some(None));
429    }
430
431    #[test]
432    fn nested_vec_string() {
433        let mut buf = Vec::new();
434        let v = vec!["foo".to_string(), "bar".to_string()];
435        v.write_field(&mut buf);
436
437        let mut cursor = StateCursor::new(&buf);
438        assert_eq!(Vec::<String>::read_field(&mut cursor), Some(v));
439    }
440
441    #[test]
442    fn persist_field_lock_round_trip() {
443        let src = std::sync::RwLock::new("guitar bus".to_string());
444        let mut buf = Vec::new();
445        src.persist_write(&mut buf);
446
447        let dst = std::sync::RwLock::new(String::new());
448        dst.persist_read(&mut StateCursor::new(&buf));
449        assert_eq!(*dst.read().unwrap(), "guitar bus");
450
451        let m_src = std::sync::Mutex::new(vec![1u32, 2, 3]);
452        let mut m_buf = Vec::new();
453        m_src.persist_write(&mut m_buf);
454        let m_dst = std::sync::Mutex::new(Vec::<u32>::new());
455        m_dst.persist_read(&mut StateCursor::new(&m_buf));
456        assert_eq!(*m_dst.lock().unwrap(), vec![1, 2, 3]);
457    }
458
459    #[test]
460    fn persist_field_atomic_cell_round_trip() {
461        let src = AtomicCell::new(0.75f32);
462        let mut buf = Vec::new();
463        src.persist_write(&mut buf);
464
465        let dst = AtomicCell::new(0.0f32);
466        dst.persist_read(&mut StateCursor::new(&buf));
467        // Bit-exact: the value round-trips through `to_le_bytes`.
468        assert_eq!(dst.load().to_bits(), 0.75f32.to_bits());
469    }
470
471    #[test]
472    fn persist_field_leaves_value_on_short_read() {
473        // A truncated blob must not clobber the current value.
474        let dst = AtomicCell::new(42u32);
475        dst.persist_read(&mut StateCursor::new(&[0xFF, 0xFF]));
476        assert_eq!(dst.load(), 42);
477    }
478}