Skip to main content

Producer

Struct Producer 

Source
pub struct Producer<T> { /* private fields */ }
Expand description

Publishes a JSON value over a track, choosing snapshots and deltas automatically.

Cheaply clonable: clones share one underlying track and publishing state, like other MoQ producers.

Implementations§

Source§

impl<T> Producer<T>

Source

pub fn consume(&self) -> TrackConsumer

Create a subscriber for the underlying track.

Source§

impl<T: Serialize> Producer<T>

Source

pub fn new(track: TrackProducer, config: ProducerConfig) -> Self

Create a producer that publishes to the given track.

Examples found in repository?
examples/telemetry.rs (line 81)
78fn wire_bytes(config: ProducerConfig, ticks: u64) -> usize {
79	let track = moq_net::Track::new("telemetry").produce();
80	let consumer = track.consume();
81	let mut producer = Producer::<Value>::new(track, config);
82
83	for tick in 0..ticks {
84		producer.update(&telemetry(tick)).unwrap();
85	}
86	producer.finish().unwrap();
87
88	// Drain the raw stored frames (compressed if the producer compressed them) and sum their sizes.
89	let waiter = kio::Waiter::noop();
90	let mut total = 0;
91	let mut track = consumer;
92	while let Poll::Ready(Ok(Some(mut group))) = track.poll_next_group(&waiter) {
93		while let Poll::Ready(Ok(Some(frame))) = group.poll_read_frame(&waiter) {
94			total += frame.len();
95		}
96	}
97	total
98}
99
100/// Drive a producer and a live consumer in lockstep, asserting that EVERY tick reconstructs to the
101/// exact input value after decompression and delta application (not just the final one).
102fn verify(producer_config: ProducerConfig, ticks: u64) {
103	let track = moq_net::Track::new("telemetry").produce();
104	let consumer = track.consume();
105	let mut producer = Producer::<Value>::new(track, producer_config.clone());
106
107	let mut consumer_config = ConsumerConfig::default();
108	consumer_config.compression = producer_config.compression;
109	let mut consumer = moq_json::Consumer::<Value>::new(consumer, consumer_config);
110	let waiter = kio::Waiter::noop();
111
112	for tick in 0..ticks {
113		let expected = telemetry(tick);
114		producer.update(&expected).unwrap();
115		// The producer emits exactly one frame per update, so the live consumer yields exactly one
116		// reconstructed value: it must match the input byte-for-byte after decompression + patching.
117		match consumer.poll_next(&waiter) {
118			Poll::Ready(Ok(Some(value))) => assert_eq!(value, expected, "tick {tick} reconstruction mismatch"),
119			other => panic!("tick {tick}: expected a value, got {other:?}"),
120		}
121	}
122	producer.finish().unwrap();
123
124	// Drain: nothing left and the stream ends cleanly.
125	assert!(
126		matches!(consumer.poll_next(&waiter), Poll::Ready(Ok(None))),
127		"stream did not end cleanly"
128	);
129}
130
131/// A consumer that joins only after the whole stream exists must still rebuild the latest value from
132/// the newest group's snapshot + deltas. For the compressed path this exercises the lazy decoder
133/// replaying the group's already-stored slices to warm its window before decoding the final frame.
134///
135/// Returns how many values the late joiner surfaced to the application: with backlog collapsing this
136/// is far below `ticks`, since stale intermediate reconstructions are applied internally but skipped.
137fn verify_late_joiner(producer_config: ProducerConfig, ticks: u64) -> usize {
138	let track = moq_net::Track::new("telemetry").produce();
139	let consumer = track.consume();
140	let mut producer = Producer::<Value>::new(track, producer_config.clone());
141	for tick in 0..ticks {
142		producer.update(&telemetry(tick)).unwrap();
143	}
144	producer.finish().unwrap();
145
146	let mut consumer_config = ConsumerConfig::default();
147	consumer_config.compression = producer_config.compression;
148	let mut consumer = moq_json::Consumer::<Value>::new(consumer, consumer_config);
149	let waiter = kio::Waiter::noop();
150	let mut last = None;
151	let mut yielded = 0;
152	while let Poll::Ready(Ok(Some(value))) = consumer.poll_next(&waiter) {
153		last = Some(value);
154		yielded += 1;
155	}
156	assert_eq!(
157		last.as_ref(),
158		Some(&telemetry(ticks - 1)),
159		"late joiner reconstruction mismatch"
160	);
161	yielded
162}
Source

pub fn update(&mut self, value: &T) -> Result<()>

