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