Skip to main content

audio_graph_bsd/
flush.rs

1//! Flushable sink contract + errors (engine-changes §4, Option A).
2//!
3//! A node whose output must be shipped off the RT thread (e.g. [`crate::RingSink`])
4//! implements [`Flushable`]. The graph engine calls [`Flushable::flush`] in the
5//! **between-cycle** (off-RT) window to push the stashed frame across the ring.
6//!
7//! `SinkNode` is the combined contract `AudioNode + Flushable` so the engine can
8//! track flushable sinks by type, without `Any` downcast.
9
10use crate::NodeId;
11
12/// Error from flushing a sink's stashed frame across its ring.
13#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
14pub enum FlushError {
15    /// The ring buffer was full (an xrun); the stashed frame was dropped.
16    #[error("ring full (xrun): {0} frame(s) dropped")]
17    RingFull(usize),
18    /// The addressed node is not a flushable sink.
19    #[error("node {0} is not a flushable sink")]
20    NotFlushable(NodeId),
21    /// The addressed node id does not exist.
22    #[error("node {0} not found")]
23    NodeNotFound(NodeId),
24}
25
26/// A node that buffers output off the RT thread and must be flushed between
27/// cycles to actually ship its data.
28///
29/// `flush` is **OFF-RT ONLY**: it typically clones the stashed frame and pushes
30/// it through an `rtrb::Producer` (allocating). Never call it from the RT
31/// [`crate::Graph::process_cycle`] path.
32pub trait Flushable {
33    /// Push the stashed frame across the ring. Returns an error if the ring is
34    /// full (treat as an xrun / dropped frame).
35    ///
36    /// # Errors
37    ///
38    /// Returns [`FlushError::RingFull`] if the ring has no free slot.
39    fn flush(&mut self) -> Result<(), FlushError>;
40}
41
42/// A sink node: both an [`audio_core_bsd::AudioNode`] (so it can be scheduled in
43/// the graph) and [`Flushable`] (so the engine can drain it between cycles).
44///
45/// Any type implementing both supertraits is automatically a `SinkNode`.
46pub trait SinkNode: audio_core_bsd::AudioNode + Flushable {}
47
48impl<T> SinkNode for T where T: audio_core_bsd::AudioNode + Flushable {}
49
50#[cfg(test)]
51mod tests {
52    use super::FlushError;
53
54    #[test]
55    fn flush_error_display() {
56        assert_eq!(
57            FlushError::RingFull(3).to_string(),
58            "ring full (xrun): 3 frame(s) dropped"
59        );
60        assert_eq!(
61            FlushError::NotFlushable(7).to_string(),
62            "node 7 is not a flushable sink"
63        );
64        assert_eq!(
65            FlushError::NodeNotFound(2).to_string(),
66            "node 2 not found"
67        );
68    }
69
70    #[test]
71    fn flush_error_clone_eq() {
72        let e = FlushError::RingFull(1);
73        assert_eq!(e.clone(), e);
74        assert_ne!(FlushError::RingFull(1), FlushError::NodeNotFound(1));
75    }
76}