Skip to main content

sim_lib_stream_host/
model.rs

1//! Host backend, device, direction, and open-plan model records.
2
3use sim_kernel::{
4    CapabilityName, Expr, Symbol,
5    effect::{effect_device_read_kind, effect_device_write_kind},
6};
7use sim_lib_stream_core::{BufferPolicy, StreamDirection, StreamMedia, StreamMetadata};
8
9use crate::capability::HostBackendCapability;
10
11/// Returns the capability name gating host stream device access.
12pub fn stream_host_capability() -> CapabilityName {
13    CapabilityName::new("stream.host")
14}
15
16/// Stable metadata describing a host backend.
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct HostBackendInfo {
19    id: Symbol,
20    transport: Symbol,
21    media: StreamMedia,
22    hardware_required: bool,
23    callbacks_bounded: bool,
24    capabilities: Vec<HostBackendCapability>,
25}
26
27/// Specification of an enumerated host device.
28#[derive(Clone, Debug, PartialEq, Eq)]
29pub struct HostDeviceSpec {
30    id: Symbol,
31    backend: Symbol,
32    media: StreamMedia,
33    direction: HostDirection,
34    clock: Symbol,
35    buffer: BufferPolicy,
36}
37
38/// Direction of a host stream relative to the device.
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub enum HostDirection {
41    /// Device delivers data into the runtime (a source).
42    Input,
43    /// Device receives data from the runtime (a sink).
44    Output,
45    /// Device both delivers and receives data.
46    Duplex,
47}
48
49/// Resolved plan describing how a device would be opened.
50///
51/// Names the backend, device, the effect kind the open performs, and the
52/// capabilities the open requires.
53#[derive(Clone, Debug, PartialEq, Eq)]
54pub struct HostOpenPlan {
55    backend: Symbol,
56    device: Symbol,
57    effect_kind: Symbol,
58    requires: Vec<CapabilityName>,
59}
60
61impl HostBackendInfo {
62    /// Builds backend metadata with bounded callbacks and no capabilities.
63    pub fn new(id: Symbol, transport: Symbol, media: StreamMedia, hardware_required: bool) -> Self {
64        Self {
65            id,
66            transport,
67            media,
68            hardware_required,
69            callbacks_bounded: true,
70            capabilities: Vec::new(),
71        }
72    }
73
74    /// Replaces the advertised capabilities.
75    pub fn with_capabilities(mut self, capabilities: Vec<HostBackendCapability>) -> Self {
76        self.capabilities = capabilities;
77        self
78    }
79
80    /// Returns the backend identifier symbol.
81    pub fn id(&self) -> &Symbol {
82        &self.id
83    }
84
85    /// Returns the transport symbol.
86    pub fn transport(&self) -> &Symbol {
87        &self.transport
88    }
89
90    /// Returns the backend media.
91    pub fn media(&self) -> StreamMedia {
92        self.media
93    }
94
95    /// Returns whether opening a stream requires hardware.
96    pub fn hardware_required(&self) -> bool {
97        self.hardware_required
98    }
99
100    /// Returns whether host callbacks are bounded (non-blocking, fixed queue).
101    pub fn callbacks_bounded(&self) -> bool {
102        self.callbacks_bounded
103    }
104
105    /// Returns the advertised capabilities.
106    pub fn capabilities(&self) -> &[HostBackendCapability] {
107        &self.capabilities
108    }
109
110    /// Builds the browse card expression for this backend.
111    pub fn card_expr(&self) -> Expr {
112        Expr::Map(vec![
113            (
114                Expr::Symbol(Symbol::new("subject")),
115                Expr::Symbol(self.id.clone()),
116            ),
117            (
118                Expr::Symbol(Symbol::new("kind")),
119                Expr::Symbol(Symbol::qualified("stream", "host-backend")),
120            ),
121            (
122                Expr::Symbol(Symbol::new("transport")),
123                Expr::Symbol(self.transport.clone()),
124            ),
125            (
126                Expr::Symbol(Symbol::new("media")),
127                Expr::Symbol(self.media.symbol()),
128            ),
129            (
130                Expr::Symbol(Symbol::new("hardware-required")),
131                Expr::Bool(self.hardware_required),
132            ),
133            (
134                Expr::Symbol(Symbol::new("callbacks-bounded")),
135                Expr::Bool(self.callbacks_bounded),
136            ),
137            (
138                Expr::Symbol(Symbol::new("capabilities")),
139                Expr::List(
140                    self.capabilities
141                        .iter()
142                        .map(|capability| Expr::Symbol(capability.symbol()))
143                        .collect(),
144                ),
145            ),
146        ])
147    }
148}
149
150impl HostDeviceSpec {
151    /// Builds a device spec from its identity, media, direction, clock, and
152    /// buffer policy.
153    pub fn new(
154        id: Symbol,
155        backend: Symbol,
156        media: StreamMedia,
157        direction: HostDirection,
158        clock: Symbol,
159        buffer: BufferPolicy,
160    ) -> Self {
161        Self {
162            id,
163            backend,
164            media,
165            direction,
166            clock,
167            buffer,
168        }
169    }
170
171    /// Returns the device identifier symbol.
172    pub fn id(&self) -> &Symbol {
173        &self.id
174    }
175
176    /// Returns the owning backend symbol.
177    pub fn backend(&self) -> &Symbol {
178        &self.backend
179    }
180
181    /// Returns the device media.
182    pub fn media(&self) -> StreamMedia {
183        self.media
184    }
185
186    /// Returns the device direction.
187    pub fn direction(&self) -> HostDirection {
188        self.direction
189    }
190
191    /// Returns the device clock-domain symbol.
192    pub fn clock(&self) -> &Symbol {
193        &self.clock
194    }
195
196    /// Returns the device buffer policy.
197    pub fn buffer(&self) -> &BufferPolicy {
198        &self.buffer
199    }
200
201    /// Builds the [`StreamMetadata`] for a stream opened on this device.
202    pub fn metadata(&self) -> StreamMetadata {
203        StreamMetadata::new(
204            self.id.clone(),
205            self.media,
206            self.direction.stream_direction(),
207            self.clock.clone(),
208            self.buffer.clone(),
209        )
210    }
211
212    /// Builds the [`HostOpenPlan`] for opening this device.
213    pub fn open_plan(&self) -> HostOpenPlan {
214        HostOpenPlan {
215            backend: self.backend.clone(),
216            device: self.id.clone(),
217            effect_kind: self.direction.effect_kind(),
218            requires: vec![stream_host_capability()],
219        }
220    }
221
222    /// Builds the browse card expression for this device.
223    pub fn card_expr(&self) -> Expr {
224        Expr::Map(vec![
225            (
226                Expr::Symbol(Symbol::new("subject")),
227                Expr::Symbol(self.id.clone()),
228            ),
229            (
230                Expr::Symbol(Symbol::new("kind")),
231                Expr::Symbol(Symbol::qualified("stream", "host-device")),
232            ),
233            (
234                Expr::Symbol(Symbol::new("backend")),
235                Expr::Symbol(self.backend.clone()),
236            ),
237            (
238                Expr::Symbol(Symbol::new("media")),
239                Expr::Symbol(self.media.symbol()),
240            ),
241            (
242                Expr::Symbol(Symbol::new("direction")),
243                Expr::Symbol(self.direction.symbol()),
244            ),
245            (
246                Expr::Symbol(Symbol::new("clock")),
247                Expr::Symbol(self.clock.clone()),
248            ),
249            (Expr::Symbol(Symbol::new("buffer")), self.buffer.to_expr()),
250        ])
251    }
252}
253
254impl HostDirection {
255    /// Maps this host direction to the core [`StreamDirection`].
256    pub fn stream_direction(self) -> StreamDirection {
257        match self {
258            Self::Input => StreamDirection::Source,
259            Self::Output => StreamDirection::Sink,
260            Self::Duplex => StreamDirection::Duplex,
261        }
262    }
263
264    /// Returns the stable qualified symbol naming this direction.
265    pub fn symbol(self) -> Symbol {
266        match self {
267            Self::Input => Symbol::qualified("stream/host-direction", "input"),
268            Self::Output => Symbol::qualified("stream/host-direction", "output"),
269            Self::Duplex => Symbol::qualified("stream/host-direction", "duplex"),
270        }
271    }
272
273    /// Returns the effect kind an open in this direction performs (device read
274    /// for input, device write for output or duplex).
275    pub fn effect_kind(self) -> Symbol {
276        match self {
277            Self::Input => effect_device_read_kind(),
278            Self::Output | Self::Duplex => effect_device_write_kind(),
279        }
280    }
281}
282
283impl HostOpenPlan {
284    /// Returns the backend that would perform the open.
285    pub fn backend(&self) -> &Symbol {
286        &self.backend
287    }
288
289    /// Returns the device that would be opened.
290    pub fn device(&self) -> &Symbol {
291        &self.device
292    }
293
294    /// Returns the effect kind the open performs.
295    pub fn effect_kind(&self) -> &Symbol {
296        &self.effect_kind
297    }
298
299    /// Returns the capabilities the open requires.
300    pub fn requires(&self) -> &[CapabilityName] {
301        &self.requires
302    }
303}