1mod diff;
13
14use std::marker::PhantomData;
15use std::ops::{Deref, DerefMut};
16use std::sync::{Arc, Mutex, MutexGuard};
17use std::task::Poll;
18
19use serde::Serialize;
20use serde::de::DeserializeOwned;
21use serde_json::Value;
22
23use crate::diff::diff;
24
25const MAX_DELTA_FRAMES: usize = 256;
30
31#[derive(thiserror::Error, Debug, Clone)]
33#[non_exhaustive]
34pub enum Error {
35 #[error(transparent)]
37 Net(#[from] moq_net::Error),
38
39 #[error("json: {0}")]
43 Json(String),
44}
45
46impl From<serde_json::Error> for Error {
47 fn from(err: serde_json::Error) -> Self {
48 Error::Json(err.to_string())
49 }
50}
51
52pub type Result<T> = std::result::Result<T, Error>;
54
55#[derive(Debug, Clone)]
57pub struct Config {
58 pub delta_ratio: u32,
69}
70
71impl Default for Config {
72 fn default() -> Self {
73 Self { delta_ratio: 8 }
74 }
75}
76
77pub struct Producer<T> {
82 inner: Arc<Mutex<Inner>>,
83 _marker: PhantomData<fn(T)>,
84}
85
86impl<T> Clone for Producer<T> {
87 fn clone(&self) -> Self {
88 Self {
89 inner: self.inner.clone(),
90 _marker: PhantomData,
91 }
92 }
93}
94
95impl<T> Producer<T> {
96 pub fn consume(&self) -> moq_net::TrackConsumer {
98 self.inner.lock().unwrap().track.consume()
99 }
100}
101
102impl<T: Serialize> Producer<T> {
103 pub fn new(track: moq_net::TrackProducer, config: Config) -> Self {
105 Self {
106 inner: Arc::new(Mutex::new(Inner {
107 track,
108 group: None,
109 last: None,
110 delta_bytes: 0,
111 group_frames: 0,
112 config,
113 })),
114 _marker: PhantomData,
115 }
116 }
117
118 pub fn update(&mut self, value: &T) -> Result<()> {
122 let json = serde_json::to_value(value)?;
123 let snapshot = serde_json::to_vec(value)?;
126 self.inner.lock().unwrap().update(json, snapshot)
127 }
128
129 pub fn lock(&mut self) -> Guard<'_, T>
140 where
141 T: Default + DeserializeOwned,
142 {
143 let inner = self.inner.lock().unwrap();
144 let value = inner
145 .last
146 .as_ref()
147 .and_then(|last| serde_json::from_value(last.clone()).ok())
148 .unwrap_or_default();
149
150 Guard {
151 inner,
152 value,
153 dirty: false,
154 }
155 }
156
157 pub fn finish(&mut self) -> Result<()> {
159 self.inner.lock().unwrap().finish()
160 }
161}
162
163pub struct Guard<'a, T: Serialize> {
168 inner: MutexGuard<'a, Inner>,
169 value: T,
170 dirty: bool,
171}
172
173impl<T: Serialize> Deref for Guard<'_, T> {
174 type Target = T;
175
176 fn deref(&self) -> &T {
177 &self.value
178 }
179}
180
181impl<T: Serialize> DerefMut for Guard<'_, T> {
182 fn deref_mut(&mut self) -> &mut T {
183 self.dirty = true;
184 &mut self.value
185 }
186}
187
188impl<T: Serialize> Drop for Guard<'_, T> {
189 fn drop(&mut self) {
190 if !self.dirty {
191 return;
192 }
193
194 let Ok(json) = serde_json::to_value(&self.value) else {
195 return;
196 };
197 let Ok(snapshot) = serde_json::to_vec(&self.value) else {
198 return;
199 };
200
201 let _ = self.inner.update(json, snapshot);
203 }
204}
205
206struct Inner {
208 track: moq_net::TrackProducer,
209 group: Option<moq_net::GroupProducer>,
210 last: Option<Value>,
211 delta_bytes: u64,
213 group_frames: usize,
214 config: Config,
215}
216
217impl Inner {
218 fn update(&mut self, json: Value, snapshot: Vec<u8>) -> Result<()> {
219 if self.last.as_ref() == Some(&json) {
220 return Ok(());
221 }
222
223 match self.delta(&json, snapshot.len())? {
224 Some(delta) => {
225 let group = self.group.as_mut().expect("delta requires an open group");
226 let len = delta.len() as u64;
227 group.write_frame(delta)?;
228 self.delta_bytes += len;
229 self.group_frames += 1;
230 }
231 None => self.snapshot(snapshot)?,
232 }
233
234 self.last = Some(json);
235 Ok(())
236 }
237
238 fn delta(&self, value: &Value, snapshot_len: usize) -> Result<Option<Vec<u8>>> {
241 let ratio = self.config.delta_ratio;
242 if ratio == 0 {
243 return Ok(None);
244 }
245 let Some(last) = &self.last else {
246 return Ok(None);
247 };
248 if self.group.is_none() || self.group_frames >= MAX_DELTA_FRAMES {
249 return Ok(None);
250 }
251
252 let diff = diff(last, value);
253 if diff.forced_snapshot {
254 return Ok(None);
255 }
256
257 let delta = serde_json::to_vec(&diff.patch)?;
258
259 let projected = self.delta_bytes + delta.len() as u64;
261 if projected > ratio as u64 * snapshot_len as u64 {
262 return Ok(None);
263 }
264
265 Ok(Some(delta))
266 }
267
268 fn snapshot(&mut self, snapshot: Vec<u8>) -> Result<()> {
270 if let Some(mut group) = self.group.take() {
272 group.finish()?;
273 }
274
275 let mut group = self.track.append_group()?;
276 group.write_frame(snapshot)?;
277 self.delta_bytes = 0;
278 self.group_frames = 1;
279
280 if self.config.delta_ratio != 0 {
281 self.group = Some(group);
283 } else {
284 group.finish()?;
286 }
287
288 Ok(())
289 }
290
291 fn finish(&mut self) -> Result<()> {
292 if let Some(mut group) = self.group.take() {
293 group.finish()?;
294 }
295 self.track.finish()?;
296 Ok(())
297 }
298}
299
300pub struct Consumer<T> {
302 track: moq_net::TrackConsumer,
303 group: Option<moq_net::GroupConsumer>,
304 current: Option<Value>,
305 frames_read: usize,
306 _marker: PhantomData<fn() -> T>,
307}
308
309impl<T> Clone for Consumer<T> {
312 fn clone(&self) -> Self {
313 Self {
314 track: self.track.clone(),
315 group: self.group.clone(),
316 current: self.current.clone(),
317 frames_read: self.frames_read,
318 _marker: PhantomData,
319 }
320 }
321}
322
323impl<T: DeserializeOwned> Consumer<T> {
324 pub fn new(track: moq_net::TrackConsumer) -> Self {
326 Self {
327 track,
328 group: None,
329 current: None,
330 frames_read: 0,
331 _marker: PhantomData,
332 }
333 }
334
335 pub async fn next(&mut self) -> Result<Option<T>>
337 where
338 T: Unpin,
339 {
340 kio::wait(|waiter| self.poll_next(waiter)).await
341 }
342
343 pub fn poll_next(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<T>>> {
348 let track_finished = loop {
350 match self.track.poll_next_group(waiter)? {
351 Poll::Ready(Some(group)) => {
352 self.group = Some(group);
353 self.current = None;
354 self.frames_read = 0;
355 }
356 Poll::Ready(None) => break true,
357 Poll::Pending => break false,
358 }
359 };
360
361 if let Some(group) = &mut self.group {
362 match group.poll_read_frame(waiter)? {
363 Poll::Ready(Some(frame)) => return Poll::Ready(Ok(Some(self.apply(frame)?))),
364 Poll::Ready(None) => self.group = None,
366 Poll::Pending => return Poll::Pending,
367 }
368 }
369
370 if track_finished {
371 Poll::Ready(Ok(None))
372 } else {
373 Poll::Pending
374 }
375 }
376
377 fn apply(&mut self, frame: bytes::Bytes) -> Result<T> {
379 if self.frames_read == 0 {
380 self.current = Some(serde_json::from_slice(&frame)?);
381 } else {
382 let patch: Value = serde_json::from_slice(&frame)?;
383 let current = self.current.as_mut().expect("a snapshot precedes any delta");
384 json_patch::merge(current, &patch);
385 }
386 self.frames_read += 1;
387
388 let current = self
389 .current
390 .as_ref()
391 .expect("a value is present after applying a frame");
392 Ok(serde_json::from_value(current.clone())?)
393 }
394}
395
396#[cfg(test)]
397mod test {
398 use super::*;
399 use serde_json::json;
400
401 fn producer(config: Config) -> (Producer<Value>, moq_net::TrackConsumer) {
402 let track = moq_net::Track::new("test").produce();
403 let consumer = track.consume();
404 (Producer::new(track, config), consumer)
405 }
406
407 fn drain(track: moq_net::TrackConsumer) -> Vec<Value> {
409 let mut consumer = Consumer::<Value>::new(track);
410 let waiter = kio::Waiter::noop();
411 let mut out = Vec::new();
412 while let Poll::Ready(Ok(Some(value))) = consumer.poll_next(&waiter) {
413 out.push(value);
414 }
415 out
416 }
417
418 #[test]
419 fn deltas_off_snapshot_per_group() {
420 let (mut producer, track) = producer(Config { delta_ratio: 0 });
421 producer.update(&json!({ "a": 1 })).unwrap();
422 producer.update(&json!({ "a": 2 })).unwrap();
423 producer.finish().unwrap();
424
425 assert_eq!(track.latest(), Some(1));
428 assert_eq!(drain(track), vec![json!({ "a": 2 })]);
429 }
430
431 #[test]
432 fn live_consumer_sees_each_update() {
433 let (mut producer, track) = producer(Config::default());
434 let mut consumer = Consumer::<Value>::new(track);
435 let waiter = kio::Waiter::noop();
436
437 for n in 1..=3 {
438 producer.update(&json!({ "a": n })).unwrap();
439 match consumer.poll_next(&waiter) {
440 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "a": n })),
441 other => panic!("expected value, got {other:?}"),
442 }
443 }
444 }
445
446 #[test]
447 fn unchanged_value_writes_nothing() {
448 let (mut producer, track) = producer(Config::default());
449 producer.update(&json!({ "a": 1 })).unwrap();
450 producer.update(&json!({ "a": 1 })).unwrap();
451 producer.finish().unwrap();
452
453 assert_eq!(track.latest(), Some(0));
454 assert_eq!(drain(track), vec![json!({ "a": 1 })]);
455 }
456
457 #[test]
458 fn deltas_share_one_group() {
459 let config = Config { delta_ratio: 100 };
460 let (mut producer, track) = producer(config);
461 producer.update(&json!({ "a": 1, "b": 1 })).unwrap();
462 producer.update(&json!({ "a": 1, "b": 2 })).unwrap();
463 producer.update(&json!({ "a": 1, "b": 3 })).unwrap();
464 producer.finish().unwrap();
465
466 assert_eq!(track.latest(), Some(0));
468 let values = drain(track);
469 assert_eq!(values.last().unwrap(), &json!({ "a": 1, "b": 3 }));
470 }
471
472 #[test]
473 fn tight_ratio_rolls_snapshots() {
474 let config = Config { delta_ratio: 1 };
478 let (mut producer, track) = producer(config);
479 producer.update(&json!({ "a": 1 })).unwrap(); producer.update(&json!({ "a": 2 })).unwrap(); producer.update(&json!({ "a": 3 })).unwrap(); producer.update(&json!({ "a": 4 })).unwrap(); producer.finish().unwrap();
484
485 assert_eq!(track.latest(), Some(1));
486 }
487
488 #[test]
489 fn deltas_stay_within_ratio_times_snapshot() {
490 let config = Config { delta_ratio: 8 };
495 let (mut producer, track) = producer(config);
496 for n in 0..=9 {
497 producer.update(&json!({ "n": n })).unwrap();
498 }
499 producer.finish().unwrap();
500
501 assert_eq!(track.latest(), Some(1));
503 assert_eq!(drain(track).last().unwrap(), &json!({ "n": 9 }));
504 }
505
506 #[test]
507 fn array_change_is_delta() {
508 let config = Config { delta_ratio: 100 };
509 let (mut producer, track) = producer(config);
510 producer.update(&json!({ "list": [1, 2] })).unwrap();
511 producer.update(&json!({ "list": [1, 2, 3] })).unwrap();
512 producer.finish().unwrap();
513
514 assert_eq!(track.latest(), Some(0));
516 assert_eq!(drain(track).last().unwrap(), &json!({ "list": [1, 2, 3] }));
517 }
518
519 #[test]
520 fn frame_cap_rolls_snapshot() {
521 let config = Config { delta_ratio: 1_000_000 };
522 let (mut producer, track) = producer(config);
523 for i in 0..=MAX_DELTA_FRAMES {
525 producer.update(&json!({ "n": i })).unwrap();
526 }
527 producer.finish().unwrap();
528
529 assert_eq!(track.latest(), Some(1));
531 assert_eq!(drain(track).last().unwrap(), &json!({ "n": MAX_DELTA_FRAMES }));
532 }
533
534 #[test]
535 fn late_joiner_reconstructs_from_deltas() {
536 let config = Config { delta_ratio: 100 };
537 let (mut producer, track) = producer(config);
538 producer.update(&json!({ "a": 1, "b": 1 })).unwrap();
539 producer.update(&json!({ "a": 1, "b": 2 })).unwrap();
540 producer.update(&json!({ "a": 5, "b": 2 })).unwrap();
541 producer.finish().unwrap();
542
543 assert_eq!(drain(track).last().unwrap(), &json!({ "a": 5, "b": 2 }));
545 }
546
547 #[test]
548 fn lock_composes_independent_owners() {
549 #[derive(serde::Serialize, serde::Deserialize, Default, PartialEq, Debug)]
551 struct Doc {
552 #[serde(skip_serializing_if = "Option::is_none")]
553 video: Option<String>,
554 #[serde(skip_serializing_if = "Option::is_none")]
555 scte35: Option<u32>,
556 }
557
558 let track = moq_net::Track::new("test").produce();
559 let consumer = track.consume();
560 let mut producer = Producer::<Doc>::new(track, Config::default());
561
562 producer.lock().video = Some("v1".to_string());
564
565 producer.lock().scte35 = Some(42);
567
568 let _ = producer.lock();
570
571 producer.finish().unwrap();
572
573 let mut consumer = Consumer::<Doc>::new(consumer);
574 let waiter = kio::Waiter::noop();
575 let mut last = None;
576 while let Poll::Ready(Ok(Some(value))) = consumer.poll_next(&waiter) {
577 last = Some(value);
578 }
579 assert_eq!(
580 last.unwrap(),
581 Doc {
582 video: Some("v1".to_string()),
583 scte35: Some(42),
584 }
585 );
586 }
587
588 #[test]
589 fn newer_group_supersedes_in_progress_reconstruction() {
590 let config = Config { delta_ratio: 1 };
592 let (mut producer, track) = producer(config);
593 let observer = producer.consume();
594 let mut consumer = Consumer::<Value>::new(track);
595 let waiter = kio::Waiter::noop();
596
597 producer.update(&json!({ "a": 1 })).unwrap(); match consumer.poll_next(&waiter) {
599 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "a": 1 })),
600 other => panic!("expected first value, got {other:?}"),
601 }
602
603 producer.update(&json!({ "a": 2 })).unwrap(); producer.update(&json!({ "a": 3 })).unwrap(); producer.finish().unwrap();
606 assert_eq!(observer.latest(), Some(1));
607
608 let mut last = None;
610 while let Poll::Ready(Ok(Some(value))) = consumer.poll_next(&waiter) {
611 last = Some(value);
612 }
613 assert_eq!(last.unwrap(), json!({ "a": 3 }));
614 }
615
616 #[test]
617 fn cloned_consumer_reconstructs_independently() {
618 let config = Config { delta_ratio: 100 };
620 let (mut producer, track) = producer(config);
621 let mut consumer = Consumer::<Value>::new(track);
622 let waiter = kio::Waiter::noop();
623
624 producer.update(&json!({ "a": 1, "b": 1 })).unwrap(); match consumer.poll_next(&waiter) {
626 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "a": 1, "b": 1 })),
627 other => panic!("expected snapshot, got {other:?}"),
628 }
629
630 let mut clone = consumer.clone();
632
633 producer.update(&json!({ "a": 1, "b": 2 })).unwrap(); producer.finish().unwrap();
635
636 let expected = json!({ "a": 1, "b": 2 });
638 for consumer in [&mut consumer, &mut clone] {
639 match consumer.poll_next(&waiter) {
640 Poll::Ready(Ok(Some(value))) => assert_eq!(value, expected),
641 other => panic!("expected delta, got {other:?}"),
642 }
643 }
644 }
645}