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 use it in your plugin's `save_state`/`load_state`:
15//!
16//! ```ignore
17//! fn save_state(&self) -> Vec<u8> {
18//!     self.persistent.serialize()
19//! }
20//! fn load_state(&mut self, data: &[u8]) -> Result<(), StateLoadError> {
21//!     match MyState::deserialize(data) {
22//!         Some(s) => { self.persistent = s; Ok(()) }
23//!         None => Err(StateLoadError::Malformed("MyState")),
24//!     }
25//! }
26//! ```
27
28/// Cursor for reading binary state data.
29pub struct StateCursor<'a> {
30    data: &'a [u8],
31    pos: usize,
32}
33
34impl<'a> StateCursor<'a> {
35    #[must_use]
36    pub fn new(data: &'a [u8]) -> Self {
37        Self { data, pos: 0 }
38    }
39
40    #[must_use]
41    pub fn remaining(&self) -> usize {
42        self.data.len().saturating_sub(self.pos)
43    }
44
45    pub fn read_bytes(&mut self, n: usize) -> Option<&'a [u8]> {
46        if self.pos + n > self.data.len() {
47            return None;
48        }
49        let slice = &self.data[self.pos..self.pos + n];
50        self.pos += n;
51        Some(slice)
52    }
53
54    /// Skip the next field (reads its encoded size and advances past it).
55    /// Returns false if the data is malformed.
56    ///
57    /// # Panics
58    ///
59    /// Does not panic - the `expect` inside is unreachable because
60    /// `read_bytes(4)` only returns `Some` when the slice is exactly
61    /// 4 bytes long.
62    pub fn skip_field(&mut self) -> bool {
63        // Fields are prefixed with a u32 byte length by the derive macro.
64        if let Some(bytes) = self.read_bytes(4) {
65            // `read_bytes(4)` returns `Some(slice of length 4)` or
66            // `None`, so the `try_into::<[u8; 4]>()` here cannot fail.
67            // The `expect` documents that invariant for readers.
68            let len = u32::from_le_bytes(
69                bytes
70                    .try_into()
71                    .expect("read_bytes(4) returned a slice of unexpected length"),
72            ) as usize;
73            if self.pos + len <= self.data.len() {
74                self.pos += len;
75                return true;
76            }
77        }
78        false
79    }
80}
81
82/// Trait for types that can be serialized as a single state field.
83///
84/// Implemented for primitives, `String`, `Vec<T>`, and `Option<T>`.
85pub trait StateField: Sized {
86    fn write_field(&self, buf: &mut Vec<u8>);
87    fn read_field(cursor: &mut StateCursor) -> Option<Self>;
88}
89
90/// Trait for custom plugin state structs.
91///
92/// Derive with `#[derive(State)]`. The struct must also implement `Default`
93/// so missing fields can be filled with defaults when loading old state.
94pub trait State: Sized + Default {
95    fn serialize(&self) -> Vec<u8>;
96    fn deserialize(data: &[u8]) -> Option<Self>;
97}
98
99// ---------------------------------------------------------------------------
100// StateField implementations for primitives
101// ---------------------------------------------------------------------------
102
103macro_rules! impl_state_field_int {
104    ($($ty:ty),*) => {
105        $(
106            impl StateField for $ty {
107                fn write_field(&self, buf: &mut Vec<u8>) {
108                    buf.extend_from_slice(&self.to_le_bytes());
109                }
110                fn read_field(cursor: &mut StateCursor) -> Option<Self> {
111                    let bytes = cursor.read_bytes(std::mem::size_of::<Self>())?;
112                    Some(Self::from_le_bytes(bytes.try_into().ok()?))
113                }
114            }
115        )*
116    };
117}
118
119impl_state_field_int!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64);
120
121impl StateField for bool {
122    fn write_field(&self, buf: &mut Vec<u8>) {
123        buf.push(u8::from(*self));
124    }
125    fn read_field(cursor: &mut StateCursor) -> Option<Self> {
126        let b = cursor.read_bytes(1)?;
127        Some(b[0] != 0)
128    }
129}
130
131impl StateField for String {
132    fn write_field(&self, buf: &mut Vec<u8>) {
133        let bytes = self.as_bytes();
134        crate::cast::len_u32(bytes.len()).write_field(buf);
135        buf.extend_from_slice(bytes);
136    }
137    fn read_field(cursor: &mut StateCursor) -> Option<Self> {
138        let len = u32::read_field(cursor)? as usize;
139        let bytes = cursor.read_bytes(len)?;
140        String::from_utf8(bytes.to_vec()).ok()
141    }
142}
143
144impl<T: StateField> StateField for Vec<T> {
145    fn write_field(&self, buf: &mut Vec<u8>) {
146        crate::cast::len_u32(self.len()).write_field(buf);
147        for item in self {
148            item.write_field(buf);
149        }
150    }
151    fn read_field(cursor: &mut StateCursor) -> Option<Self> {
152        let len = u32::read_field(cursor)? as usize;
153        let mut vec = Vec::with_capacity(len.min(1024));
154        for _ in 0..len {
155            vec.push(T::read_field(cursor)?);
156        }
157        Some(vec)
158    }
159}
160
161impl<T: StateField> StateField for Option<T> {
162    fn write_field(&self, buf: &mut Vec<u8>) {
163        match self {
164            Some(val) => {
165                1u8.write_field(buf);
166                val.write_field(buf);
167            }
168            None => {
169                0u8.write_field(buf);
170            }
171        }
172    }
173    fn read_field(cursor: &mut StateCursor) -> Option<Self> {
174        let tag = u8::read_field(cursor)?;
175        if tag == 0 {
176            Some(None)
177        } else {
178            Some(Some(T::read_field(cursor)?))
179        }
180    }
181}
182
183// ---------------------------------------------------------------------------
184// StateBinding - typed wrapper for editor state access
185// ---------------------------------------------------------------------------
186
187use crate::editor::PluginContext;
188use std::sync::Arc;
189
190/// Typed state binding for editors.
191///
192/// Wraps the `get_state`/`set_state` closures from `PluginContext` with
193/// typed serialization. Caches the deserialized state to avoid repeated
194/// deserialization each frame.
195///
196/// ```ignore
197/// struct MyEditor {
198///     state: StateBinding<PersistentState>,
199/// }
200///
201/// // In open():
202/// self.state = StateBinding::new(&context);
203///
204/// // In state_changed():
205/// self.state.sync();
206///
207/// // Reading:
208/// let name = &self.state.get().instance_name;
209///
210/// // Writing:
211/// self.state.update(|s| s.instance_name = new_name);
212/// ```
213pub struct StateBinding<T: State> {
214    cached: T,
215    get_state: Arc<dyn Fn() -> Vec<u8> + Send + Sync>,
216    set_state: Arc<dyn Fn(Vec<u8>) + Send + Sync>,
217}
218
219impl<T: State> StateBinding<T> {
220    /// Create a new binding from a [`PluginContext`]. Generic over the
221    /// context's `<P>` since `StateBinding` cares only about the
222    /// `get_state` / `set_state` channel on the underlying
223    /// `EditorBridge`, never about parameter typing.
224    #[must_use]
225    pub fn new<P: ?Sized>(context: &PluginContext<P>) -> Self {
226        let bridge_for_get = Arc::clone(context.bridge());
227        let bridge_for_set = Arc::clone(context.bridge());
228        let mut binding = Self {
229            cached: T::default(),
230            get_state: Arc::new(move || bridge_for_get.get_state()),
231            set_state: Arc::new(move |data| bridge_for_set.set_state(data)),
232        };
233        binding.sync();
234        binding
235    }
236
237    /// Re-read state from the plugin. Call this from `state_changed()`.
238    pub fn sync(&mut self) {
239        let data = (self.get_state)();
240        if !data.is_empty()
241            && let Some(s) = T::deserialize(&data)
242        {
243            self.cached = s;
244        }
245    }
246
247    /// Get the current cached state.
248    pub fn get(&self) -> &T {
249        &self.cached
250    }
251
252    /// Modify state and write it back to the plugin.
253    pub fn update(&mut self, f: impl FnOnce(&mut T)) {
254        f(&mut self.cached);
255        let data = self.cached.serialize();
256        (self.set_state)(data);
257    }
258}
259
260impl<T: State> Default for StateBinding<T> {
261    /// Construct an **unwired** binding: `get()` returns `T::default()`
262    /// and `update()` *silently discards* the new state. Only useful
263    /// as a placeholder before the editor is opened; replace with
264    /// [`StateBinding::new(&context)`](StateBinding::new) inside
265    /// `Editor::open` once a [`PluginContext`] is available. If you
266    /// see writes vanishing, check that the binding has been wired up
267    /// before you call `update`.
268    fn default() -> Self {
269        Self {
270            cached: T::default(),
271            get_state: Arc::new(Vec::new),
272            set_state: Arc::new(|_| {}),
273        }
274    }
275}
276
277// ---------------------------------------------------------------------------
278// Tests
279// ---------------------------------------------------------------------------
280
281#[cfg(test)]
282mod tests {
283    use super::*;
284
285    #[test]
286    fn primitives_round_trip() {
287        let mut buf = Vec::new();
288        42u32.write_field(&mut buf);
289        2.5f64.write_field(&mut buf);
290        true.write_field(&mut buf);
291
292        let mut cursor = StateCursor::new(&buf);
293        assert_eq!(u32::read_field(&mut cursor), Some(42));
294        assert_eq!(f64::read_field(&mut cursor), Some(2.5));
295        assert_eq!(bool::read_field(&mut cursor), Some(true));
296    }
297
298    #[test]
299    fn string_round_trip() {
300        let mut buf = Vec::new();
301        "hello world".to_string().write_field(&mut buf);
302
303        let mut cursor = StateCursor::new(&buf);
304        assert_eq!(
305            String::read_field(&mut cursor),
306            Some("hello world".to_string())
307        );
308    }
309
310    #[test]
311    fn vec_round_trip() {
312        let mut buf = Vec::new();
313        vec![1u32, 2, 3].write_field(&mut buf);
314
315        let mut cursor = StateCursor::new(&buf);
316        assert_eq!(Vec::<u32>::read_field(&mut cursor), Some(vec![1, 2, 3]));
317    }
318
319    #[test]
320    fn option_round_trip() {
321        let mut buf = Vec::new();
322        Some(42u32).write_field(&mut buf);
323        None::<u32>.write_field(&mut buf);
324
325        let mut cursor = StateCursor::new(&buf);
326        assert_eq!(Option::<u32>::read_field(&mut cursor), Some(Some(42)));
327        assert_eq!(Option::<u32>::read_field(&mut cursor), Some(None));
328    }
329
330    #[test]
331    fn nested_vec_string() {
332        let mut buf = Vec::new();
333        let v = vec!["foo".to_string(), "bar".to_string()];
334        v.write_field(&mut buf);
335
336        let mut cursor = StateCursor::new(&buf);
337        assert_eq!(Vec::<String>::read_field(&mut cursor), Some(v));
338    }
339}