Skip to main content

sim_lib_stream_host/
device.rs

1//! Session-oriented stream-device provider surface.
2
3use std::fmt;
4
5use sim_kernel::{Expr, Symbol};
6
7/// Result type returned by device providers and sessions.
8pub type DeviceResult<T> = std::result::Result<T, DeviceError>;
9
10/// Error returned by stream-device providers and sessions.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub enum DeviceError {
13    /// The selected provider does not support opening or using a device.
14    Unsupported,
15    /// A sample expression was malformed for the requested sample kind.
16    Sample(String),
17    /// A provider or session failed for a host-specific reason.
18    Host(String),
19}
20
21impl fmt::Display for DeviceError {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        match self {
24            Self::Unsupported => f.write_str("device provider is unsupported"),
25            Self::Sample(message) => write!(f, "device sample error: {message}"),
26            Self::Host(message) => f.write_str(message),
27        }
28    }
29}
30
31impl std::error::Error for DeviceError {}
32
33impl From<DeviceError> for sim_kernel::Error {
34    fn from(error: DeviceError) -> Self {
35        match error {
36            DeviceError::Unsupported => {
37                Self::HostError("device provider is unsupported".to_owned())
38            }
39            DeviceError::Sample(message) => Self::Eval(format!("device sample error: {message}")),
40            DeviceError::Host(message) => Self::HostError(message),
41        }
42    }
43}
44
45/// Stream-facing profile advertised by a concrete device.
46#[derive(Clone, Debug, PartialEq, Eq)]
47pub struct DeviceProfile {
48    /// Stable device identity.
49    pub device: Symbol,
50    /// Sample streams this device can emit.
51    pub streams: Vec<Symbol>,
52    /// Input controls accepted by the device.
53    pub inputs: Vec<Symbol>,
54    /// Output actuators exposed by the device.
55    pub outputs: Vec<Symbol>,
56    /// Sample kinds the provider may return from [`DeviceSession::poll`].
57    pub sample_kinds: Vec<Symbol>,
58}
59
60impl DeviceProfile {
61    /// Builds a device profile from stable stream-facing metadata.
62    pub fn new(
63        device: Symbol,
64        streams: Vec<Symbol>,
65        inputs: Vec<Symbol>,
66        outputs: Vec<Symbol>,
67        sample_kinds: Vec<Symbol>,
68    ) -> Self {
69        Self {
70            device,
71            streams,
72            inputs,
73            outputs,
74            sample_kinds,
75        }
76    }
77
78    /// Builds the deterministic modeled edge profile used by tests and docs.
79    pub fn modeled_edge() -> Self {
80        Self::new(
81            Symbol::qualified("device", "modeled-edge"),
82            vec![
83                Symbol::qualified("device/stream", "battery"),
84                Symbol::qualified("device/stream", "motion"),
85            ],
86            vec![Symbol::qualified("device/input", "button")],
87            vec![
88                Symbol::qualified("device/output", "screen"),
89                Symbol::qualified("device/output", "haptic"),
90            ],
91            vec![device_sample_kind_symbol("device-caps")],
92        )
93    }
94
95    /// Returns whether this profile advertises `sample_kind`.
96    pub fn supports_sample_kind(&self, sample_kind: &Symbol) -> bool {
97        self.sample_kinds.contains(sample_kind)
98    }
99}
100
101/// Device sample expression contract used by session polling helpers.
102pub trait DeviceSample: Sized {
103    /// Stable bare sample kind, such as `device-caps`.
104    fn sample_kind() -> &'static str;
105
106    /// Encodes the sample as a self-describing expression.
107    fn to_expr(&self) -> Expr;
108
109    /// Decodes the sample from its expression form.
110    fn from_expr(expr: &Expr) -> DeviceResult<Self>;
111}
112
113/// Returns the qualified sample-kind symbol for `kind`.
114pub fn device_sample_kind_symbol(kind: &str) -> Symbol {
115    Symbol::qualified("stream/device-sample", kind)
116}
117
118/// Provider that opens one stream-device session.
119pub trait DeviceProvider: Send {
120    /// Opens a provider-owned device session.
121    fn open(&self) -> DeviceResult<Box<dyn DeviceSession>>;
122}
123
124/// Open stream-device session.
125pub trait DeviceSession: Send {
126    /// Returns the profile for this session.
127    fn profile(&self) -> &DeviceProfile;
128
129    /// Starts sample or command processing.
130    fn start(&mut self) -> DeviceResult<()>;
131
132    /// Polls one sample expression for `kind`.
133    fn poll(&mut self, kind: &str) -> DeviceResult<Option<Expr>>;
134
135    /// Sends an actuator command expression to the device.
136    fn send(&mut self, command: &Expr) -> DeviceResult<()>;
137
138    /// Stops sample or command processing and releases session resources.
139    fn stop(&mut self) -> DeviceResult<()>;
140}
141
142/// Polls and decodes a typed device sample from a session.
143pub fn poll_device_sample<S>(session: &mut dyn DeviceSession) -> DeviceResult<Option<S>>
144where
145    S: DeviceSample,
146{
147    session
148        .poll(S::sample_kind())?
149        .map(|expr| S::from_expr(&expr))
150        .transpose()
151}
152
153/// Hardware-free provider used when no concrete device provider is installed.
154#[derive(Clone, Debug, PartialEq, Eq)]
155pub struct StubProvider {
156    profile: DeviceProfile,
157}
158
159impl StubProvider {
160    /// Builds a stub provider for the supplied profile.
161    pub fn new(profile: DeviceProfile) -> Self {
162        Self { profile }
163    }
164
165    /// Returns the profile this stub advertises for browse and placement.
166    pub fn profile(&self) -> &DeviceProfile {
167        &self.profile
168    }
169
170    /// Builds an unopened stub session for provider-surface validation.
171    pub fn session(&self) -> StubSession {
172        StubSession::new(self.profile.clone())
173    }
174}
175
176impl DeviceProvider for StubProvider {
177    fn open(&self) -> DeviceResult<Box<dyn DeviceSession>> {
178        Err(DeviceError::Unsupported)
179    }
180}
181
182/// Hardware-free session that refuses all live device operations.
183#[derive(Clone, Debug, PartialEq, Eq)]
184pub struct StubSession {
185    profile: DeviceProfile,
186}
187
188impl StubSession {
189    /// Builds a stub session for the supplied profile.
190    pub fn new(profile: DeviceProfile) -> Self {
191        Self { profile }
192    }
193}
194
195impl DeviceSession for StubSession {
196    fn profile(&self) -> &DeviceProfile {
197        &self.profile
198    }
199
200    fn start(&mut self) -> DeviceResult<()> {
201        Err(DeviceError::Unsupported)
202    }
203
204    fn poll(&mut self, _kind: &str) -> DeviceResult<Option<Expr>> {
205        Err(DeviceError::Unsupported)
206    }
207
208    fn send(&mut self, _command: &Expr) -> DeviceResult<()> {
209        Err(DeviceError::Unsupported)
210    }
211
212    fn stop(&mut self) -> DeviceResult<()> {
213        Ok(())
214    }
215}