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, Cx, Expr, Ref, Result, Symbol,
5    effect::{Effect, effect_abort_op_key, effect_resume_op_key, resolve_effect},
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/// Returns the stream-host effect kind for device reads.
17pub fn stream_host_device_read_effect_kind() -> Symbol {
18    Symbol::qualified("effect", "device-read")
19}
20
21/// Returns the stream-host effect kind for device writes.
22pub fn stream_host_device_write_effect_kind() -> Symbol {
23    Symbol::qualified("effect", "device-write")
24}
25
26/// Stable metadata describing a host backend.
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct HostBackendInfo {
29    id: Symbol,
30    transport: Symbol,
31    media: StreamMedia,
32    hardware_required: bool,
33    callbacks_bounded: bool,
34    capabilities: Vec<HostBackendCapability>,
35}
36
37/// Specification of an enumerated host device.
38#[derive(Clone, Debug, PartialEq, Eq)]
39pub struct HostDeviceSpec {
40    id: Symbol,
41    backend: Symbol,
42    media: StreamMedia,
43    direction: HostDirection,
44    clock: Symbol,
45    buffer: BufferPolicy,
46}
47
48/// Direction of a host stream relative to the device.
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub enum HostDirection {
51    /// Device delivers data into the runtime (a source).
52    Input,
53    /// Device receives data from the runtime (a sink).
54    Output,
55    /// Device both delivers and receives data.
56    Duplex,
57}
58
59/// Resolved plan describing how a device would be opened.
60///
61/// Names the backend, device, the effect kinds the open performs, and the
62/// capabilities the open requires.
63#[derive(Clone, Debug, PartialEq, Eq)]
64pub struct HostOpenPlan {
65    backend: Symbol,
66    device: Symbol,
67    effect_kinds: Vec<Symbol>,
68    requires: Vec<CapabilityName>,
69}
70
71impl HostBackendInfo {
72    /// Builds backend metadata with bounded callbacks and no capabilities.
73    pub fn new(id: Symbol, transport: Symbol, media: StreamMedia, hardware_required: bool) -> Self {
74        Self {
75            id,
76            transport,
77            media,
78            hardware_required,
79            callbacks_bounded: true,
80            capabilities: Vec::new(),
81        }
82    }
83
84    /// Replaces the advertised capabilities.
85    pub fn with_capabilities(mut self, capabilities: Vec<HostBackendCapability>) -> Self {
86        self.capabilities = capabilities;
87        self
88    }
89
90    /// Returns the backend identifier symbol.
91    pub fn id(&self) -> &Symbol {
92        &self.id
93    }
94
95    /// Returns the transport symbol.
96    pub fn transport(&self) -> &Symbol {
97        &self.transport
98    }
99
100    /// Returns the backend media.
101    pub fn media(&self) -> StreamMedia {
102        self.media
103    }
104
105    /// Returns whether opening a stream requires hardware.
106    pub fn hardware_required(&self) -> bool {
107        self.hardware_required
108    }
109
110    /// Returns whether host callbacks are bounded (non-blocking, fixed queue).
111    pub fn callbacks_bounded(&self) -> bool {
112        self.callbacks_bounded
113    }
114
115    /// Returns the advertised capabilities.
116    pub fn capabilities(&self) -> &[HostBackendCapability] {
117        &self.capabilities
118    }
119
120    /// Builds the browse card expression for this backend.
121    pub fn card_expr(&self) -> Expr {
122        Expr::Map(vec![
123            (
124                Expr::Symbol(Symbol::new("subject")),
125                Expr::Symbol(self.id.clone()),
126            ),
127            (
128                Expr::Symbol(Symbol::new("kind")),
129                Expr::Symbol(Symbol::qualified("stream", "host-backend")),
130            ),
131            (
132                Expr::Symbol(Symbol::new("transport")),
133                Expr::Symbol(self.transport.clone()),
134            ),
135            (
136                Expr::Symbol(Symbol::new("media")),
137                Expr::Symbol(self.media.symbol()),
138            ),
139            (
140                Expr::Symbol(Symbol::new("hardware-required")),
141                Expr::Bool(self.hardware_required),
142            ),
143            (
144                Expr::Symbol(Symbol::new("callbacks-bounded")),
145                Expr::Bool(self.callbacks_bounded),
146            ),
147            (
148                Expr::Symbol(Symbol::new("capabilities")),
149                Expr::List(
150                    self.capabilities
151                        .iter()
152                        .map(|capability| Expr::Symbol(capability.symbol()))
153                        .collect(),
154                ),
155            ),
156        ])
157    }
158}
159
160impl HostDeviceSpec {
161    /// Builds a device spec from its identity, media, direction, clock, and
162    /// buffer policy.
163    pub fn new(
164        id: Symbol,
165        backend: Symbol,
166        media: StreamMedia,
167        direction: HostDirection,
168        clock: Symbol,
169        buffer: BufferPolicy,
170    ) -> Self {
171        Self {
172            id,
173            backend,
174            media,
175            direction,
176            clock,
177            buffer,
178        }
179    }
180
181    /// Returns the device identifier symbol.
182    pub fn id(&self) -> &Symbol {
183        &self.id
184    }
185
186    /// Returns the owning backend symbol.
187    pub fn backend(&self) -> &Symbol {
188        &self.backend
189    }
190
191    /// Returns the device media.
192    pub fn media(&self) -> StreamMedia {
193        self.media
194    }
195
196    /// Returns the device direction.
197    pub fn direction(&self) -> HostDirection {
198        self.direction
199    }
200
201    /// Returns the device clock-domain symbol.
202    pub fn clock(&self) -> &Symbol {
203        &self.clock
204    }
205
206    /// Returns the device buffer policy.
207    pub fn buffer(&self) -> &BufferPolicy {
208        &self.buffer
209    }
210
211    /// Builds the [`StreamMetadata`] for a stream opened on this device.
212    pub fn metadata(&self) -> StreamMetadata {
213        StreamMetadata::new(
214            self.id.clone(),
215            self.media,
216            self.direction.stream_direction(),
217            self.clock.clone(),
218            self.buffer.clone(),
219        )
220    }
221
222    /// Builds the [`HostOpenPlan`] for opening this device.
223    pub fn open_plan(&self) -> HostOpenPlan {
224        HostOpenPlan {
225            backend: self.backend.clone(),
226            device: self.id.clone(),
227            effect_kinds: self.direction.effect_kinds(),
228            requires: vec![stream_host_capability()],
229        }
230    }
231
232    /// Builds the browse card expression for this device.
233    pub fn card_expr(&self) -> Expr {
234        Expr::Map(vec![
235            (
236                Expr::Symbol(Symbol::new("subject")),
237                Expr::Symbol(self.id.clone()),
238            ),
239            (
240                Expr::Symbol(Symbol::new("kind")),
241                Expr::Symbol(Symbol::qualified("stream", "host-device")),
242            ),
243            (
244                Expr::Symbol(Symbol::new("backend")),
245                Expr::Symbol(self.backend.clone()),
246            ),
247            (
248                Expr::Symbol(Symbol::new("media")),
249                Expr::Symbol(self.media.symbol()),
250            ),
251            (
252                Expr::Symbol(Symbol::new("direction")),
253                Expr::Symbol(self.direction.symbol()),
254            ),
255            (
256                Expr::Symbol(Symbol::new("clock")),
257                Expr::Symbol(self.clock.clone()),
258            ),
259            (Expr::Symbol(Symbol::new("buffer")), self.buffer.to_expr()),
260        ])
261    }
262}
263
264impl HostDirection {
265    /// Maps this host direction to the core [`StreamDirection`].
266    pub fn stream_direction(self) -> StreamDirection {
267        match self {
268            Self::Input => StreamDirection::Source,
269            Self::Output => StreamDirection::Sink,
270            Self::Duplex => StreamDirection::Duplex,
271        }
272    }
273
274    /// Returns the stable qualified symbol naming this direction.
275    pub fn symbol(self) -> Symbol {
276        match self {
277            Self::Input => Symbol::qualified("stream/host-direction", "input"),
278            Self::Output => Symbol::qualified("stream/host-direction", "output"),
279            Self::Duplex => Symbol::qualified("stream/host-direction", "duplex"),
280        }
281    }
282
283    /// Returns the effect kinds an open in this direction performs.
284    pub fn effect_kinds(self) -> Vec<Symbol> {
285        match self {
286            Self::Input => vec![stream_host_device_read_effect_kind()],
287            Self::Output => vec![stream_host_device_write_effect_kind()],
288            Self::Duplex => vec![
289                stream_host_device_read_effect_kind(),
290                stream_host_device_write_effect_kind(),
291            ],
292        }
293    }
294}
295
296impl HostOpenPlan {
297    /// Builds an open plan from resolved backend, device, effects, and
298    /// required capabilities.
299    pub fn new(
300        backend: Symbol,
301        device: Symbol,
302        effect_kinds: Vec<Symbol>,
303        requires: Vec<CapabilityName>,
304    ) -> Self {
305        Self {
306            backend,
307            device,
308            effect_kinds,
309            requires,
310        }
311    }
312
313    /// Returns the backend that would perform the open.
314    pub fn backend(&self) -> &Symbol {
315        &self.backend
316    }
317
318    /// Returns the device that would be opened.
319    pub fn device(&self) -> &Symbol {
320        &self.device
321    }
322
323    /// Returns the effect kinds the open performs.
324    pub fn effect_kinds(&self) -> &[Symbol] {
325        &self.effect_kinds
326    }
327
328    /// Returns the capabilities the open requires.
329    pub fn requires(&self) -> &[CapabilityName] {
330        &self.requires
331    }
332
333    /// Enforces the plan's capabilities and records every declared device
334    /// effect in the supplied context before a host stream is opened.
335    pub fn enforce(&self, cx: &mut Cx) -> Result<()> {
336        for capability in &self.requires {
337            cx.require(capability)?;
338        }
339        for effect_kind in &self.effect_kinds {
340            let effect = Effect::new(
341                effect_kind.clone(),
342                Ref::Symbol(self.device.clone()),
343                Ref::Symbol(self.backend.clone()),
344                Ref::Symbol(Symbol::qualified("stream/host", "open-result")),
345                effect_resume_op_key(),
346                effect_abort_op_key(),
347            )
348            .with_requirements(self.requires.clone());
349            resolve_effect(cx, effect, |_cx, _effect| {
350                Ok(Ref::Symbol(Symbol::qualified(
351                    "stream/host",
352                    "open-authorized",
353                )))
354            })?;
355        }
356        Ok(())
357    }
358}