media_pp/core/bus.rs
1//! Out-of-band reporting for what cannot be returned to the caller.
2//!
3//! Inside a single thread a failing stage propagates with `?`. Once a buffer
4//! has crossed a [`Queue`](crate::queue::Queue) there is no longer a caller
5//! to return to, so the element posts a [`BusEvent`] here instead and keeps
6//! running. Whoever owns the pipeline observes those events through
7//! [`BusReceiver`].
8//!
9//! Every event is delivered as a [`BusMessage`] carrying the stable graph
10//! identity of the element that posted it, so a report can be attributed to
11//! one branch even when several elements share a name.
12
13use std::{sync::Arc, time::Duration};
14
15use crate::pp_log::{PpLog, pp_error, pp_info, pp_warn};
16use crossbeam_channel::{Receiver, Sender, unbounded};
17
18use crate::{element::ElementType, error::Error, graph::ElementId};
19
20/// Marked `#[non_exhaustive]`: this grows as elements gain conditions worth
21/// reporting, and a caller acting on `Eos`/`Error` should not stop compiling
22/// because some unrelated element learned to report a stall. Within this crate
23/// the attribute has no effect, so [`Bus::post`] and
24/// [`BusReceiver::log_events`] still fail to compile until they handle a new
25/// variant — the completeness check stays where it belongs.
26#[derive(Debug)]
27#[non_exhaustive]
28pub enum BusEvent {
29 /// An element completed ordered end-of-stream processing.
30 Eos {
31 /// Built-in kind of the element that completed.
32 element_type: ElementType,
33 /// Caller-selected instance name of the element that completed.
34 name: Arc<str>,
35 },
36
37 /// An element encountered a failure that could not be returned through
38 /// the synchronous call stack.
39 Error {
40 /// Built-in kind of the element reporting the failure.
41 element_type: ElementType,
42 /// Caller-selected instance name of the element reporting the failure.
43 name: Arc<str>,
44 /// Typed crate or component error reported by the element.
45 error: Error,
46 },
47 /// A `Queue` with `OverflowPolicy::DropNewest` dropped a buffer
48 /// because it was full.
49 Dropped {
50 /// Built-in kind of the queue that dropped the buffer.
51 element_type: ElementType,
52 /// Caller-selected instance name of the queue that dropped the buffer.
53 name: Arc<str>,
54 },
55 /// Posted by [`crate::control::drain_control`] once
56 /// [`crate::element::SourceElement::seek`] returns — `requested` is`1`
57 /// whatever [`crate::pipeline::Pipeline::seek`] was called with;
58 /// `landed` is where the source actually ended up, which the source
59 /// itself has to resolve (e.g. `FileDemuxer` can only reposition to a
60 /// keyframe at or before `requested`, never exactly on top of an
61 /// arbitrary timestamp — see its `seek` impl). Watch this instead of
62 /// assuming `requested` took effect verbatim.
63 Seeked {
64 /// Built-in kind of the source that performed the seek.
65 element_type: ElementType,
66 /// Caller-selected instance name of the source that performed the seek.
67 name: Arc<str>,
68 /// Absolute media position requested by [`crate::pipeline::Pipeline::seek`].
69 requested: Duration,
70 /// Absolute media position at which the source actually resumed.
71 landed: Duration,
72 },
73}
74
75/// Cross-thread event channel. Once a buffer crosses a `Queue` boundary,
76/// errors can no longer be propagated up the call stack with `?` — they're
77/// posted here instead so the owner of the `Pipeline` can observe them.
78#[derive(Clone)]
79pub struct Bus {
80 tx: Sender<BusMessage>,
81 element_id: Option<ElementId>,
82}
83
84/// The receiving half of a [`Bus`], held by whoever owns the pipeline.
85///
86/// Draining it blocks until every `Bus` sender has been dropped, which is how
87/// a caller waits for a pipeline to actually finish rather than polling for it.
88pub struct BusReceiver {
89 rx: Receiver<BusMessage>,
90}
91
92/// One bus event together with the stable graph identity of the element
93/// that posted it. Drivers and standalone elements that do not belong to a
94/// `PipelineGraph` use `None`.
95#[derive(Debug)]
96pub struct BusMessage {
97 /// Stable graph identity of the posting element, or `None` for a driver or
98 /// standalone element outside a pipeline graph.
99 pub element_id: Option<ElementId>,
100
101 /// Event payload posted by the element.
102 pub event: BusEvent,
103}
104
105impl Bus {
106 /// Creates an unbounded event channel with no graph element identity.
107 ///
108 /// Pipeline construction derives element-specific senders internally.
109 /// Standalone elements and drivers can use the returned sender directly;
110 /// their [`BusMessage::element_id`] remains `None`.
111 pub fn new() -> (Bus, BusReceiver) {
112 let (tx, rx) = unbounded();
113 (
114 Bus {
115 tx,
116 element_id: None,
117 },
118 BusReceiver { rx },
119 )
120 }
121
122 pub(crate) fn for_element(&self, element_id: ElementId) -> Bus {
123 Bus {
124 tx: self.tx.clone(),
125 element_id: Some(element_id),
126 }
127 }
128
129 /// Logs and enqueues an event without blocking on the receiver.
130 ///
131 /// If the receiving half has already been dropped, the event is discarded;
132 /// posting never turns pipeline teardown into another error.
133 ///
134 /// `pp_log` is the posting element's own [`crate::element::Element::pp_log`]
135 /// — used (via `crate::pp_log`'s `pp_log:` macro form) instead of `event`'s
136 /// own `name` so the element's full identity, pipeline id included, reaches
137 /// the log record rather than just the name carried in the event.
138 pub fn post(&self, pp_log: &PpLog, event: BusEvent) {
139 // Each `pp_*` macro checks `crate::log::enabled` before evaluating its
140 // arguments, so posting to a bus nobody is logging costs no `format!`
141 // — no hand-rolled check needed here.
142 match &event {
143 BusEvent::Eos { .. } => {
144 pp_info!(pp_log: pp_log, "event=eos phase=reported")
145 }
146 BusEvent::Error { error, .. } => pp_error!(pp_log: pp_log, "{error}"),
147 BusEvent::Dropped { .. } => {
148 pp_warn!(pp_log: pp_log, "dropped a buffer (queue full)")
149 }
150 BusEvent::Seeked {
151 requested, landed, ..
152 } => pp_info!(pp_log: pp_log, "seeked: requested {requested:.2?}, landed {landed:.2?}"),
153 }
154 // Nothing to do if the receiving end is gone (pipeline dropped).
155 let _ = self.tx.send(BusMessage {
156 element_id: self.element_id,
157 event,
158 });
159 }
160}
161
162impl BusReceiver {
163 /// Blocks until the next event arrives.
164 ///
165 /// Returns `None` only after every corresponding [`Bus`] sender has been
166 /// dropped and all already-queued events have been received. This discards
167 /// the posting element's stable graph ID; use [`Self::recv_message`] when
168 /// duplicate element names must be distinguished.
169 pub fn recv(&self) -> Option<BusEvent> {
170 self.recv_message().map(|message| message.event)
171 }
172
173 /// Receives one currently queued event without blocking.
174 ///
175 /// Returns `None` both when the channel is currently empty and when every
176 /// sender has disconnected. Use [`Self::try_recv_message`] to retain the
177 /// posting element's stable graph ID.
178 pub fn try_recv(&self) -> Option<BusEvent> {
179 self.try_recv_message().map(|message| message.event)
180 }
181
182 /// Iterates over events until every corresponding [`Bus`] sender drops.
183 ///
184 /// The iterator blocks while the channel is still connected but empty.
185 /// It discards stable graph IDs; use [`Self::iter_with_ids`] when duplicate
186 /// element names must be distinguished.
187 pub fn iter(&self) -> impl Iterator<Item = BusEvent> + '_ {
188 self.iter_with_ids().map(|message| message.event)
189 }
190
191 /// Blocks until the next event and its stable posting-element ID arrive.
192 ///
193 /// Returns `None` after the channel disconnects and its queued messages
194 /// have been drained.
195 pub fn recv_message(&self) -> Option<BusMessage> {
196 self.rx.recv().ok()
197 }
198
199 /// Receives one currently queued message without blocking.
200 ///
201 /// Returns `None` for both an empty connected channel and a disconnected
202 /// channel.
203 pub fn try_recv_message(&self) -> Option<BusMessage> {
204 self.rx.try_recv().ok()
205 }
206
207 /// Iterates over messages, preserving stable graph element IDs, until all
208 /// corresponding [`Bus`] senders have been dropped.
209 ///
210 /// The iterator blocks while the channel remains connected but empty.
211 pub fn iter_with_ids(&self) -> impl Iterator<Item = BusMessage> + '_ {
212 self.rx.iter()
213 }
214
215 /// Blocks and prints events in a common default format (`[name] eos`,
216 /// `[name] error: ...`, `[name] dropped a buffer (queue full)`,
217 /// `[name] seeked: requested ... landed ...`) until every corresponding
218 /// [`Bus`] sender has been dropped. This consumes both events already
219 /// queued and events posted while the call is waiting; use
220 /// [`BusReceiver::try_recv`] to drain only what is currently available.
221 ///
222 /// Convenience for examples and smoke tests; anything that needs to
223 /// act on specific events — e.g. deciding whether an `Error` warrants
224 /// a [`crate::pipeline::Pipeline::stop`] — should match on `iter()`
225 /// directly instead, where `error`'s concrete variant (see
226 /// [`crate::error::Error`]) is still available, not just its
227 /// `Display` text.
228 pub fn log_events(&self) {
229 for event in self.iter() {
230 match event {
231 BusEvent::Error { name, error, .. } => eprintln!("[{name}] error: {error}"),
232 BusEvent::Eos { name, .. } => println!("[{name}] eos"),
233 BusEvent::Dropped { name, .. } => {
234 eprintln!("[{name}] dropped a buffer (queue full)")
235 }
236 BusEvent::Seeked {
237 name,
238 requested,
239 landed,
240 ..
241 } => println!("[{name}] seeked: requested {requested:.2?}, landed {landed:.2?}"),
242 }
243 }
244 }
245}