Publish a new value, emitting a snapshot or a delta automatically.

Does nothing if the value is unchanged from the previous publish.

Examples found in repository?
examples/telemetry.rs (line 84)
78fn wire_bytes(config: ProducerConfig, ticks: u64) -> usize {
79	let track = moq_net::Track::new("telemetry").produce();
80	let consumer = track.consume();
81	let mut producer = Producer::<Value>::new(track, config);
82
83	for tick in 0..ticks {
84		producer.update(&telemetry(tick)).unwrap();
85	}
86	producer.finish().unwrap();
87
88	// Drain the raw stored frames (compressed if the producer compressed them) and sum their sizes.
89	let waiter = kio::Waiter::noop();
90	let mut total = 0;
91	let mut track = consumer;
92	while let Poll::Ready(Ok(Some(mut group))) = track.poll_next_group(&waiter) {
93		while let Poll::Ready(Ok(Some(frame))) = group.poll_read_frame(&waiter) {
94			total += frame.len();
95		}
96	}
97	total
98}
99
100/// Drive a producer and a live consumer in lockstep, asserting that EVERY tick reconstructs to the
101/// exact input value after decompression and delta application (not just the final one).
102fn verify(producer_config: ProducerConfig, ticks: u64) {
103	let track = moq_net::Track::new("telemetry").produce();
104	let consumer = track.consume();
105	let mut producer = Producer::<Value>::new(track, producer_config.clone());
106
107	let mut consumer_config = ConsumerConfig::default();
108	consumer_config.compression = producer_config.compression;
109	let mut consumer = moq_json::Consumer::<Value>::new(consumer, consumer_config);
110	let waiter = kio::Waiter::noop();
111
112	for tick in 0..ticks {
113		let expected = telemetry(tick);
114		producer.update(&expected).unwrap();
115		// The producer emits exactly one frame per update, so the live consumer yields exactly one
116		// reconstructed value: it must match the input byte-for-byte after decompression + patching.
117		match consumer.poll_next(&waiter) {
118			Poll::Ready(Ok(Some(value))) => assert_eq!(value, expected, "tick {tick} reconstruction mismatch"),
119			other => panic!("tick {tick}: expected a value, got {other:?}"),
120		}
121	}
122	producer.finish().unwrap();
123
124	// Drain: nothing left and the stream ends cleanly.
125	assert!(
126		matches!(consumer.poll_next(&waiter), Poll::Ready(Ok(None))),
127		"stream did not end cleanly"
128	);
129}
130
131/// A consumer that joins only after the whole stream exists must still rebuild the latest value from
132/// the newest group's snapshot + deltas. For the compressed path this exercises the lazy decoder
133/// replaying the group's already-stored slices to warm its window before decoding the final frame.
134///
135/// Returns how many values the late joiner surfaced to the application: with backlog collapsing this
136/// is far below `ticks`, since stale intermediate reconstructions are applied internally but skipped.
137fn verify_late_joiner(producer_config: ProducerConfig, ticks: u64) -> usize {
138	let track = moq_net::Track::new("telemetry").produce();
139	let consumer = track.consume();
140	let mut producer = Producer::<Value>::new(track, producer_config.clone());
141	for tick in 0..ticks {
142		producer.update(&telemetry(tick)).unwrap();
143	}
144	producer.finish().unwrap();
145
146	let mut consumer_config = ConsumerConfig::default();
147	consumer_config.compression = producer_config.compression;
148	let mut consumer = moq_json::Consumer::<Value>::new(consumer, consumer_config);
149	let waiter = kio::Waiter::noop();
150	let mut last = None;
151	let mut yielded = 0;
152	while let Poll::Ready(Ok(Some(value))) = consumer.poll_next(&waiter) {
153		last = Some(value);
154		yielded += 1;
155	}
156	assert_eq!(
157		last.as_ref(),
158		Some(&telemetry(ticks - 1)),
159		"late joiner reconstruction mismatch"
160	);
161	yielded
162}
Source

pub fn lock(&mut self) -> Guard<'_, T>

Lock the current value for in-place editing, publishing on drop.

The returned Guard derefs to the last-published value (or T::default() if nothing has been published yet). Editing it through DerefMut marks the guard dirty; when a dirty guard drops it publishes the result, a no-op if unchanged.

This is the counterpart to a callback: hold the guard, mutate, drop. The guard holds the producer’s lock for its lifetime, so independent owners are serialized: each one starts from the latest value and their changes compose instead of clobbering. Don’t hold a guard across an .await, since that keeps the lock held while suspended.

