1use std::sync::{Arc, Mutex};
43
44use crate::types::{ChunkRequest, ChunkResult, LaserPoint};
45
46#[derive(Clone, Debug)]
48pub struct Frame {
49 pub points: Vec<LaserPoint>,
50}
51
52impl Frame {
53 pub fn new(points: Vec<LaserPoint>) -> Self {
55 Self { points }
56 }
57
58 pub fn empty() -> Self {
60 Self { points: Vec::new() }
61 }
62}
63
64impl From<Vec<LaserPoint>> for Frame {
65 fn from(points: Vec<LaserPoint>) -> Self {
66 Self::new(points)
67 }
68}
69
70pub struct FrameAdapter {
98 current: Frame,
99 pending: Option<Frame>,
100 point_index: usize,
101 last_position: (f32, f32),
102}
103
104impl FrameAdapter {
105 pub fn new() -> Self {
109 Self {
110 current: Frame::empty(),
111 pending: None,
112 point_index: 0,
113 last_position: (0.0, 0.0),
114 }
115 }
116
117 pub fn update(&mut self, frame: Frame) {
123 self.pending = Some(frame);
124 }
125
126 pub fn fill_chunk(&mut self, req: &ChunkRequest, buffer: &mut [LaserPoint]) -> ChunkResult {
140 let n_points = req.target_points.min(buffer.len());
141 self.fill_points(buffer, n_points);
142 ChunkResult::Filled(n_points)
143 }
144
145 #[allow(clippy::needless_range_loop)] fn fill_points(&mut self, buffer: &mut [LaserPoint], n_points: usize) {
148 for i in 0..n_points {
149 if self.current.points.is_empty() {
151 if let Some(pending) = self.pending.take() {
152 self.current = pending;
153 self.point_index = 0;
154 }
155
156 if self.current.points.is_empty() {
157 let (x, y) = self.last_position;
158 buffer[i] = LaserPoint::blanked(x, y);
159 continue;
160 }
161 }
162
163 let point = self.current.points[self.point_index];
164 buffer[i] = point;
165 self.last_position = (point.x, point.y);
166
167 self.point_index += 1;
168 if self.point_index >= self.current.points.len() {
169 self.point_index = 0;
170 if let Some(pending) = self.pending.take() {
172 self.current = pending;
173 self.point_index = 0;
174 }
175 }
176 }
177 }
178
179 pub fn shared(self) -> SharedFrameAdapter {
181 SharedFrameAdapter {
182 inner: Arc::new(Mutex::new(self)),
183 }
184 }
185}
186
187impl Default for FrameAdapter {
188 fn default() -> Self {
189 Self::new()
190 }
191}
192
193#[derive(Clone)]
195pub struct SharedFrameAdapter {
196 inner: Arc<Mutex<FrameAdapter>>,
197}
198
199impl SharedFrameAdapter {
200 pub fn update(&self, frame: Frame) {
202 let mut adapter = self.inner.lock().unwrap();
203 adapter.update(frame);
204 }
205
206 pub fn fill_chunk(&self, req: &ChunkRequest, buffer: &mut [LaserPoint]) -> ChunkResult {
211 let mut adapter = self.inner.lock().unwrap();
212 adapter.fill_chunk(req, buffer)
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use crate::types::StreamInstant;
220 use std::time::Duration;
221
222 fn make_fill_request(target_points: usize) -> ChunkRequest {
223 ChunkRequest {
224 start: StreamInstant(0),
225 pps: 30000,
226 min_points: target_points,
227 target_points,
228 buffered_points: 0,
229 buffered: Duration::ZERO,
230 device_queued_points: None,
231 }
232 }
233
234 #[test]
235 fn test_empty_frame() {
236 let mut adapter = FrameAdapter::new();
237 let req = make_fill_request(100);
238 let mut buffer = vec![LaserPoint::default(); 100];
239
240 let result = adapter.fill_chunk(&req, &mut buffer);
241 assert!(matches!(result, ChunkResult::Filled(100)));
242 assert!(buffer.iter().all(|p| p.intensity == 0));
243 }
244
245 #[test]
246 fn test_frame_cycles() {
247 let mut adapter = FrameAdapter::new();
248 let frame_points: Vec<LaserPoint> = (0..10)
249 .map(|i| LaserPoint::new(i as f32 / 10.0, 0.0, 65535, 0, 0, 65535))
250 .collect();
251 adapter.update(Frame::new(frame_points));
252
253 let req = make_fill_request(25);
254 let mut buffer = vec![LaserPoint::default(); 25];
255
256 let result = adapter.fill_chunk(&req, &mut buffer);
257 assert!(matches!(result, ChunkResult::Filled(25)));
258 }
259
260 #[test]
261 fn test_single_point_swaps_immediately() {
262 let mut adapter = FrameAdapter::new();
263 adapter.update(Frame::new(vec![LaserPoint::new(
264 0.0, 0.0, 65535, 0, 0, 65535,
265 )]));
266
267 let req = make_fill_request(10);
268 let mut buffer = vec![LaserPoint::default(); 10];
269
270 adapter.fill_chunk(&req, &mut buffer);
271 assert_eq!(buffer[0].x, 0.0);
272
273 adapter.update(Frame::new(vec![LaserPoint::new(
274 1.0, 1.0, 0, 65535, 0, 65535,
275 )]));
276
277 adapter.fill_chunk(&req, &mut buffer);
280 assert_eq!(buffer[0].x, 0.0, "First point finishes old frame");
281 assert_eq!(buffer[1].x, 1.0, "Second point starts new frame");
282 assert!(buffer[2..].iter().all(|p| p.x == 1.0), "Rest is new frame");
283 }
284
285 #[test]
286 fn test_swap_waits_for_frame_end() {
287 let mut adapter = FrameAdapter::new();
288 let frame1: Vec<LaserPoint> = (0..100)
289 .map(|i| LaserPoint::new(i as f32 / 100.0, 0.0, 65535, 0, 0, 65535))
290 .collect();
291 adapter.update(Frame::new(frame1));
292
293 let req = make_fill_request(10);
294 let mut buffer = vec![LaserPoint::default(); 10];
295
296 adapter.fill_chunk(&req, &mut buffer);
297 assert_eq!(buffer[0].x, 0.0);
298
299 let frame2: Vec<LaserPoint> = (0..100)
301 .map(|_| LaserPoint::new(9.0, 9.0, 0, 65535, 0, 65535))
302 .collect();
303 adapter.update(Frame::new(frame2));
304
305 adapter.fill_chunk(&req, &mut buffer);
307 assert!(
308 (buffer[0].x - 0.1).abs() < 1e-4,
309 "Expected ~0.1, got {}",
310 buffer[0].x
311 );
312
313 for _ in 0..8 {
315 adapter.fill_chunk(&req, &mut buffer);
316 }
317
318 adapter.fill_chunk(&req, &mut buffer);
320 assert_eq!(buffer[0].x, 9.0);
321 }
322
323 #[test]
324 fn test_mid_chunk_stitching() {
325 let mut adapter = FrameAdapter::new();
327 let frame1: Vec<LaserPoint> = (0..95)
328 .map(|i| LaserPoint::new(i as f32, 0.0, 65535, 0, 0, 65535))
329 .collect();
330 adapter.update(Frame::new(frame1));
331
332 let req = make_fill_request(10);
333 let mut buffer = vec![LaserPoint::default(); 10];
334
335 for _ in 0..9 {
337 adapter.fill_chunk(&req, &mut buffer);
338 }
339
340 let frame2: Vec<LaserPoint> = (0..95)
342 .map(|_| LaserPoint::new(999.0, 999.0, 0, 65535, 0, 65535))
343 .collect();
344 adapter.update(Frame::new(frame2));
345
346 adapter.fill_chunk(&req, &mut buffer);
348
349 for (i, p) in buffer[0..5].iter().enumerate() {
351 assert_eq!(p.x, (90 + i) as f32, "Point {} should be from frame1", i);
352 }
353
354 for (i, p) in buffer[5..10].iter().enumerate() {
356 assert_eq!(p.x, 999.0, "Point {} should be from frame2", i + 5);
357 }
358 }
359
360 #[test]
361 fn test_empty_holds_last_position() {
362 let mut adapter = FrameAdapter::new();
363 adapter.update(Frame::new(vec![LaserPoint::new(
364 0.5, -0.3, 65535, 0, 0, 65535,
365 )]));
366
367 let req = make_fill_request(5);
368 let mut buffer = vec![LaserPoint::default(); 5];
369
370 adapter.fill_chunk(&req, &mut buffer);
371 adapter.update(Frame::empty());
372
373 adapter.fill_chunk(&req, &mut buffer);
375 assert_eq!(buffer[0].intensity, 65535, "First point finishes old frame");
376 assert!(
377 buffer[1..].iter().all(|p| p.intensity == 0),
378 "Rest is blanked"
379 );
380 assert_eq!(buffer[1].x, 0.5);
382 assert_eq!(buffer[1].y, -0.3);
383 }
384
385 #[test]
386 fn test_integer_index_deterministic() {
387 let mut adapter = FrameAdapter::new();
388 let frame: Vec<LaserPoint> = (0..7)
389 .map(|i| LaserPoint::new(i as f32, 0.0, 65535, 0, 0, 65535))
390 .collect();
391 adapter.update(Frame::new(frame));
392
393 let req = make_fill_request(7);
394 let mut buffer = vec![LaserPoint::default(); 7];
395
396 for cycle in 0..1000 {
398 adapter.fill_chunk(&req, &mut buffer);
399 for (i, p) in buffer.iter().enumerate() {
400 assert_eq!(p.x, i as f32, "Cycle {}: drift detected", cycle);
401 }
402 }
403 }
404
405 #[test]
406 fn test_from_vec() {
407 let points: Vec<LaserPoint> = vec![LaserPoint::new(0.0, 0.0, 65535, 0, 0, 65535)];
408 let frame: Frame = points.into();
409 assert_eq!(frame.points.len(), 1);
410 }
411}