lnmp_spatial/
protocol.rs

1use crate::delta::Delta;
2use crate::error::SpatialError;
3use crate::types::*;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
7pub enum FrameMode {
8    Absolute = 0x00,
9    Delta = 0x01,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
13pub struct SpatialFrameHeader {
14    pub mode: FrameMode,
15    pub sequence_id: u32,
16    pub timestamp: u64, // Nanoseconds
17    pub checksum: u32,  // CRC32 of payload
18}
19
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21pub struct SpatialFrame {
22    pub header: SpatialFrameHeader,
23    pub payload: SpatialValue,
24}
25
26#[derive(Debug, Clone)]
27pub struct SpatialStreamerConfig {
28    pub abs_interval: u32,
29    pub enable_prediction: bool,
30    pub max_prediction_frames: u8,
31}
32
33impl Default for SpatialStreamerConfig {
34    fn default() -> Self {
35        Self {
36            abs_interval: 100,
37            enable_prediction: true,
38            max_prediction_frames: 3,
39        }
40    }
41}
42
43pub struct SpatialStreamer {
44    config: SpatialStreamerConfig,
45    sequence_counter: u32,
46    last_sent_state: Option<SpatialState>,
47
48    // Receiver state
49    last_received_seq: Option<u32>,
50    current_state: Option<SpatialState>,
51    predicted_next: Option<Position3D>,
52    prediction_frame_count: u8,
53}
54
55impl SpatialStreamer {
56    pub fn new(abs_interval: u32) -> Self {
57        Self::with_config(SpatialStreamerConfig {
58            abs_interval,
59            ..Default::default()
60        })
61    }
62
63    pub fn with_config(config: SpatialStreamerConfig) -> Self {
64        Self {
65            config,
66            sequence_counter: 0,
67            last_sent_state: None,
68            last_received_seq: None,
69            current_state: None,
70            predicted_next: None,
71            prediction_frame_count: 0,
72        }
73    }
74
75    /// Generates the next frame for a given state.
76    /// Automatically decides whether to send ABS or DELTA.
77    pub fn next_frame(
78        &mut self,
79        new_state: &SpatialState,
80        timestamp: u64,
81    ) -> Result<SpatialFrame, SpatialError> {
82        let seq = self.sequence_counter;
83        self.sequence_counter += 1;
84
85        let force_abs = seq.is_multiple_of(self.config.abs_interval);
86
87        let (mode, payload) = if force_abs || self.last_sent_state.is_none() {
88            (FrameMode::Absolute, SpatialValue::S10(new_state.clone()))
89        } else {
90            let delta =
91                SpatialState::compute_delta(self.last_sent_state.as_ref().unwrap(), new_state);
92            (FrameMode::Delta, SpatialValue::S13(delta))
93        };
94
95        self.last_sent_state = Some(new_state.clone());
96
97        // Predictive Delta: Compute predicted_next if enabled
98        if self.config.enable_prediction {
99            if let (Some(pos), Some(vel)) = (&new_state.position, &new_state.velocity) {
100                // Predict next position based on current velocity
101                // Assuming dt = 1ms (typical for high-frequency control)
102                let dt = 0.001; // 1ms in seconds
103                self.predicted_next = Some(Position3D {
104                    x: pos.x + vel.vx * dt,
105                    y: pos.y + vel.vy * dt,
106                    z: pos.z + vel.vz * dt,
107                });
108            }
109        }
110
111        // Compute checksum of payload
112        let payload_bytes = bincode::serialize(&payload)
113            .map_err(|e| SpatialError::ValidationError(format!("Serialization error: {}", e)))?;
114        let checksum = crate::checksum::compute_checksum(&payload_bytes);
115
116        Ok(SpatialFrame {
117            header: SpatialFrameHeader {
118                mode,
119                sequence_id: seq,
120                timestamp,
121                checksum,
122            },
123            payload,
124        })
125    }
126
127    /// Processes an incoming frame and updates the internal state.
128    /// Handles drift correction and sequence checking.
129    pub fn process_frame(&mut self, frame: &SpatialFrame) -> Result<&SpatialState, SpatialError> {
130        // 0. Checksum Verification
131        let payload_bytes = bincode::serialize(&frame.payload)
132            .map_err(|e| SpatialError::ValidationError(format!("Serialization error: {}", e)))?;
133
134        if !crate::checksum::verify_checksum(&payload_bytes, frame.header.checksum) {
135            return Err(SpatialError::ValidationError(
136                "Checksum mismatch! Frame corrupted.".into(),
137            ));
138        }
139
140        // 1. Sequence Check
141        if let Some(last_seq) = self.last_received_seq {
142            if frame.header.sequence_id <= last_seq {
143                // Out of order or duplicate
144                // For this simple implementation, we ignore or warn.
145                // In a strict system, we might error.
146            } else if frame.header.sequence_id > last_seq + 1 {
147                // Gap detected! Packet loss.
148                // If this is a DELTA frame, we CANNOT apply it safely because we missed the base.
149
150                if frame.header.mode == FrameMode::Delta {
151                    // Predictive Fallback: Use prediction if enabled
152                    if self.config.enable_prediction && self.predicted_next.is_some() {
153                        // Use predicted position to continue smoothly
154                        self.prediction_frame_count += 1;
155
156                        if self.prediction_frame_count > self.config.max_prediction_frames {
157                            // Too many predictions, must reset with ABS
158                            return Err(SpatialError::ValidationError(format!(
159                                "Prediction limit exceeded ({} frames). Waiting for ABS frame.",
160                                self.prediction_frame_count
161                            )));
162                        }
163
164                        // Use prediction to update current state
165                        if let Some(predicted_pos) = self.predicted_next {
166                            if let Some(mut state) = self.current_state.clone() {
167                                state.position = Some(predicted_pos);
168                                self.current_state = Some(state);
169                            }
170                        }
171                    } else {
172                        // No prediction, must wait for ABS
173                        return Err(SpatialError::ValidationError(format!(
174                            "Packet loss detected (gap {} -> {}). Waiting for ABS frame.",
175                            last_seq, frame.header.sequence_id
176                        )));
177                    }
178                }
179            }
180        }
181
182        // 2. Apply Update
183        match frame.header.mode {
184            FrameMode::Absolute => {
185                if let SpatialValue::S10(state) = &frame.payload {
186                    self.current_state = Some(state.clone());
187                    // Reset prediction counter on ABS frame
188                    self.prediction_frame_count = 0;
189                } else {
190                    return Err(SpatialError::ValidationError(
191                        "ABS frame must contain SpatialState".into(),
192                    ));
193                }
194            }
195            FrameMode::Delta => {
196                if let SpatialValue::S13(delta) = &frame.payload {
197                    if let Some(current) = &self.current_state {
198                        // Apply delta to current state
199                        // Note: SpatialState::apply_delta expects &SpatialDelta (ref)
200                        // but S13 contains SpatialDelta.
201                        // We need to match the enum variant inside S13.
202                        // Wait, S13 IS SpatialDelta enum variant in SpatialValue.
203                        // But SpatialDelta is the type.
204                        // Let's verify types.rs.
205                        // SpatialValue::S13(SpatialDelta)
206
207                        let new_state = SpatialState::apply_delta(current, delta);
208                        self.current_state = Some(new_state);
209                    } else {
210                        return Err(SpatialError::ValidationError(
211                            "Received DELTA frame without prior ABS state".into(),
212                        ));
213                    }
214                } else {
215                    return Err(SpatialError::ValidationError(
216                        "DELTA frame must contain SpatialDelta".into(),
217                    ));
218                }
219            }
220        }
221
222        self.last_received_seq = Some(frame.header.sequence_id);
223
224        Ok(self.current_state.as_ref().unwrap())
225    }
226}