Source

pub fn finish(&mut self) -> Result<()>

Finish the track, closing any open group.

Examples found in repository?
examples/telemetry.rs (line 86)
78fn wire_bytes(config: ProducerConfig, ticks: u64) -> usize {
79	let track = moq_net::Track::new("telemetry").produce();
80	let consumer = track.consume();
81	let mut producer = Producer::<Value>::new(track, config);
82
83	for tick in 0..ticks {
84		producer.update(&telemetry(tick)).unwrap();
85	}
86	producer.finish().unwrap();
87
88	// Drain the raw stored frames (compressed if the producer compressed them) and sum their sizes.
89	let waiter = kio::Waiter::noop();
90	let mut total = 0;
91	let mut track = consumer;
92	while let Poll::Ready(Ok(Some(mut group))) = track.poll_next_group(&waiter) {
93		while let Poll::Ready(Ok(Some(frame))) = group.poll_read_frame(&waiter) {
94			total += frame.len();
95		}
96	}
97	total
98}
99
100/// Drive a producer and a live consumer in lockstep, asserting that EVERY tick reconstructs to the
101/// exact input value after decompression and delta application (not just the final one).
102fn verify(producer_config: ProducerConfig, ticks: u64) {
103	let track = moq_net::Track::new("telemetry").produce();
104	let consumer = track.consume();
105	let mut producer = Producer::<Value>::new(track, producer_config.clone());
106
107	let mut consumer_config = ConsumerConfig::default();
108	consumer_config.compression = producer_config.compression;
109	let mut consumer = moq_json::Consumer::<Value>::new(consumer, consumer_config);
110	let waiter = kio::Waiter::noop();
111
112	for tick in 0..ticks {
113		let expected = telemetry(tick);
114		producer.update(&expected).unwrap();
115		// The producer emits exactly one frame per update, so the live consumer yields exactly one
116		// reconstructed value: it must match the input byte-for-byte after decompression + patching.
117		match consumer.poll_next(&waiter) {
118			Poll::Ready(Ok(Some(value))) => assert_eq!(value, expected, "tick {tick} reconstruction mismatch"),
119			other => panic!("tick {tick}: expected a value, got {other:?}"),
120		}
121	}
122	producer.finish().unwrap();
123
124	// Drain: nothing left and the stream ends cleanly.
125	assert!(
126		matches!(consumer.poll_next(&waiter), Poll::Ready(Ok(None))),
127		"stream did not end cleanly"
128	);
129}
130
131/// A consumer that joins only after the whole stream exists must still rebuild the latest value from
132/// the newest group's snapshot + deltas. For the compressed path this exercises the lazy decoder
133/// replaying the group's already-stored slices to warm its window before decoding the final frame.
134///
135/// Returns how many values the late joiner surfaced to the application: with backlog collapsing this
136/// is far below `ticks`, since stale intermediate reconstructions are applied internally but skipped.
137fn verify_late_joiner(producer_config: ProducerConfig, ticks: u64) -> usize {
138	let track = moq_net::Track::new("telemetry").produce();
139	let consumer = track.consume();
140	let mut producer = Producer::<Value>::new(track, producer_config.clone());
141	for tick in 0..ticks {
142		producer.update(&telemetry(tick)).unwrap();
143	}
144	producer.finish().unwrap();
145
146	let mut consumer_config = ConsumerConfig::default();
147	consumer_config.compression = producer_config.compression;
148	let mut consumer = moq_json::Consumer::<Value>::new(consumer, consumer_config);
149	let waiter = kio::Waiter::noop();
150	let mut last = None;
151	let mut yielded = 0;
152	while let Poll::Ready(Ok(Some(value))) = consumer.poll_next(&waiter) {
153		last = Some(value);
154		yielded += 1;
155	}
156	assert_eq!(
157		last.as_ref(),
158		Some(&telemetry(ticks - 1)),
159		"late joiner reconstruction mismatch"
160	);
161	yielded
162}

Trait Implementations§

Source§

impl<T> Clone for Producer<T>

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

§

impl<T> Freeze for Producer<T>

§

impl<T> RefUnwindSafe for Producer<T>

§

impl<T> Send for Producer<T>

§

impl<T> Sync for Producer<T>

§

impl<T> Unpin for Producer<T>

§

impl<T> UnsafeUnpin for Producer<T>

§

impl<T> UnwindSafe for Producer<T>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> MaybeSend for T
where T: Send,

Source§

impl<T> MaybeSync for T
where T: Sync,

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more