sim-lib-stream-host 0.1.2

Host-device stream backend substrate for SIM streams.
Documentation
//! Registry for host stream backends.

use std::{collections::BTreeMap, sync::Arc};

use sim_kernel::{Cx, Error, Expr, Result, Symbol};

use crate::{
    HostBackend, HostDeviceInventory, HostOpenPlan, HostOpenStream, HostStreamConfigRequest,
    missing_capability_card_expr,
};

/// Deterministic registry of host stream backends.
#[derive(Default)]
pub struct HostBackendRegistry {
    backends: BTreeMap<Symbol, Arc<dyn HostBackend>>,
}

impl HostBackendRegistry {
    /// Creates an empty backend registry.
    pub fn new() -> Self {
        Self::default()
    }

    /// Registers a backend by its stable backend id.
    pub fn register<B>(&mut self, backend: B) -> Result<()>
    where
        B: HostBackend + 'static,
    {
        self.register_arc(Arc::new(backend))
    }

    /// Registers an already shared backend.
    pub fn register_arc(&mut self, backend: Arc<dyn HostBackend>) -> Result<()> {
        let id = backend.info().id().clone();
        if self.backends.contains_key(&id) {
            return Err(Error::Eval(format!(
                "stream host backend {id} is already registered"
            )));
        }
        self.backends.insert(id, backend);
        Ok(())
    }

    /// Returns a registered backend by id.
    pub fn backend(&self, id: &Symbol) -> Option<Arc<dyn HostBackend>> {
        self.backends.get(id).cloned()
    }

    /// Enumerates every registered backend.
    pub fn enumerate(&self) -> Result<Vec<HostDeviceInventory>> {
        self.backends
            .values()
            .map(|backend| backend.enumerate())
            .collect()
    }

    /// Resolves the authority and effect plan for a request without opening it.
    pub fn plan_open(&self, request: &HostStreamConfigRequest) -> Result<HostOpenPlan> {
        let backend = self.backend_or_error(request.backend())?;
        let inventory = backend.enumerate()?;
        let device = inventory
            .devices()
            .iter()
            .find(|device| device.id() == request.device())
            .ok_or_else(|| {
                Error::Eval(format!(
                    "stream host backend {} has no device {}",
                    request.backend(),
                    request.device()
                ))
            })?;
        if device.media() != request.media() {
            return Err(Error::TypeMismatch {
                expected: "request media matching host device",
                found: "request media for another host device",
            });
        }
        if device.direction() != request.direction() {
            return Err(Error::TypeMismatch {
                expected: "request direction matching host device",
                found: "request direction for another host device",
            });
        }
        Ok(device.open_plan())
    }

    /// Opens a stream using the backend named in the request after checking
    /// authority and recording the declared device effects.
    pub fn open_checked(
        &self,
        cx: &mut Cx,
        request: HostStreamConfigRequest,
    ) -> Result<HostOpenStream> {
        self.plan_open(&request)?.enforce(cx)?;
        self.open(request)
    }

    /// Opens a stream using the backend named in the request through the
    /// backend-level compatibility dispatch path.
    ///
    /// Runtime and public host opens should use [`Self::open_checked`] so the
    /// request's authority and device effects are handled first.
    pub fn open(&self, request: HostStreamConfigRequest) -> Result<HostOpenStream> {
        self.plan_open(&request)?;
        let backend = self.backend_or_error(request.backend())?;
        backend.open(request)
    }

    fn backend_or_error(&self, backend: &Symbol) -> Result<Arc<dyn HostBackend>> {
        self.backends
            .get(backend)
            .cloned()
            .ok_or_else(|| Error::Eval(format!("stream host backend {backend} is not registered")))
    }

    /// Emits backend, device, and port browse card expressions.
    pub fn card_exprs(&self) -> Result<Vec<Expr>> {
        let mut cards = Vec::new();
        for backend in self.backends.values() {
            cards.push(backend.info().card_expr());
            cards.extend(backend.enumerate()?.card_exprs());
        }
        Ok(cards)
    }

    /// Emits a browse card for a missing backend capability.
    pub fn missing_capability_card(
        &self,
        backend: &Symbol,
        capability: crate::HostBackendCapability,
    ) -> Expr {
        missing_capability_card_expr(backend, capability)
    }
}