Skip to main content

sim_lib_stream_host/
registry.rs

1//! Registry for host stream backends.
2
3use std::{collections::BTreeMap, sync::Arc};
4
5use sim_kernel::{Cx, Error, Expr, Result, Symbol};
6
7use crate::{
8    HostBackend, HostDeviceInventory, HostOpenPlan, HostOpenStream, HostStreamConfigRequest,
9    missing_capability_card_expr,
10};
11
12/// Deterministic registry of host stream backends.
13#[derive(Default)]
14pub struct HostBackendRegistry {
15    backends: BTreeMap<Symbol, Arc<dyn HostBackend>>,
16}
17
18impl HostBackendRegistry {
19    /// Creates an empty backend registry.
20    pub fn new() -> Self {
21        Self::default()
22    }
23
24    /// Registers a backend by its stable backend id.
25    pub fn register<B>(&mut self, backend: B) -> Result<()>
26    where
27        B: HostBackend + 'static,
28    {
29        self.register_arc(Arc::new(backend))
30    }
31
32    /// Registers an already shared backend.
33    pub fn register_arc(&mut self, backend: Arc<dyn HostBackend>) -> Result<()> {
34        let id = backend.info().id().clone();
35        if self.backends.contains_key(&id) {
36            return Err(Error::Eval(format!(
37                "stream host backend {id} is already registered"
38            )));
39        }
40        self.backends.insert(id, backend);
41        Ok(())
42    }
43
44    /// Returns a registered backend by id.
45    pub fn backend(&self, id: &Symbol) -> Option<Arc<dyn HostBackend>> {
46        self.backends.get(id).cloned()
47    }
48
49    /// Enumerates every registered backend.
50    pub fn enumerate(&self) -> Result<Vec<HostDeviceInventory>> {
51        self.backends
52            .values()
53            .map(|backend| backend.enumerate())
54            .collect()
55    }
56
57    /// Resolves the authority and effect plan for a request without opening it.
58    pub fn plan_open(&self, request: &HostStreamConfigRequest) -> Result<HostOpenPlan> {
59        let backend = self.backend_or_error(request.backend())?;
60        let inventory = backend.enumerate()?;
61        let device = inventory
62            .devices()
63            .iter()
64            .find(|device| device.id() == request.device())
65            .ok_or_else(|| {
66                Error::Eval(format!(
67                    "stream host backend {} has no device {}",
68                    request.backend(),
69                    request.device()
70                ))
71            })?;
72        if device.media() != request.media() {
73            return Err(Error::TypeMismatch {
74                expected: "request media matching host device",
75                found: "request media for another host device",
76            });
77        }
78        if device.direction() != request.direction() {
79            return Err(Error::TypeMismatch {
80                expected: "request direction matching host device",
81                found: "request direction for another host device",
82            });
83        }
84        Ok(device.open_plan())
85    }
86
87    /// Opens a stream using the backend named in the request after checking
88    /// authority and recording the declared device effects.
89    pub fn open_checked(
90        &self,
91        cx: &mut Cx,
92        request: HostStreamConfigRequest,
93    ) -> Result<HostOpenStream> {
94        self.plan_open(&request)?.enforce(cx)?;
95        self.open(request)
96    }
97
98    /// Opens a stream using the backend named in the request through the
99    /// backend-level compatibility dispatch path.
100    ///
101    /// Runtime and public host opens should use [`Self::open_checked`] so the
102    /// request's authority and device effects are handled first.
103    pub fn open(&self, request: HostStreamConfigRequest) -> Result<HostOpenStream> {
104        self.plan_open(&request)?;
105        let backend = self.backend_or_error(request.backend())?;
106        backend.open(request)
107    }
108
109    fn backend_or_error(&self, backend: &Symbol) -> Result<Arc<dyn HostBackend>> {
110        self.backends
111            .get(backend)
112            .cloned()
113            .ok_or_else(|| Error::Eval(format!("stream host backend {backend} is not registered")))
114    }
115
116    /// Emits backend, device, and port browse card expressions.
117    pub fn card_exprs(&self) -> Result<Vec<Expr>> {
118        let mut cards = Vec::new();
119        for backend in self.backends.values() {
120            cards.push(backend.info().card_expr());
121            cards.extend(backend.enumerate()?.card_exprs());
122        }
123        Ok(cards)
124    }
125
126    /// Emits a browse card for a missing backend capability.
127    pub fn missing_capability_card(
128        &self,
129        backend: &Symbol,
130        capability: crate::HostBackendCapability,
131    ) -> Expr {
132        missing_capability_card_expr(backend, capability)
133    }
134}