1use std::marker::PhantomData;
20use std::ops::{Deref, DerefMut};
21use std::sync::{Arc, Mutex, MutexGuard};
22use std::task::Poll;
23
24use bytes::Bytes;
25use moq_flate::{Decoder, Encoder};
26use serde::Serialize;
27use serde::de::DeserializeOwned;
28use serde_json::Value;
29
30use crate::{Diff, Result, diff};
31
32const MAX_DELTA_FRAMES: usize = 256;
37
38#[derive(Debug, Clone)]
43#[non_exhaustive]
44pub struct ProducerConfig {
45 pub delta_ratio: u32,
60
61 pub compression: bool,
67}
68
69impl Default for ProducerConfig {
70 fn default() -> Self {
71 Self {
72 delta_ratio: 8,
73 compression: false,
74 }
75 }
76}
77
78#[derive(Debug, Clone, Default)]
83#[non_exhaustive]
84pub struct ConsumerConfig {
85 pub compression: bool,
88}
89
90pub struct Producer<T> {
95 inner: Arc<Mutex<Inner>>,
96 _marker: PhantomData<fn(T)>,
97}
98
99impl<T> Clone for Producer<T> {
100 fn clone(&self) -> Self {
101 Self {
102 inner: self.inner.clone(),
103 _marker: PhantomData,
104 }
105 }
106}
107
108impl<T> Producer<T> {
109 pub fn consume(&self) -> moq_net::TrackConsumer {
111 self.inner.lock().unwrap().track.consume()
112 }
113}
114
115impl<T: Serialize> Producer<T> {
116 pub fn new(track: moq_net::TrackProducer, config: ProducerConfig) -> Self {
118 Self {
119 inner: Arc::new(Mutex::new(Inner {
120 track,
121 group: None,
122 encoder: None,
123 last: None,
124 delta_bytes: 0,
125 snapshot_len: 0,
126 group_frames: 0,
127 config,
128 })),
129 _marker: PhantomData,
130 }
131 }
132
133 pub fn update(&mut self, value: &T) -> Result<()> {
137 self.inner.lock().unwrap().update(value)
138 }
139
140 pub fn lock(&mut self) -> Guard<'_, T>
151 where
152 T: Default + DeserializeOwned,
153 {
154 let inner = self.inner.lock().unwrap();
155 let value = inner
156 .last
157 .as_ref()
158 .and_then(|last| serde_json::from_value(last.clone()).ok())
159 .unwrap_or_default();
160
161 Guard {
162 inner,
163 value,
164 dirty: false,
165 }
166 }
167
168 pub fn finish(&mut self) -> Result<()> {
170 self.inner.lock().unwrap().finish()
171 }
172}
173
174pub struct Guard<'a, T: Serialize> {
179 inner: MutexGuard<'a, Inner>,
180 value: T,
181 dirty: bool,
182}
183
184impl<T: Serialize> Deref for Guard<'_, T> {
185 type Target = T;
186
187 fn deref(&self) -> &T {
188 &self.value
189 }
190}
191
192impl<T: Serialize> DerefMut for Guard<'_, T> {
193 fn deref_mut(&mut self) -> &mut T {
194 self.dirty = true;
195 &mut self.value
196 }
197}
198
199impl<T: Serialize> Drop for Guard<'_, T> {
200 fn drop(&mut self) {
201 if !self.dirty {
202 return;
203 }
204
205 let _ = self.inner.update(&self.value);
207 }
208}
209
210struct Inner {
212 track: moq_net::TrackProducer,
213 group: Option<moq_net::GroupProducer>,
214 encoder: Option<Encoder>,
216 last: Option<Value>,
217 delta_bytes: u64,
220 snapshot_len: u64,
223 group_frames: usize,
224 config: ProducerConfig,
225}
226
227impl Inner {
228 fn update<T: Serialize>(&mut self, value: &T) -> Result<()> {
229 let Some(last) = self.last.as_ref() else {
232 return self.snapshot(value);
233 };
234
235 let Diff { patch, forced_snapshot } = diff(last, value);
237
238 if !forced_snapshot && patch.as_object().is_some_and(serde_json::Map::is_empty) {
240 return Ok(());
241 }
242
243 if forced_snapshot || !self.delta_allowed() {
246 return self.snapshot(value);
247 }
248
249 let bytes = serde_json::to_vec(&patch)?;
251 let slice = match self.encoder.as_mut() {
252 Some(encoder) => encoder.frame(&bytes),
253 None => Bytes::from(bytes),
254 };
255 let len = slice.len() as u64;
256 self.group
257 .as_mut()
258 .expect("delta_allowed guarantees an open group")
259 .write_frame(slice)?;
260 self.delta_bytes += len;
261 self.group_frames += 1;
262
263 json_patch::merge(self.last.as_mut().expect("a snapshot precedes any delta"), &patch);
265 Ok(())
266 }
267
268 fn delta_allowed(&self) -> bool {
276 let ratio = self.config.delta_ratio as u64;
277 ratio != 0
278 && self.group.is_some()
279 && self.group_frames < MAX_DELTA_FRAMES
280 && self.delta_bytes <= ratio * self.snapshot_len
281 }
282
283 fn snapshot<T: Serialize>(&mut self, value: &T) -> Result<()> {
285 let snapshot = serde_json::to_vec(value)?;
288
289 if let Some(mut group) = self.group.take() {
291 group.finish()?;
292 }
293
294 let mut group = self.track.append_group()?;
295
296 let (slice, encoder) = if self.config.compression {
299 let mut encoder = Encoder::new();
300 let slice = encoder.frame(&snapshot);
301 (slice, Some(encoder))
302 } else {
303 (Bytes::from(snapshot), None)
304 };
305 self.snapshot_len = slice.len() as u64;
306 group.write_frame(slice)?;
307 self.delta_bytes = 0;
308 self.group_frames = 1;
309 self.encoder = encoder;
310
311 if self.config.delta_ratio != 0 {
312 self.group = Some(group);
314 } else {
315 self.encoder = None;
317 group.finish()?;
318 }
319
320 self.last = Some(serde_json::to_value(value)?);
322 Ok(())
323 }
324
325 fn finish(&mut self) -> Result<()> {
326 if let Some(mut group) = self.group.take() {
327 group.finish()?;
328 }
329 self.track.finish()?;
330 Ok(())
331 }
332}
333
334pub struct Consumer<T> {
336 track: moq_net::TrackConsumer,
337 group: Option<moq_net::GroupConsumer>,
338 compressed: bool,
340 decoder: Option<Decoder>,
342 group_slices: Vec<Bytes>,
345 current: Option<Value>,
346 frames_read: usize,
347 _marker: PhantomData<fn() -> T>,
348}
349
350impl<T> Clone for Consumer<T> {
353 fn clone(&self) -> Self {
354 Self {
355 track: self.track.clone(),
356 group: self.group.clone(),
357 compressed: self.compressed,
358 decoder: None,
361 group_slices: self.group_slices.clone(),
362 current: self.current.clone(),
363 frames_read: self.frames_read,
364 _marker: PhantomData,
365 }
366 }
367}
368
369impl<T: DeserializeOwned> Consumer<T> {
370 pub fn new(track: moq_net::TrackConsumer, config: ConsumerConfig) -> Self {
375 Self {
376 track,
377 group: None,
378 compressed: config.compression,
379 decoder: None,
380 group_slices: Vec::new(),
381 current: None,
382 frames_read: 0,
383 _marker: PhantomData,
384 }
385 }
386
387 pub async fn next(&mut self) -> Result<Option<T>>
389 where
390 T: Unpin,
391 {
392 kio::wait(|waiter| self.poll_next(waiter)).await
393 }
394
395 pub fn poll_next(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<T>>> {
405 let track_finished = loop {
407 match self.track.poll_next_group(waiter)? {
408 Poll::Ready(Some(group)) => {
409 self.group = Some(group);
410 self.current = None;
411 self.frames_read = 0;
412 self.decoder = None;
414 self.group_slices.clear();
415 }
416 Poll::Ready(None) => break true,
417 Poll::Pending => break false,
418 }
419 };
420
421 let mut advanced = false;
426 let mut group_pending = false;
427 while let Some(group) = &mut self.group {
428 match group.poll_read_frame(waiter)? {
429 Poll::Ready(Some(frame)) => {
430 self.apply(frame)?;
431 advanced = true;
432 }
433 Poll::Ready(None) => {
435 self.group = None;
436 break;
437 }
438 Poll::Pending => {
440 group_pending = true;
441 break;
442 }
443 }
444 }
445
446 if advanced {
447 return Poll::Ready(Ok(Some(self.reconstruct()?)));
449 }
450
451 if group_pending {
454 return Poll::Pending;
455 }
456
457 if track_finished {
458 Poll::Ready(Ok(None))
459 } else {
460 Poll::Pending
461 }
462 }
463
464 fn decode(&mut self, slice: Bytes) -> Result<Bytes> {
470 if !self.compressed {
471 return Ok(slice);
472 }
473
474 if self.decoder.is_none() {
475 let mut decoder = Decoder::new();
476 for prev in &self.group_slices {
477 decoder.frame(prev)?;
478 }
479 self.decoder = Some(decoder);
480 }
481
482 let plain = self.decoder.as_mut().unwrap().frame(&slice)?;
483 self.group_slices.push(slice);
484 Ok(plain)
485 }
486
487 fn apply(&mut self, frame: Bytes) -> Result<()> {
490 let frame = self.decode(frame)?;
491 if self.frames_read == 0 {
492 self.current = Some(serde_json::from_slice(&frame)?);
493 } else {
494 let patch: Value = serde_json::from_slice(&frame)?;
495 let current = self.current.as_mut().expect("a snapshot precedes any delta");
496 json_patch::merge(current, &patch);
497 }
498 self.frames_read += 1;
499 Ok(())
500 }
501
502 fn reconstruct(&self) -> Result<T> {
505 let current = self
506 .current
507 .as_ref()
508 .expect("a value is present after applying a frame");
509 Ok(serde_json::from_value(current.clone())?)
510 }
511}
512
513#[cfg(test)]
514mod test {
515 use super::*;
516 use serde_json::json;
517
518 fn cfg(delta_ratio: u32) -> ProducerConfig {
520 ProducerConfig {
521 delta_ratio,
522 ..Default::default()
523 }
524 }
525
526 fn cfg_deflate(delta_ratio: u32) -> ProducerConfig {
528 ProducerConfig {
529 delta_ratio,
530 compression: true,
531 }
532 }
533
534 fn deflate_consumer(track: moq_net::TrackConsumer) -> Consumer<Value> {
536 Consumer::new(track, ConsumerConfig { compression: true })
537 }
538
539 fn producer(config: ProducerConfig) -> (Producer<Value>, moq_net::TrackConsumer) {
540 let track = moq_net::Track::new("test").produce();
541 let consumer = track.consume();
542 (Producer::new(track, config), consumer)
543 }
544
545 fn drain(track: moq_net::TrackConsumer) -> Vec<Value> {
547 drain_with(Consumer::<Value>::new(track, ConsumerConfig::default()))
548 }
549
550 fn drain_with(mut consumer: Consumer<Value>) -> Vec<Value> {
552 let waiter = kio::Waiter::noop();
553 let mut out = Vec::new();
554 while let Poll::Ready(Ok(Some(value))) = consumer.poll_next(&waiter) {
555 out.push(value);
556 }
557 out
558 }
559
560 #[test]
561 fn deltas_off_snapshot_per_group() {
562 let (mut producer, track) = producer(cfg(0));
563 producer.update(&json!({ "a": 1 })).unwrap();
564 producer.update(&json!({ "a": 2 })).unwrap();
565 producer.finish().unwrap();
566
567 assert_eq!(track.latest(), Some(1));
570 assert_eq!(drain(track), vec![json!({ "a": 2 })]);
571 }
572
573 #[test]
574 fn live_consumer_sees_each_update() {
575 let (mut producer, track) = producer(ProducerConfig::default());
576 let mut consumer = Consumer::<Value>::new(track, ConsumerConfig::default());
577 let waiter = kio::Waiter::noop();
578
579 for n in 1..=3 {
580 producer.update(&json!({ "a": n })).unwrap();
581 match consumer.poll_next(&waiter) {
582 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "a": n })),
583 other => panic!("expected value, got {other:?}"),
584 }
585 }
586 }
587
588 #[test]
589 fn unchanged_value_writes_nothing() {
590 let (mut producer, track) = producer(ProducerConfig::default());
591 producer.update(&json!({ "a": 1 })).unwrap();
592 producer.update(&json!({ "a": 1 })).unwrap();
593 producer.finish().unwrap();
594
595 assert_eq!(track.latest(), Some(0));
596 assert_eq!(drain(track), vec![json!({ "a": 1 })]);
597 }
598
599 #[test]
600 fn deltas_share_one_group() {
601 let config = cfg(100);
602 let (mut producer, track) = producer(config);
603 producer.update(&json!({ "a": 1, "b": 1 })).unwrap();
604 producer.update(&json!({ "a": 1, "b": 2 })).unwrap();
605 producer.update(&json!({ "a": 1, "b": 3 })).unwrap();
606 producer.finish().unwrap();
607
608 assert_eq!(track.latest(), Some(0));
610 let values = drain(track);
611 assert_eq!(values.last().unwrap(), &json!({ "a": 1, "b": 3 }));
612 }
613
614 #[test]
615 fn tight_ratio_rolls_snapshots() {
616 let config = cfg(1);
621 let (mut producer, track) = producer(config);
622 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();
627
628 assert_eq!(track.latest(), Some(1));
629 }
630
631 #[test]
632 fn deltas_stay_within_ratio_times_snapshot() {
633 let config = cfg(8);
639 let (mut producer, track) = producer(config);
640 for n in 0..=10 {
641 producer.update(&json!({ "n": n })).unwrap();
642 }
643 producer.finish().unwrap();
644
645 assert_eq!(track.latest(), Some(1));
647 assert_eq!(drain(track).last().unwrap(), &json!({ "n": 10 }));
648 }
649
650 #[test]
651 fn array_change_is_delta() {
652 let config = cfg(100);
653 let (mut producer, track) = producer(config);
654 producer.update(&json!({ "list": [1, 2] })).unwrap();
655 producer.update(&json!({ "list": [1, 2, 3] })).unwrap();
656 producer.finish().unwrap();
657
658 assert_eq!(track.latest(), Some(0));
660 assert_eq!(drain(track).last().unwrap(), &json!({ "list": [1, 2, 3] }));
661 }
662
663 #[test]
664 fn frame_cap_rolls_snapshot() {
665 let config = cfg(1_000_000);
666 let (mut producer, track) = producer(config);
667 for i in 0..=MAX_DELTA_FRAMES {
669 producer.update(&json!({ "n": i })).unwrap();
670 }
671 producer.finish().unwrap();
672
673 assert_eq!(track.latest(), Some(1));
675 assert_eq!(drain(track).last().unwrap(), &json!({ "n": MAX_DELTA_FRAMES }));
676 }
677
678 #[test]
679 fn late_joiner_reconstructs_from_deltas() {
680 let config = cfg(100);
681 let (mut producer, track) = producer(config);
682 producer.update(&json!({ "a": 1, "b": 1 })).unwrap();
683 producer.update(&json!({ "a": 1, "b": 2 })).unwrap();
684 producer.update(&json!({ "a": 5, "b": 2 })).unwrap();
685 producer.finish().unwrap();
686
687 assert_eq!(drain(track).last().unwrap(), &json!({ "a": 5, "b": 2 }));
689 }
690
691 #[test]
692 fn lock_composes_independent_owners() {
693 #[derive(serde::Serialize, serde::Deserialize, Default, PartialEq, Debug)]
695 struct Doc {
696 #[serde(skip_serializing_if = "Option::is_none")]
697 video: Option<String>,
698 #[serde(skip_serializing_if = "Option::is_none")]
699 scte35: Option<u32>,
700 }
701
702 let track = moq_net::Track::new("test").produce();
703 let consumer = track.consume();
704 let mut producer = Producer::<Doc>::new(track, ProducerConfig::default());
705
706 producer.lock().video = Some("v1".to_string());
708
709 producer.lock().scte35 = Some(42);
711
712 let _ = producer.lock();
714
715 producer.finish().unwrap();
716
717 let mut consumer = Consumer::<Doc>::new(consumer, ConsumerConfig::default());
718 let waiter = kio::Waiter::noop();
719 let mut last = None;
720 while let Poll::Ready(Ok(Some(value))) = consumer.poll_next(&waiter) {
721 last = Some(value);
722 }
723 assert_eq!(
724 last.unwrap(),
725 Doc {
726 video: Some("v1".to_string()),
727 scte35: Some(42),
728 }
729 );
730 }
731
732 #[test]
733 fn newer_group_supersedes_in_progress_reconstruction() {
734 let config = cfg(1);
737 let (mut producer, track) = producer(config);
738 let observer = producer.consume();
739 let mut consumer = Consumer::<Value>::new(track, ConsumerConfig::default());
740 let waiter = kio::Waiter::noop();
741
742 producer.update(&json!({ "a": 1 })).unwrap(); match consumer.poll_next(&waiter) {
744 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "a": 1 })),
745 other => panic!("expected first value, got {other:?}"),
746 }
747
748 producer.update(&json!({ "a": 2 })).unwrap(); producer.update(&json!({ "a": 3 })).unwrap(); producer.update(&json!({ "a": 4 })).unwrap(); producer.finish().unwrap();
752 assert_eq!(observer.latest(), Some(1));
753
754 let mut last = None;
756 while let Poll::Ready(Ok(Some(value))) = consumer.poll_next(&waiter) {
757 last = Some(value);
758 }
759 assert_eq!(last.unwrap(), json!({ "a": 4 }));
760 }
761
762 #[test]
763 fn cloned_consumer_reconstructs_independently() {
764 let config = cfg(100);
766 let (mut producer, track) = producer(config);
767 let mut consumer = Consumer::<Value>::new(track, ConsumerConfig::default());
768 let waiter = kio::Waiter::noop();
769
770 producer.update(&json!({ "a": 1, "b": 1 })).unwrap(); match consumer.poll_next(&waiter) {
772 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "a": 1, "b": 1 })),
773 other => panic!("expected snapshot, got {other:?}"),
774 }
775
776 let mut clone = consumer.clone();
778
779 producer.update(&json!({ "a": 1, "b": 2 })).unwrap(); producer.finish().unwrap();
781
782 let expected = json!({ "a": 1, "b": 2 });
784 for consumer in [&mut consumer, &mut clone] {
785 match consumer.poll_next(&waiter) {
786 Poll::Ready(Ok(Some(value))) => assert_eq!(value, expected),
787 other => panic!("expected delta, got {other:?}"),
788 }
789 }
790 }
791
792 #[test]
793 fn open_group_pends_after_track_finish() {
794 let mut track = moq_net::Track::new("test").produce();
797 let mut group = track.append_group().unwrap();
798 track.finish().unwrap();
799
800 let mut consumer = Consumer::<Value>::new(track.consume(), ConsumerConfig::default());
801 let waiter = kio::Waiter::noop();
802
803 assert!(matches!(consumer.poll_next(&waiter), Poll::Pending));
805
806 group
807 .write_frame(Bytes::from(serde_json::to_vec(&json!({ "a": 1 })).unwrap()))
808 .unwrap();
809 group.finish().unwrap();
810
811 match consumer.poll_next(&waiter) {
812 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "a": 1 })),
813 other => panic!("expected the catalog value, got {other:?}"),
814 }
815 }
816
817 #[test]
818 fn late_joiner_collapses_backlog_to_latest() {
819 let (mut producer, track) = producer(cfg(100));
822 for n in 0..=20 {
823 producer.update(&json!({ "n": n })).unwrap();
824 }
825 producer.finish().unwrap();
826
827 assert_eq!(track.latest(), Some(0));
829 let values = drain(track);
830 assert_eq!(
831 values,
832 vec![json!({ "n": 20 })],
833 "backlog should collapse to the latest value"
834 );
835 }
836
837 #[test]
838 fn compressed_late_joiner_collapses_backlog_to_latest() {
839 let (mut producer, track) = producer(cfg_deflate(100));
841 for n in 0..=20 {
842 producer.update(&json!({ "n": n })).unwrap();
843 }
844 producer.finish().unwrap();
845
846 assert_eq!(track.latest(), Some(0));
847 let values = drain_with(deflate_consumer(track));
848 assert_eq!(
849 values,
850 vec![json!({ "n": 20 })],
851 "compressed backlog should collapse to the latest"
852 );
853 }
854
855 #[test]
856 fn compressed_snapshot_per_group_roundtrips() {
857 let (mut producer, track) = producer(cfg_deflate(0));
858 producer.update(&json!({ "a": 1 })).unwrap();
859 producer.update(&json!({ "a": 2 })).unwrap();
860 producer.finish().unwrap();
861
862 assert_eq!(track.latest(), Some(1));
864 let values = drain_with(deflate_consumer(track));
865 assert_eq!(values, vec![json!({ "a": 2 })]);
866 }
867
868 #[test]
869 fn compressed_deltas_share_one_group() {
870 let (mut producer, track) = producer(cfg_deflate(100));
871 producer.update(&json!({ "a": 1, "b": 1 })).unwrap();
872 producer.update(&json!({ "a": 1, "b": 2 })).unwrap();
873 producer.update(&json!({ "a": 1, "b": 3 })).unwrap();
874 producer.finish().unwrap();
875
876 assert_eq!(track.latest(), Some(0));
878 let values = drain_with(deflate_consumer(track));
879 assert_eq!(values.last().unwrap(), &json!({ "a": 1, "b": 3 }));
880 }
881
882 #[test]
883 fn compressed_late_joiner_reconstructs_from_deltas() {
884 let (mut producer, track) = producer(cfg_deflate(100));
885 producer.update(&json!({ "a": 1, "b": 1 })).unwrap();
886 producer.update(&json!({ "a": 1, "b": 2 })).unwrap();
887 producer.update(&json!({ "a": 5, "b": 2 })).unwrap();
888 producer.finish().unwrap();
889
890 let values = drain_with(deflate_consumer(track));
892 assert_eq!(values.last().unwrap(), &json!({ "a": 5, "b": 2 }));
893 }
894
895 #[test]
896 fn compressed_deltas_roll_on_compressed_budget() {
897 let (mut producer, track) = producer(cfg_deflate(2));
903 for n in 0..=40 {
904 producer.update(&json!({ "n": n })).unwrap();
905 }
906 producer.finish().unwrap();
907
908 assert!(
909 track.latest().unwrap() > 0,
910 "a tight ratio should roll at least one compressed group"
911 );
912 assert_eq!(drain_with(deflate_consumer(track)).last().unwrap(), &json!({ "n": 40 }));
913 }
914
915 #[test]
916 fn compressed_cloned_consumer_reconstructs_mid_group() {
917 let (mut producer, track) = producer(cfg_deflate(100));
919 let mut consumer = deflate_consumer(track);
920 let waiter = kio::Waiter::noop();
921
922 producer.update(&json!({ "a": 1, "b": 1 })).unwrap(); match consumer.poll_next(&waiter) {
924 Poll::Ready(Ok(Some(value))) => assert_eq!(value, json!({ "a": 1, "b": 1 })),
925 other => panic!("expected snapshot, got {other:?}"),
926 }
927
928 let mut clone = consumer.clone();
929
930 producer.update(&json!({ "a": 1, "b": 2 })).unwrap(); producer.finish().unwrap();
932
933 let expected = json!({ "a": 1, "b": 2 });
934 for consumer in [&mut consumer, &mut clone] {
935 match consumer.poll_next(&waiter) {
936 Poll::Ready(Ok(Some(value))) => assert_eq!(value, expected),
937 other => panic!("expected delta, got {other:?}"),
938 }
939 }
940 }
941
942 #[test]
943 fn compression_shrinks_wire_frames() {
944 let value = json!({ "renditions": ["video".repeat(50), "video".repeat(50), "video".repeat(50)] });
946
947 let plaintext_bytes = wire_frame_len(cfg(0), &value);
948 let compressed_bytes = wire_frame_len(cfg_deflate(0), &value);
949 assert!(
950 compressed_bytes < plaintext_bytes,
951 "compressed frame {compressed_bytes} should be smaller than plaintext {plaintext_bytes}"
952 );
953 }
954
955 #[test]
956 fn compressed_deltas_reuse_window() {
957 let (mut producer, mut track) = producer(cfg_deflate(100));
960 let phrase = "Media over QUIC delivers real-time latency at massive scale";
961 producer.update(&json!({ "note": phrase })).unwrap();
962 producer.update(&json!({ "note": phrase, "echo": phrase })).unwrap();
963 producer.finish().unwrap();
964
965 let waiter = kio::Waiter::noop();
967 let Poll::Ready(Ok(Some(mut group))) = track.poll_next_group(&waiter) else {
968 panic!("expected a group");
969 };
970 let mut frames = Vec::new();
971 while let Poll::Ready(Ok(Some(frame))) = group.poll_read_frame(&waiter) {
972 frames.push(frame);
973 }
974 assert_eq!(frames.len(), 2, "snapshot + one delta in a single group");
975
976 let raw_delta = serde_json::to_vec(&json!({ "echo": phrase })).unwrap();
978 assert!(
979 frames[1].len() < raw_delta.len() / 2,
980 "windowed delta {} should be far below the raw patch {}",
981 frames[1].len(),
982 raw_delta.len()
983 );
984 }
985
986 fn wire_frame_len(config: ProducerConfig, value: &Value) -> usize {
988 let (mut producer, mut track) = producer(config);
989 producer.update(value).unwrap();
990 producer.finish().unwrap();
991
992 let waiter = kio::Waiter::noop();
993 let Poll::Ready(Ok(Some(mut group))) = track.poll_next_group(&waiter) else {
994 panic!("expected a group");
995 };
996 let Poll::Ready(Ok(Some(frame))) = group.poll_read_frame(&waiter) else {
998 panic!("expected a frame");
999 };
1000 frame.len()
1001 }
1002}