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}