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/// Registry of named backend constructors with caching.
36#[derive(Clone)]
37pub struct BackendFactory {
38    ctors: HashMap<&'static str, BackendCtor>,
39    cache: HashMap<String, BackendParts>,
40}
41
42impl BackendFactory {
43    /// Create an empty backend factory.
44    pub fn new() -> Self {
45        Self {
46            ctors: HashMap::new(),
47            cache: HashMap::new(),
48        }
49    }
50
51    /// Register a named backend constructor.
52    pub fn register(&mut self, name: &'static str, ctor: BackendCtor) {
53        self.ctors.insert(name, ctor);
54    }
55
56    /// Create or retrieve a cached backend by name.
57    fn get_or_create(
58        &mut self,
59        name: &str,
60        params: &HashMap<String, ParamValue>,
61    ) -> Result<BackendParts, String> {
62        if let Some(cached) = self.cache.get(name) {
63            return Ok(cached.clone());
64        }
65        let ctor = self
66            .ctors
67            .get(name)
68            .ok_or_else(|| format!("unknown backend: {name}"))?;
69        let result = ctor(params)?;
70        self.cache.insert(name.to_string(), result.clone());
71        Ok(result)
72    }
73
74    /// Create a backend returning whatever capabilities it provides.
75    /// Use this when the graph determines what's needed (launch path).
76    pub fn create_any(
77        &mut self,
78        name: &str,
79        params: &HashMap<String, ParamValue>,
80    ) -> Result<BackendParts, String> {
81        self.get_or_create(name, params)
82    }
83
84    /// Create an output-only backend.
85    pub fn create_output(
86        &mut self,
87        name: &str,
88        params: &HashMap<String, ParamValue>,
89    ) -> Result<OutputBundle, String> {
90        let (driver, _capture, playback) = self.get_or_create(name, params)?;
91        Ok(OutputBundle {
92            driver,
93            playback: playback
94                .ok_or_else(|| format!("backend '{name}' does not support output"))?,
95        })
96    }
97
98    /// Create an input-only backend.
99    pub fn create_input(
100        &mut self,
101        name: &str,
102        params: &HashMap<String, ParamValue>,
103    ) -> Result<InputBundle, String> {
104        let (driver, capture, _playback) = self.get_or_create(name, params)?;
105        Ok(InputBundle {
106            driver,
107            capture: capture.ok_or_else(|| format!("backend '{name}' does not support input"))?,
108        })
109    }
110
111    /// Returns `true` if a backend with the given name is registered.
112    pub fn contains(&self, name: &str) -> bool {
113        self.ctors.contains_key(name)
114    }
115}
116
117impl Default for BackendFactory {
118    fn default() -> Self {
119        Self::new()
120    }
121}