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}