Skip to main content

delta_pack/
sync_session.rs

1use crate::DeltaPack;
2
3/// A stateful handle over a one-way state-sync session between two endpoints.
4///
5/// Each endpoint holds a [`SyncSession`] representing the current shared view
6/// of the peer's state. The sender calls [`encode`](Self::encode) to produce
7/// bytes; the receiver calls [`decode`](Self::decode) to apply them. Both
8/// sides converge on the same view as long as messages are delivered in order.
9///
10/// The type handles the "first call is full encode, subsequent are diffs"
11/// distinction internally. It also ensures the sender's view always matches
12/// what the peer reconstructs — even when the user's `state` has been mutated
13/// or reordered in ways that would break a raw `encode_diff` call.
14///
15/// # Example
16///
17/// ```ignore
18/// let mut session = SyncSession::<GameState>::new();
19///
20/// // Sender
21/// let bytes = session.encode(&state);
22///
23/// // Receiver
24/// let state = session.decode(&bytes);
25/// ```
26pub struct SyncSession<T: DeltaPack + Clone> {
27    view: Option<T>,
28}
29
30impl<T: DeltaPack + Clone> SyncSession<T> {
31    /// Create a new session with no view yet.
32    pub fn new() -> Self {
33        Self { view: None }
34    }
35
36    /// Produce bytes to send to the peer. First call emits a full encode;
37    /// subsequent calls emit a diff against the current view. Either way,
38    /// the internal view is updated to match what the peer will hold after
39    /// applying the returned bytes.
40    pub fn encode(&mut self, state: &T) -> Vec<u8> {
41        match &self.view {
42            None => {
43                let bytes = state.encode();
44                // Full encode iterates `state` in its insertion order, so
45                // `state`'s order is the wire order. A clone captures a safe
46                // "peer view" copy.
47                self.view = Some(state.clone());
48                bytes
49            }
50            Some(view) => {
51                let bytes = T::encode_diff(view, state);
52                // Simulate the peer's decode so our view stays aligned with
53                // theirs, regardless of any reordering in the user's `state`.
54                self.view = Some(T::decode_diff(view, &bytes));
55                bytes
56            }
57        }
58    }
59
60    /// Apply bytes received from the peer. First call expects a full encode;
61    /// subsequent calls expect a diff. Returns a reference to the updated view.
62    pub fn decode(&mut self, bytes: &[u8]) -> &T {
63        self.view = Some(match &self.view {
64            None => T::decode(bytes),
65            Some(view) => T::decode_diff(view, bytes),
66        });
67        self.view.as_ref().expect("view just assigned")
68    }
69
70    /// The current view, or `None` if neither [`encode`](Self::encode) nor
71    /// [`decode`](Self::decode) has been called.
72    pub fn current(&self) -> Option<&T> {
73        self.view.as_ref()
74    }
75}
76
77impl<T: DeltaPack + Clone> Default for SyncSession<T> {
78    fn default() -> Self {
79        Self::new()
80    }
81}