Skip to main content

moq_json/window/
producer.rs

1//! Publishing a window over a track: an [`Encoder`] plus the track it writes to.
2
3use std::marker::PhantomData;
4use std::sync::{Arc, Mutex};
5
6use serde::Serialize;
7use serde_json::Value;
8
9use super::{Encoded, Encoder, ProducerConfig};
10use crate::Result;
11
12/// Publishes a sliding window of JSON records over a track.
13///
14/// An [`Encoder`] that owns its track: it writes each encoded frame and rolls a group whenever the
15/// encoder emits a header. When something else already owns the track, use the [`Encoder`] directly.
16///
17/// Cheaply clonable: clones share one underlying track and window, like other MoQ producers.
18pub struct Producer<T> {
19	inner: Arc<Mutex<Inner<T>>>,
20	_marker: PhantomData<fn(T)>,
21}
22
23impl<T> Clone for Producer<T> {
24	fn clone(&self) -> Self {
25		Self {
26			inner: self.inner.clone(),
27			_marker: PhantomData,
28		}
29	}
30}
31
32impl<T> Producer<T> {
33	/// Create a producer that publishes to the given track.
34	pub fn new(track: moq_net::track::Producer, config: ProducerConfig) -> Self {
35		Self {
36			inner: Arc::new(Mutex::new(Inner {
37				track: Track {
38					inner: track,
39					group: None,
40				},
41				encoder: Encoder::new(config),
42				finished: false,
43			})),
44			_marker: PhantomData,
45		}
46	}
47
48	/// Create a subscriber for the underlying track.
49	pub fn consume(&self) -> moq_net::track::Subscriber {
50		self.inner.lock().unwrap().track.inner.subscribe(None)
51	}
52
53	/// The retained window, oldest first.
54	pub fn window(&self) -> Vec<Value> {
55		self.inner.lock().unwrap().encoder.window()
56	}
57
58	/// Absolute index of the oldest retained record, and of the next to be pushed.
59	pub fn range(&self) -> std::ops::Range<u64> {
60		self.inner.lock().unwrap().encoder.range()
61	}
62
63	/// Drop `count` records from the front of the window.
64	///
65	/// A no-op when the window is already empty, and clamped to what it holds, so a caller can trim
66	/// unconditionally.
67	pub fn pop(&mut self, count: u64) -> Result<()> {
68		self.inner.lock().unwrap().pop(count)
69	}
70
71	/// Finish the track, closing any open group.
72	pub fn finish(self) -> Result<()> {
73		self.inner.lock().unwrap().finish()
74	}
75}
76
77impl<T: Serialize> Producer<T> {
78	/// Append one record to the back of the window.
79	pub fn push(&mut self, value: &T) -> Result<()> {
80		self.inner.lock().unwrap().push(value)
81	}
82}
83
84/// Shared publishing state behind [`Producer`]'s `Arc<Mutex>`.
85///
86/// The track and the encoder are separate fields so a [`Pending`](super::Pending) frame (which
87/// borrows the encoder) and the write that consumes it (which borrows the track) don't contend for
88/// one `&mut self`.
89struct Inner<T> {
90	track: Track,
91	encoder: Encoder<T>,
92	finished: bool,
93}
94
95impl<T> Inner<T> {
96	fn pop(&mut self, count: u64) -> Result<()> {
97		self.ensure_open()?;
98		let Inner { track, encoder, .. } = self;
99
100		let Some(frame) = encoder.pop(count)? else {
101			return Ok(());
102		};
103
104		// A failed write drops the frame uncommitted. The pop is discarded, and the next edit opens a
105		// new group because the attempted frame advanced the group-local compression state.
106		track.write(&frame)?;
107		frame.commit();
108
109		Ok(())
110	}
111
112	fn finish(&mut self) -> Result<()> {
113		if self.finished {
114			return Ok(());
115		}
116		self.finished = true;
117		self.track.finish()
118	}
119
120	fn ensure_open(&self) -> Result<()> {
121		if self.finished {
122			return Err(moq_net::Error::Closed.into());
123		}
124		Ok(())
125	}
126}
127
128impl<T: Serialize> Inner<T> {
129	fn push(&mut self, value: &T) -> Result<()> {
130		self.ensure_open()?;
131		let Inner { track, encoder, .. } = self;
132
133		let frame = encoder.push(value)?;
134		track.write(&frame)?;
135		frame.commit();
136
137		Ok(())
138	}
139}
140
141/// The track half of [`Inner`]: where an encoded frame goes and how groups are rolled.
142struct Track {
143	inner: moq_net::track::Producer,
144
145	/// The group an op would be appended to, open after a header.
146	group: Option<moq_net::group::Producer>,
147}
148
149impl Track {
150	/// Write one encoded frame, rolling a group when it is a header.
151	fn write(&mut self, encoded: &Encoded) -> Result<()> {
152		match encoded.keyframe {
153			true => self.write_header(encoded.payload.clone()),
154			false => self.write_op(encoded.payload.clone()),
155		}
156	}
157
158	/// Close the open group and write the header as the first frame of a new one.
159	fn write_header(&mut self, payload: bytes::Bytes) -> Result<()> {
160		// The previous group is complete; no more frames will be appended to it.
161		if let Some(mut group) = self.group.take() {
162			group.finish()?;
163		}
164
165		let mut group = self.inner.append_group()?;
166		if let Err(err) = group.write_frame(moq_net::Timestamp::now(), payload) {
167			// `append_group` already published this group, and a rejected frame (too large) doesn't
168			// close the track. Dropping the handle does NOT close the group, so leaving it would strand
169			// any subscriber that advanced into it with nothing to read and no end.
170			let _ = group.finish();
171			return Err(err.into());
172		}
173
174		self.group = Some(group);
175		Ok(())
176	}
177
178	/// Append an op to the group the last header opened.
179	fn write_op(&mut self, payload: bytes::Bytes) -> Result<()> {
180		self.group
181			.as_mut()
182			.expect("the encoder only emits an op after a header opened a group")
183			.write_frame(moq_net::Timestamp::now(), payload)?;
184		Ok(())
185	}
186
187	fn finish(&mut self) -> Result<()> {
188		if let Some(mut group) = self.group.take() {
189			group.finish()?;
190		}
191		self.inner.finish()?;
192		Ok(())
193	}
194}