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#[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}