Skip to main content

rill_graph/
backend_factory.rs

1//! # BackendFactory — constructor registry for I/O backends
2
3use std::collections::HashMap;
4use std::sync::Arc;
5
6use rill_core::io::{IoCapture, IoDriver, IoPlayback};
7use rill_core::traits::ParamValue;
8
9/// Raw backend construction result: `(driver, capture?, playback?)`.
10pub type BackendParts = (
11    Arc<dyn IoDriver>,
12    Option<Arc<dyn IoCapture>>,
13    Option<Arc<dyn IoPlayback>>,
14);
15
16/// Constructor signature. Returns `(driver, capture?, playback?)`.
17pub type BackendCtor = fn(params: &HashMap<String, ParamValue>) -> Result<BackendParts, String>;
18
19/// Output-only backend bundle.
20pub struct OutputBundle {
21    /// The clock driver.
22    pub driver: Arc<dyn IoDriver>,
23    /// The playback (output) backend.
24    pub playback: Arc<dyn IoPlayback>,
25}
26
27/// Input-only backend bundle.
28pub struct InputBundle {
29    /// The clock driver.
30    pub driver: Arc<dyn IoDriver>,
31    /// The capture (input) backend.
32    pub capture: Arc<dyn IoCapture>,
33}
34
35/// Full-duplex backend bundle.
36pub struct DuplexBundle {
37    /// The clock driver.
38    pub driver: Arc<dyn IoDriver>,
39    /// The capture (input) backend.
40    pub capture: Arc<dyn IoCapture>,
41    /// The playback (output) backend.
42    pub playback: Arc<dyn IoPlayback>,
43}
44
45/// Registry of named backend constructors with caching.
46#[derive(Clone)]
47pub struct BackendFactory {
48    ctors: HashMap<&'static str, BackendCtor>,
49    cache: HashMap<String, BackendParts>,
50}
51
52impl BackendFactory {
53    /// Create an empty backend factory.
54    pub fn new() -> Self {
55        Self {
56            ctors: HashMap::new(),
57            cache: HashMap::new(),
58        }
59    }
60
61    /// Register a named backend constructor.
62    pub fn register(&mut self, name: &'static str, ctor: BackendCtor) {
63        self.ctors.insert(name, ctor);
64    }
65
66    /// Create or retrieve a cached backend by name.
67    fn get_or_create(
68        &mut self,
69        name: &str,
70        params: &HashMap<String, ParamValue>,
71    ) -> Result<BackendParts, String> {
72        if let Some(cached) = self.cache.get(name) {
73            return Ok(cached.clone());
74        }
75        let ctor = self
76            .ctors
77            .get(name)
78            .ok_or_else(|| format!("unknown backend: {name}"))?;
79        let result = ctor(params)?;
80        self.cache.insert(name.to_string(), result.clone());
81        Ok(result)
82    }
83
84    /// Create a backend returning whatever capabilities it provides.
85    /// Use this when the graph determines what's needed (launch path).
86    pub fn create_any(
87        &mut self,
88        name: &str,
89        params: &HashMap<String, ParamValue>,
90    ) -> Result<BackendParts, String> {
91        self.get_or_create(name, params)
92    }
93
94    /// Create an output-only backend.
95    pub fn create_output(
96        &mut self,
97        name: &str,
98        params: &HashMap<String, ParamValue>,
99    ) -> Result<OutputBundle, String> {
100        let (driver, _capture, playback) = self.get_or_create(name, params)?;
101        Ok(OutputBundle {
102            driver,
103            playback: playback
104                .ok_or_else(|| format!("backend '{name}' does not support output"))?,
105        })
106    }
107
108    /// Create an input-only backend.
109    pub fn create_input(
110        &mut self,
111        name: &str,
112        params: &HashMap<String, ParamValue>,
113    ) -> Result<InputBundle, String> {
114        let (driver, capture, _playback) = self.get_or_create(name, params)?;
115        Ok(InputBundle {
116            driver,
117            capture: capture.ok_or_else(|| format!("backend '{name}' does not support input"))?,
118        })
119    }
120
121    /// Create a full-duplex backend.
122    pub fn create_duplex(
123        &mut self,
124        name: &str,
125        params: &HashMap<String, ParamValue>,
126    ) -> Result<DuplexBundle, String> {
127        let (driver, capture, playback) = self.get_or_create(name, params)?;
128        Ok(DuplexBundle {
129            driver,
130            capture: capture.ok_or_else(|| format!("backend '{name}' does not support input"))?,
131            playback: playback
132                .ok_or_else(|| format!("backend '{name}' does not support output"))?,
133        })
134    }
135
136    /// Returns `true` if a backend with the given name is registered.
137    pub fn contains(&self, name: &str) -> bool {
138        self.ctors.contains_key(name)
139    }
140}
141
142impl Default for BackendFactory {
143    fn default() -> Self {
144        Self::new()
145    }
146}