Skip to main content

moq_json/snapshot/
consumer.rs

1//! Consuming a JSON value from a track: a [`Decoder`] plus the track it reads from.
2
3use std::task::Poll;
4
5use serde::de::DeserializeOwned;
6
7use super::Decoder;
8use crate::{Compression, Result};
9
10/// Track-owning options for a [`Consumer`].
11///
12/// Build from [`Default`] and override fields (the struct is `#[non_exhaustive]`, so new options
13/// stay additive).
14#[derive(Debug, Clone, Default)]
15#[non_exhaustive]
16pub struct Config {
17	/// How the frames are compressed. Must match the encoder's
18	/// [`Config::compression`](super::Config::compression). Defaults to [`Compression::None`].
19	pub compression: Compression,
20}
21
22/// Consumes a JSON value from a track, reconstructing it from snapshots and deltas.
23///
24/// A [`Decoder`] that owns its track: it reads groups, routes each frame by its position, and
25/// yields the reconstructed value. When something else already owns the track, use the [`Decoder`]
26/// directly.
27pub struct Consumer<T> {
28	track: moq_net::track::Ordered,
29	group: Option<moq_net::group::Consumer>,
30	decoder: Decoder<T>,
31	frames_read: usize,
32}
33
34impl<T: DeserializeOwned> Consumer<T> {
35	/// Create a consumer reading from the given track subscriber.
36	///
37	/// Set [`Config::compression`] to read a track written by a producer with the same
38	/// [`compression`](super::Config::compression).
39	pub fn new(track: moq_net::track::Subscriber, config: Config) -> Self {
40		Self {
41			track: track.ordered(),
42			group: None,
43			decoder: Decoder::new(config),
44			frames_read: 0,
45		}
46	}
47
48	/// Get the next reconstructed value, or `None` once the track ends.
49	pub async fn next(&mut self) -> Result<Option<T>>
50	where
51		T: Unpin,
52	{
53		kio::wait(|waiter| self.poll_next(waiter)).await
54	}
55
56	/// Poll for the next reconstructed value, without blocking.
57	///
58	/// Jumps to the newest group, reads its snapshot, and applies deltas in order. All frames already
59	/// buffered in the group are applied in one poll but only the resulting *latest* value is yielded:
60	/// the intermediate reconstructions are stale, so a late joiner (or any consumer that has fallen
61	/// behind) catches up to the head in a single step instead of replaying every superseded state.
62	/// Frames must still be decoded in order (the DEFLATE window and merge patches are sequential);
63	/// only the per-frame deserialize and yield are skipped. Switching to a newer group discards the
64	/// older one.
65	///
66	/// A group the transport can no longer serve is discarded the same way, not reported: on a
67	/// snapshot track its content is superseded by definition, so the reader waits for the
68	/// replacement. Only a failure of the track itself ends the stream.
69	pub fn poll_next(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<T>>> {
70		// Drain to the newest group, resetting reconstruction state whenever we switch.
71		let track_finished = loop {
72			match self.track.poll_next_group(waiter)? {
73				Poll::Ready(Some(group)) => {
74					self.group = Some(group);
75					// The next frame is the new group's snapshot, which also restarts the decoder's window.
76					self.frames_read = 0;
77				}
78				Poll::Ready(None) => break true,
79				Poll::Pending => break false,
80			}
81		};
82
83		// Apply every frame currently buffered in the group, tracking whether any moved us forward and
84		// whether the group is still open with nothing buffered yet (vs. exhausted).
85		// `poll_read_frame` returns an owned `Poll`, so the borrow of `self.group` ends before the
86		// match arms, leaving `apply` (and clearing the group) free to take `&mut self`.
87		let mut advanced = false;
88		let mut group_pending = false;
89		while let Some(group) = &mut self.group {
90			match group.poll_read_frame(waiter) {
91				Poll::Ready(Ok(Some(frame))) => {
92					self.apply(&frame.payload)?;
93					advanced = true;
94				}
95				// The current group is exhausted; wait for a newer one.
96				Poll::Ready(Ok(None)) => {
97					self.group = None;
98					break;
99				}
100				// The transport can no longer serve the rest of this group: it was superseded and
101				// reclaimed (`Old`), dropped under memory pressure (`Evicted`), or read past the
102				// drift budget (`Lagged`). A snapshot reader only ever wants the newest value, so a
103				// group whose content is gone is never fatal: drop it and wait for its replacement.
104				// A track- or session-level failure still arrives through `poll_next_group` above.
105				Poll::Ready(Err(err)) => {
106					let sequence = group.sequence;
107					self.group = None;
108					tracing::warn!(
109						track = self.track.name(),
110						group = sequence,
111						error = ?err,
112						"snapshot group lost; waiting for a newer one"
113					);
114					break;
115				}
116				// The group is still open but has nothing buffered yet.
117				Poll::Pending => {
118					group_pending = true;
119					break;
120				}
121			}
122		}
123
124		if advanced {
125			// Deserialize once, from the head of the backlog we just drained.
126			return Poll::Ready(Ok(self.decoder.decode()?));
127		}
128
129		// An open group may still deliver frames even after the track finishes (it was appended before
130		// the finish), so wait on it rather than ending the stream.
131		if group_pending {
132			return Poll::Pending;
133		}
134
135		if track_finished {
136			Poll::Ready(Ok(None))
137		} else {
138			Poll::Pending
139		}
140	}
141
142	/// Apply one frame: frame 0 of a group is a snapshot, the rest are merge patches.
143	fn apply(&mut self, payload: &[u8]) -> Result<()> {
144		match self.frames_read {
145			0 => self.decoder.snapshot(payload)?,
146			_ => self.decoder.delta(payload)?,
147		}
148		self.frames_read += 1;
149		Ok(())
150	}
151}