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}