Skip to main content

moq_json/snapshot/
producer.rs

1//! Publishing a JSON value over a track: an [`Encoder`] plus the track it writes to.
2
3use std::marker::PhantomData;
4use std::ops::{Deref, DerefMut};
5use std::sync::{Arc, Mutex, MutexGuard};
6
7use serde::Serialize;
8use serde::de::DeserializeOwned;
9
10use super::{Encoded, Encoder, ProducerConfig};
11use crate::Result;
12
13/// Publishes a JSON value over a track, choosing snapshots and deltas automatically.
14///
15/// An [`Encoder`] that owns its track: it writes each encoded frame and rolls a group whenever the
16/// encoder emits a snapshot. When something else already owns the track, use the [`Encoder`]
17/// directly.
18///
19/// Cheaply clonable: clones share one underlying track and publishing state, like other MoQ
20/// producers.
21pub struct Producer<T> {
22	inner: Arc<Mutex<Inner<T>>>,
23	_marker: PhantomData<fn(T)>,
24}
25
26impl<T> Clone for Producer<T> {
27	fn clone(&self) -> Self {
28		Self {
29			inner: self.inner.clone(),
30			_marker: PhantomData,
31		}
32	}
33}
34
35impl<T> Producer<T> {
36	/// Create a subscriber for the underlying track.
37	pub fn consume(&self) -> moq_net::track::Subscriber {
38		self.inner.lock().unwrap().track.inner.subscribe(None)
39	}
40
41	/// Whether any consumer for the underlying track currently exists.
42	///
43	/// The demand signal for a producer serving on request: an unused track is
44	/// cached state nobody is watching, safe to drop and recreate on the next
45	/// request.
46	pub fn is_used(&self) -> bool {
47		self.inner
48			.lock()
49			.unwrap()
50			.track
51			.inner
52			.poll_unused(&moq_net::kio::Waiter::noop())
53			.is_pending()
54	}
55}
56
57impl<T: Serialize> Producer<T> {
58	/// Create a producer that publishes to the given track.
59	pub fn new(track: moq_net::track::Producer, config: ProducerConfig) -> Self {
60		Self {
61			inner: Arc::new(Mutex::new(Inner {
62				track: Track {
63					inner: track,
64					group: None,
65					deltas: config.delta_ratio != 0,
66				},
67				encoder: Encoder::new(config),
68			})),
69			_marker: PhantomData,
70		}
71	}
72
73	/// Publish a new value, emitting a snapshot or a delta automatically.
74	///
75	/// Does nothing if the value is unchanged from the previous publish.
76	pub fn update(&mut self, value: &T) -> Result<()> {
77		self.inner.lock().unwrap().update(value)
78	}
79
80	/// Lock the current value for in-place editing, publishing on drop.
81	///
82	/// The returned [`Guard`] derefs to the current value: everything published through this producer
83	/// so far, composed, or `T::default()` if nothing has been. Editing it through [`DerefMut`] marks
84	/// the guard dirty; when a dirty guard drops it publishes the result, a no-op if unchanged.
85	///
86	/// After a rejected frame the current value is what the producer last *tried* to publish, which
87	/// consumers never received. That is deliberate. The guard exists so independent owners can each
88	/// edit their own field without clobbering, and dropping a rejected owner's field would clobber it
89	/// for whoever edits next, which is the failure this API exists to prevent. The owner whose write
90	/// failed sees the error and can act on it; the next successful publish is a full snapshot
91	/// carrying the composed value, so consumers converge on it either way.
92	///
93	/// This is the counterpart to a callback: hold the guard, mutate, drop. The guard holds the
94	/// producer's lock for its lifetime, so independent owners are serialized: each one starts from
95	/// the latest value and their changes compose instead of clobbering. Don't hold a guard across
96	/// an `.await`, since that keeps the lock held while suspended.
97	///
98	/// Publishing on drop can fail (a closed track, a value that won't serialize) and only logs a
99	/// warning. Call [`Guard::commit`] instead to handle the error.
100	pub fn lock(&mut self) -> Guard<'_, T>
101	where
102		T: Default + DeserializeOwned,
103	{
104		let inner = self.inner.lock().unwrap();
105		let value = inner
106			.encoder
107			.value()
108			.and_then(|last| serde_json::from_value(last.clone()).ok())
109			.unwrap_or_default();
110
111		Guard {
112			inner,
113			value,
114			dirty: false,
115		}
116	}
117
118	/// Finish the track, closing any open group.
119	pub fn finish(&mut self) -> Result<()> {
120		self.inner.lock().unwrap().finish()
121	}
122}
123
124/// An RAII editing guard returned by [`Producer::lock`].
125///
126/// Holds the producer's lock for its lifetime and derefs to the current value. Mutating it through
127/// [`DerefMut`] marks it dirty, and dropping a dirty guard publishes the edited value.
128///
129/// Publishing on drop swallows any error into a warning, so prefer [`commit`](Self::commit) when the
130/// caller can act on a failure.
131pub struct Guard<'a, T: Serialize> {
132	inner: MutexGuard<'a, Inner<T>>,
133	value: T,
134	dirty: bool,
135}
136
137impl<T: Serialize> Guard<'_, T> {
138	/// Publish the edited value, returning any error.
139	///
140	/// Consumes the guard, so the subsequent drop publishes nothing. A no-op if the value was never
141	/// mutated.
142	pub fn commit(mut self) -> Result<()> {
143		self.publish()
144	}
145
146	/// Publish a dirty value once, clearing the dirty flag so it isn't published again.
147	fn publish(&mut self) -> Result<()> {
148		if !self.dirty {
149			return Ok(());
150		}
151		self.dirty = false;
152
153		// We already hold the lock, so publish through the held guard rather than re-locking.
154		self.inner.update(&self.value)
155	}
156}
157
158impl<T: Serialize> Deref for Guard<'_, T> {
159	type Target = T;
160
161	fn deref(&self) -> &T {
162		&self.value
163	}
164}
165
166impl<T: Serialize> DerefMut for Guard<'_, T> {
167	fn deref_mut(&mut self) -> &mut T {
168		self.dirty = true;
169		&mut self.value
170	}
171}
172
173impl<T: Serialize> Drop for Guard<'_, T> {
174	fn drop(&mut self) {
175		if let Err(err) = self.publish() {
176			tracing::warn!(%err, "failed to publish JSON value on guard drop");
177		}
178	}
179}
180
181/// Shared publishing state behind [`Producer`]'s `Arc<Mutex>`.
182///
183/// The track and the encoder are separate fields so a [`Pending`](super::Pending) frame (which
184/// borrows the encoder) and the write that consumes it (which borrows the track) don't contend for
185/// one `&mut self`.
186struct Inner<T> {
187	track: Track,
188	encoder: Encoder<T>,
189}
190
191impl<T: Serialize> Inner<T> {
192	fn update(&mut self, value: &T) -> Result<()> {
193		// Split the borrow so `frame` can hold the encoder while `track` is written through.
194		let Inner { track, encoder } = self;
195
196		let Some(frame) = encoder.update(value)? else {
197			return Ok(());
198		};
199
200		// A failed write drops `frame` uncommitted, which resets the encoder so the next update
201		// resynchronizes with a fresh snapshot. Most failures kill the track outright, but a rejected
202		// frame (too large) doesn't, and a delta against a snapshot no consumer ever saw is unreadable.
203		track.write(&frame)?;
204		frame.commit();
205
206		Ok(())
207	}
208
209	fn finish(&mut self) -> Result<()> {
210		// The open group goes with the track, so the encoder must not keep emitting deltas into it.
211		// Any further update fails on the closed track, but it has to fail as an error rather than by
212		// writing a delta with no group to hold it.
213		self.encoder.reset();
214		self.track.finish()
215	}
216}
217
218/// The track half of [`Inner`]: where an encoded frame goes and how groups are rolled.
219struct Track {
220	inner: moq_net::track::Producer,
221
222	/// The group a delta would be appended to, open only while deltas are enabled.
223	group: Option<moq_net::group::Producer>,
224
225	/// Whether the encoder can emit deltas at all. With them off every frame is a snapshot, so a
226	/// group is closed the moment it's written and never held open.
227	deltas: bool,
228}
229
230impl Track {
231	/// Write one encoded frame, rolling a group when it's a snapshot.
232	fn write(&mut self, encoded: &Encoded) -> Result<()> {
233		match encoded.keyframe {
234			true => self.write_snapshot(encoded.payload.clone()),
235			false => self.write_delta(encoded.payload.clone()),
236		}
237	}
238
239	/// Close the open group and write a snapshot as the first frame of a new one.
240	fn write_snapshot(&mut self, payload: bytes::Bytes) -> Result<()> {
241		// The previous group is complete; no more frames will be appended to it.
242		if let Some(mut group) = self.group.take() {
243			group.finish()?;
244		}
245
246		let mut group = self.inner.append_group()?;
247		if let Err(err) = group.write_frame(moq_net::Timestamp::now(), payload) {
248			// `append_group` already published this group, and a rejected frame (too large) doesn't
249			// close the track. Dropping the handle does NOT close the group, so leaving it would strand
250			// any subscriber that advanced into it with nothing to read and no end.
251			let _ = group.finish();
252			return Err(err.into());
253		}
254
255		match self.deltas {
256			// Keep the group open so future deltas can be appended to it.
257			true => self.group = Some(group),
258			// One frame per group, identical to a plain JSON track.
259			false => group.finish()?,
260		}
261
262		Ok(())
263	}
264
265	/// Append a delta to the group the last snapshot opened.
266	fn write_delta(&mut self, payload: bytes::Bytes) -> Result<()> {
267		self.group
268			.as_mut()
269			.expect("the encoder only emits a delta after a snapshot opened a group")
270			.write_frame(moq_net::Timestamp::now(), payload)?;
271		Ok(())
272	}
273
274	fn finish(&mut self) -> Result<()> {
275		if let Some(mut group) = self.group.take() {
276			group.finish()?;
277		}
278		self.inner.finish()?;
279		Ok(())
280	}
281}