nomad_protocol/core/
traits.rs

1//! Core traits for NOMAD protocol.
2//!
3//! These traits define the interface for state synchronization.
4
5use super::error::{ApplyError, DecodeError};
6
7/// Core trait for any state that can be synchronized.
8///
9/// Implements the state type interface from 3-SYNC.md.
10///
11/// # Requirements
12///
13/// - `diff_from` MUST produce idempotent diffs
14/// - `apply_diff` MUST handle repeated application
15/// - `encode_diff`/`decode_diff` MUST roundtrip correctly
16///
17/// # Example
18///
19/// ```ignore
20/// #[derive(Clone)]
21/// struct Counter { value: u64 }
22///
23/// #[derive(Clone)]
24/// struct CounterDiff { delta: i64 }
25///
26/// impl SyncState for Counter {
27///     type Diff = CounterDiff;
28///     const STATE_TYPE_ID: &'static str = "example.counter.v1";
29///
30///     fn diff_from(&self, old: &Self) -> Self::Diff {
31///         CounterDiff { delta: self.value as i64 - old.value as i64 }
32///     }
33///
34///     fn apply_diff(&mut self, diff: &Self::Diff) -> Result<(), ApplyError> {
35///         self.value = (self.value as i64 + diff.delta) as u64;
36///         Ok(())
37///     }
38///
39///     fn encode_diff(diff: &Self::Diff) -> Vec<u8> {
40///         diff.delta.to_le_bytes().to_vec()
41///     }
42///
43///     fn decode_diff(data: &[u8]) -> Result<Self::Diff, DecodeError> {
44///         if data.len() < 8 {
45///             return Err(DecodeError::UnexpectedEof);
46///         }
47///         let delta = i64::from_le_bytes(data[..8].try_into().unwrap());
48///         Ok(CounterDiff { delta })
49///     }
50/// }
51/// ```
52pub trait SyncState: Clone + Send + Sync + 'static {
53    /// Diff representation (must be idempotent when applied).
54    type Diff: Clone + Send + Sync;
55
56    /// Unique type identifier (e.g., "nomad.echo.v1").
57    const STATE_TYPE_ID: &'static str;
58
59    /// Create diff from old_state to self.
60    ///
61    /// MUST be idempotent: applying twice has no additional effect.
62    fn diff_from(&self, old: &Self) -> Self::Diff;
63
64    /// Apply diff to produce new state.
65    ///
66    /// MUST handle repeated application (idempotent).
67    fn apply_diff(&mut self, diff: &Self::Diff) -> Result<(), ApplyError>;
68
69    /// Serialize diff for wire transmission.
70    fn encode_diff(diff: &Self::Diff) -> Vec<u8>;
71
72    /// Deserialize diff from wire format.
73    fn decode_diff(data: &[u8]) -> Result<Self::Diff, DecodeError>;
74
75    /// Check if diff is empty (optimization for ack-only).
76    ///
77    /// Returns `true` if the diff represents no change.
78    fn is_diff_empty(diff: &Self::Diff) -> bool {
79        let _ = diff;
80        false
81    }
82}
83
84/// Optional trait for states that support client-side prediction.
85///
86/// See 4-EXTENSIONS.md for prediction specification.
87pub trait Predictable: SyncState {
88    /// User input type (e.g., keystrokes).
89    type Input;
90
91    /// Apply speculative input locally.
92    fn predict(&mut self, input: &Self::Input);
93
94    /// Reconcile with authoritative server state.
95    fn reconcile(&mut self, authoritative: &Self);
96}