Skip to main content

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}