media_pp/core/bus.rs
1use std::{sync::Arc, time::Duration};
2
3use crate::pp_log::{PpLog, pp_error, pp_info, pp_warn};
4use crossbeam_channel::{Receiver, Sender, unbounded};
5
6use crate::{element::ElementType, error::Error, graph::ElementId};
7
8/// Marked `#[non_exhaustive]`: this grows as elements gain conditions worth
9/// reporting, and a caller acting on `Eos`/`Error` should not stop compiling
10/// because some unrelated element learned to report a stall. Within this crate
11/// the attribute has no effect, so [`Bus::post`] and
12/// [`BusReceiver::log_events`] still fail to compile until they handle a new
13/// variant — the completeness check stays where it belongs.
14#[derive(Debug)]
15#[non_exhaustive]
16pub enum BusEvent {
17 Eos {
18 element_type: ElementType,
19 name: Arc<str>,
20 },
21 Error {
22 element_type: ElementType,
23 name: Arc<str>,
24 error: Error,
25 },
26 /// A `Queue` with `OverflowPolicy::DropNewest` dropped a buffer
27 /// because it was full.
28 Dropped {
29 element_type: ElementType,
30 name: Arc<str>,
31 },
32 /// Posted by [`crate::control::drain_control`] once
33 /// [`crate::element::SourceElement::seek`] returns — `requested` is`1`
34 /// whatever [`crate::pipeline::Pipeline::seek`] was called with;
35 /// `landed` is where the source actually ended up, which the source
36 /// itself has to resolve (e.g. `FileDemuxer` can only reposition to a
37 /// keyframe at or before `requested`, never exactly on top of an
38 /// arbitrary timestamp — see its `seek` impl). Watch this instead of
39 /// assuming `requested` took effect verbatim.
40 Seeked {
41 element_type: ElementType,
42 name: Arc<str>,
43 requested: Duration,
44 landed: Duration,
45 },
46}
47
48/// Cross-thread event channel. Once a buffer crosses a `Queue` boundary,
49/// errors can no longer be propagated up the call stack with `?` — they're
50/// posted here instead so the owner of the `Pipeline` can observe them.
51#[derive(Clone)]
52pub struct Bus {
53 tx: Sender<BusMessage>,
54 element_id: Option<ElementId>,
55}
56
57pub struct BusReceiver {
58 rx: Receiver<BusMessage>,
59}
60
61/// One bus event together with the stable graph identity of the element
62/// that posted it. Drivers and standalone elements that do not belong to a
63/// `PipelineGraph` use `None`.
64#[derive(Debug)]
65pub struct BusMessage {
66 pub element_id: Option<ElementId>,
67 pub event: BusEvent,
68}
69
70impl Bus {
71 pub fn new() -> (Bus, BusReceiver) {
72 let (tx, rx) = unbounded();
73 (
74 Bus {
75 tx,
76 element_id: None,
77 },
78 BusReceiver { rx },
79 )
80 }
81
82 pub(crate) fn for_element(&self, element_id: ElementId) -> Bus {
83 Bus {
84 tx: self.tx.clone(),
85 element_id: Some(element_id),
86 }
87 }
88
89 /// `pp_log` is the posting element's own [`crate::element::Element::pp_log`]
90 /// — used (via `crate::pp_log`'s `pp_log:` macro form) instead of `event`'s
91 /// own `name` so the element's full identity, pipeline id included, reaches
92 /// the log record rather than just the name carried in the event.
93 pub fn post(&self, pp_log: &PpLog, event: BusEvent) {
94 // Each `pp_*` macro checks `crate::log::enabled` before evaluating its
95 // arguments, so posting to a bus nobody is logging costs no `format!`
96 // — no hand-rolled check needed here.
97 match &event {
98 BusEvent::Eos { .. } => {
99 pp_info!(pp_log: pp_log, "event=eos phase=reported")
100 }
101 BusEvent::Error { error, .. } => pp_error!(pp_log: pp_log, "{error}"),
102 BusEvent::Dropped { .. } => {
103 pp_warn!(pp_log: pp_log, "dropped a buffer (queue full)")
104 }
105 BusEvent::Seeked {
106 requested, landed, ..
107 } => pp_info!(pp_log: pp_log, "seeked: requested {requested:.2?}, landed {landed:.2?}"),
108 }
109 // Nothing to do if the receiving end is gone (pipeline dropped).
110 let _ = self.tx.send(BusMessage {
111 element_id: self.element_id,
112 event,
113 });
114 }
115}
116
117impl BusReceiver {
118 pub fn recv(&self) -> Option<BusEvent> {
119 self.recv_message().map(|message| message.event)
120 }
121
122 pub fn try_recv(&self) -> Option<BusEvent> {
123 self.try_recv_message().map(|message| message.event)
124 }
125
126 pub fn iter(&self) -> impl Iterator<Item = BusEvent> + '_ {
127 self.iter_with_ids().map(|message| message.event)
128 }
129
130 pub fn recv_message(&self) -> Option<BusMessage> {
131 self.rx.recv().ok()
132 }
133
134 pub fn try_recv_message(&self) -> Option<BusMessage> {
135 self.rx.try_recv().ok()
136 }
137
138 pub fn iter_with_ids(&self) -> impl Iterator<Item = BusMessage> + '_ {
139 self.rx.iter()
140 }
141
142 /// Blocks and prints events in a common default format (`[name] eos`,
143 /// `[name] error: ...`, `[name] dropped a buffer (queue full)`,
144 /// `[name] seeked: requested ... landed ...`) until every corresponding
145 /// [`Bus`] sender has been dropped. This consumes both events already
146 /// queued and events posted while the call is waiting; use
147 /// [`BusReceiver::try_recv`] to drain only what is currently available.
148 ///
149 /// Convenience for examples and smoke tests; anything that needs to
150 /// act on specific events — e.g. deciding whether an `Error` warrants
151 /// a [`crate::pipeline::Pipeline::stop`] — should match on `iter()`
152 /// directly instead, where `error`'s concrete variant (see
153 /// [`crate::error::Error`]) is still available, not just its
154 /// `Display` text.
155 pub fn log_events(&self) {
156 for event in self.iter() {
157 match event {
158 BusEvent::Error { name, error, .. } => eprintln!("[{name}] error: {error}"),
159 BusEvent::Eos { name, .. } => println!("[{name}] eos"),
160 BusEvent::Dropped { name, .. } => {
161 eprintln!("[{name}] dropped a buffer (queue full)")
162 }
163 BusEvent::Seeked {
164 name,
165 requested,
166 landed,
167 ..
168 } => println!("[{name}] seeked: requested {requested:.2?}, landed {landed:.2?}"),
169 }
170 }
171 }
172}