Skip to main content

moq_binary/snapshot/
consumer.rs

1//! Consuming a binary value from a track.
2
3use std::task::Poll;
4
5use bytes::Bytes;
6
7use crate::Result;
8
9pub use super::Config;
10
11/// Consumes a binary value from a track, yielding the newest one.
12///
13/// Jumps to the newest group and reads the value out of it, so a late joiner starts at the current
14/// value rather than replaying superseded ones.
15pub struct Consumer {
16	track: moq_net::track::Ordered,
17	group: Option<moq_net::group::Consumer>,
18	/// The DEFLATE decoder for the current group, `Some` while decompressing. A snapshot group is
19	/// normally one frame, but the window is per group either way.
20	flate: Option<moq_flate::Decoder>,
21	compression: bool,
22}
23
24impl Consumer {
25	/// Create a consumer reading from the given track subscriber.
26	///
27	/// Set [`Config::compression`] to match the producer that wrote the track.
28	pub fn new(track: moq_net::track::Subscriber, config: Config) -> Self {
29		Self {
30			track: track.ordered(),
31			group: None,
32			flate: None,
33			compression: config.compression.is_deflate(),
34		}
35	}
36
37	/// Get the next value, or `None` once the track ends.
38	pub async fn next(&mut self) -> Result<Option<Bytes>> {
39		kio::wait(|waiter| self.poll_next(waiter)).await
40	}
41
42	/// Poll for the next value, without blocking.
43	///
44	/// Jumps to the newest group and drains everything buffered in it, yielding only the last value:
45	/// the earlier ones are already superseded, so a consumer that has fallen behind catches up to
46	/// the head in a single step. A compressed group's frames are still decoded in order, since they
47	/// share one window; only the yield is skipped. Switching to a newer group discards the older one.
48	///
49	/// A group the transport can no longer serve is discarded the same way, not reported: on a
50	/// snapshot track its content is superseded by definition, so the reader waits for the
51	/// replacement. Only a failure of the track itself ends the stream.
52	pub fn poll_next(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Bytes>>> {
53		// Drain to the newest group, starting a cold window whenever we switch.
54		let track_finished = loop {
55			match self.track.poll_next_group(waiter)? {
56				Poll::Ready(Some(group)) => {
57					self.group = Some(group);
58					self.flate = self.compression.then(moq_flate::Decoder::new);
59				}
60				Poll::Ready(None) => break true,
61				Poll::Pending => break false,
62			}
63		};
64
65		// Decode every frame currently buffered in the group, keeping only the last.
66		// `poll_read_frame` returns an owned `Poll`, so the borrow of `self.group` ends before the
67		// match arms, leaving `decode` (and clearing the group) free to take `&mut self`.
68		let mut latest = None;
69		let mut group_pending = false;
70		while let Some(group) = &mut self.group {
71			match group.poll_read_frame(waiter) {
72				Poll::Ready(Ok(Some(frame))) => latest = Some(self.decode(&frame.payload)?),
73				// The current group is exhausted; wait for a newer one.
74				Poll::Ready(Ok(None)) => {
75					self.group = None;
76					break;
77				}
78				// The transport can no longer serve the rest of this group: it was superseded and
79				// reclaimed (`Old`), dropped under memory pressure (`Evicted`), or read past the
80				// drift budget (`Lagged`). A snapshot reader only ever wants the newest value, so a
81				// group whose content is gone is never fatal: drop it and wait for its replacement.
82				// A track- or session-level failure still arrives through `poll_next_group` above.
83				Poll::Ready(Err(err)) => {
84					let sequence = group.sequence;
85					self.group = None;
86					tracing::warn!(
87						track = self.track.name(),
88						group = sequence,
89						error = ?err,
90						"snapshot group lost; waiting for a newer one"
91					);
92					break;
93				}
94				// The group is still open but has nothing buffered yet.
95				Poll::Pending => {
96					group_pending = true;
97					break;
98				}
99			}
100		}
101
102		if let Some(payload) = latest {
103			return Poll::Ready(Ok(Some(payload)));
104		}
105
106		// An open group may still deliver frames even after the track finishes (it was appended before
107		// the finish), so wait on it rather than ending the stream.
108		if group_pending {
109			return Poll::Pending;
110		}
111
112		match track_finished {
113			true => Poll::Ready(Ok(None)),
114			false => Poll::Pending,
115		}
116	}
117
118	/// Decompress one frame, if the track is compressed.
119	fn decode(&mut self, payload: &Bytes) -> Result<Bytes> {
120		Ok(match self.flate.as_mut() {
121			Some(flate) => flate.frame(payload)?,
122			None => payload.clone(),
123		})
124	}
125}