Skip to main content

sim_lib_stream_device/
citizen.rs

1//! Runtime class and read-construct support for device sample values.
2
3use std::sync::Arc;
4
5use sim_kernel::{
6    AbiVersion, Args, CORE_CLASS_CLASS_ID, CORE_FUNCTION_CLASS_ID, Callable, Class, ClassId,
7    ClassRef, Cx, DefaultFactory, Export, Expr, Factory, Lib, LibManifest, LibTarget, Linker,
8    Object, ObjectCompat, ObjectEncode, ObjectEncoding, ReadConstructor, ReadConstructorRef,
9    Result, ShapeRef, Symbol, TableRef, Value, Version,
10};
11
12use crate::{
13    DeviceCaps, DeviceSample,
14    sample::{decode_known_sample, sample_constructor_args},
15};
16
17const DEVICE_SAMPLE_CLASS_ID: ClassId = ClassId(6201);
18
19/// Runtime object wrapping a device sample expression.
20///
21/// The value validates the sample expression on construction and encodes as a
22/// read constructor, allowing quoted device sample data to round-trip through
23/// the kernel object surface.
24#[derive(Clone)]
25pub struct DeviceSampleValue {
26    sample: Expr,
27}
28
29impl DeviceSampleValue {
30    /// Validates and wraps a device sample expression.
31    pub fn new(sample: Expr) -> Result<Self> {
32        decode_known_sample(&sample)?;
33        Ok(Self { sample })
34    }
35
36    /// Returns the wrapped sample expression.
37    pub fn sample(&self) -> &Expr {
38        &self.sample
39    }
40
41    /// Decodes the wrapped expression as the base device capabilities sample.
42    pub fn device_caps(&self) -> Result<DeviceCaps> {
43        Ok(DeviceCaps::from_expr(&self.sample)?)
44    }
45}
46
47impl Object for DeviceSampleValue {
48    fn display(&self, _cx: &mut Cx) -> Result<String> {
49        Ok("#<stream-device-sample>".to_owned())
50    }
51
52    fn as_any(&self) -> &dyn std::any::Any {
53        self
54    }
55}
56
57impl ObjectCompat for DeviceSampleValue {
58    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
59        class_value_or_stub(cx)
60    }
61
62    fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
63        Ok(Expr::Call {
64            operator: Box::new(Expr::Symbol(device_sample_class_symbol())),
65            args: sample_constructor_args(&self.sample)?,
66        })
67    }
68
69    fn as_table(&self, cx: &mut Cx) -> Result<Value> {
70        let Expr::Map(entries) = self.sample.clone() else {
71            unreachable!("validated device samples are maps");
72        };
73        cx.factory().table(
74            entries
75                .into_iter()
76                .map(|(key, value)| match key {
77                    Expr::Symbol(symbol) => Ok((symbol, cx.factory().expr(value)?)),
78                    _ => unreachable!("device sample map keys are symbols"),
79                })
80                .collect::<Result<Vec<_>>>()?,
81        )
82    }
83
84    fn as_object_encoder(&self) -> Option<&dyn ObjectEncode> {
85        Some(self)
86    }
87}
88
89impl ObjectEncode for DeviceSampleValue {
90    fn object_encoding(&self, _cx: &mut Cx) -> Result<ObjectEncoding> {
91        Ok(ObjectEncoding::Constructor {
92            class: device_sample_class_symbol(),
93            args: sample_constructor_args(&self.sample)?,
94        })
95    }
96}
97
98impl sim_citizen::Citizen for DeviceSampleValue {
99    fn citizen_symbol() -> Symbol {
100        device_sample_class_symbol()
101    }
102
103    fn citizen_version() -> u32 {
104        0
105    }
106
107    fn citizen_arity() -> usize {
108        1
109    }
110
111    fn citizen_fields() -> &'static [&'static str] {
112        &["sample"]
113    }
114}
115
116/// Host-registered library that installs the device stream base class.
117pub struct DeviceStreamBaseLib;
118
119impl Lib for DeviceStreamBaseLib {
120    fn manifest(&self) -> LibManifest {
121        LibManifest {
122            id: device_stream_base_manifest_symbol(),
123            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
124            abi: AbiVersion { major: 0, minor: 1 },
125            target: LibTarget::HostRegistered,
126            requires: Vec::new(),
127            capabilities: Vec::new(),
128            exports: device_stream_base_exports(),
129        }
130    }
131
132    fn load(&self, cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
133        register_device_sample_class(linker)?;
134        linker.value(
135            crate::device_caps_sample_kind_symbol(),
136            cx.factory()
137                .expr(Expr::Symbol(crate::device_caps_sample_kind_symbol()))?,
138        )?;
139        Ok(())
140    }
141}
142
143/// Installs the device stream base into a context exactly once.
144pub fn install_device_stream_base(cx: &mut Cx) -> Result<()> {
145    sim_lib_core::install_once(cx, &DeviceStreamBaseLib).map(|_| ())
146}
147
148/// Export records advertised by [`DeviceStreamBaseLib`].
149pub fn device_stream_base_exports() -> Vec<Export> {
150    vec![
151        Export::Class {
152            symbol: device_sample_class_symbol(),
153            class_id: Some(DEVICE_SAMPLE_CLASS_ID),
154        },
155        Export::Value {
156            symbol: crate::device_caps_sample_kind_symbol(),
157        },
158    ]
159}
160
161/// Returns the manifest id for the device stream base library.
162pub fn device_stream_base_manifest_symbol() -> Symbol {
163    Symbol::qualified("stream", "device-base")
164}
165
166/// Returns the read-construct class symbol for device sample values.
167pub fn device_sample_class_symbol() -> Symbol {
168    Symbol::qualified("stream", "DeviceSample")
169}
170
171fn register_device_sample_class(linker: &mut Linker<'_>) -> Result<()> {
172    let class = DefaultFactory
173        .opaque(Arc::new(DeviceSampleClass))
174        .expect("device sample class should be boxable");
175    let id = linker.class_with_id(device_sample_class_symbol(), DEVICE_SAMPLE_CLASS_ID)?;
176    linker.bind_class_value(id, class)?;
177    Ok(())
178}
179
180fn install_device_sample_citizen(linker: &mut Linker<'_>) -> Result<()> {
181    register_device_sample_class(linker)
182}
183
184fn conformance_device_sample_citizen(cx: &mut Cx) -> Result<()> {
185    let value = cx.factory().opaque(Arc::new(DeviceSampleValue::new(
186        DeviceCaps::demo(0).to_expr(),
187    )?))?;
188    sim_citizen::check_value_fixture(cx, value)
189}
190
191sim_citizen::inventory::submit! {
192    sim_citizen::CitizenInfo {
193        symbol: "stream/DeviceSample",
194        version: 0,
195        crate_name: env!("CARGO_PKG_NAME"),
196        arity: 1,
197        install: install_device_sample_citizen,
198        conformance: conformance_device_sample_citizen,
199    }
200}
201
202#[derive(Clone)]
203struct DeviceSampleClass;
204
205impl Object for DeviceSampleClass {
206    fn display(&self, _cx: &mut Cx) -> Result<String> {
207        Ok("#<class stream/DeviceSample>".to_owned())
208    }
209
210    fn as_any(&self) -> &dyn std::any::Any {
211        self
212    }
213}
214
215impl ObjectCompat for DeviceSampleClass {
216    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
217        let symbol = Symbol::qualified("core", "Class");
218        if let Some(value) = cx.registry().class_by_symbol(&symbol) {
219            return Ok(value.clone());
220        }
221        cx.factory().class_stub(CORE_CLASS_CLASS_ID, symbol)
222    }
223
224    fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
225        Ok(Expr::Symbol(device_sample_class_symbol()))
226    }
227
228    fn as_callable(&self) -> Option<&dyn Callable> {
229        Some(self)
230    }
231
232    fn as_class(&self) -> Option<&dyn Class> {
233        Some(self)
234    }
235}
236
237impl Callable for DeviceSampleClass {
238    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
239        construct_device_sample_value(cx, args.into_vec())
240    }
241}
242
243impl Class for DeviceSampleClass {
244    fn id(&self) -> ClassId {
245        DEVICE_SAMPLE_CLASS_ID
246    }
247
248    fn symbol(&self) -> Symbol {
249        device_sample_class_symbol()
250    }
251
252    fn constructor_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
253        cx.factory().nil()
254    }
255
256    fn instance_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
257        cx.factory().nil()
258    }
259
260    fn read_constructor(&self, _cx: &mut Cx) -> Result<Option<ReadConstructorRef>> {
261        Ok(Some(
262            DefaultFactory.opaque(Arc::new(DeviceSampleReadConstructor))?,
263        ))
264    }
265
266    fn members(&self, cx: &mut Cx) -> Result<TableRef> {
267        cx.factory().table(Vec::new())
268    }
269}
270
271#[derive(Clone)]
272struct DeviceSampleReadConstructor;
273
274impl Object for DeviceSampleReadConstructor {
275    fn display(&self, _cx: &mut Cx) -> Result<String> {
276        Ok("#<read-constructor stream/DeviceSample>".to_owned())
277    }
278
279    fn as_any(&self) -> &dyn std::any::Any {
280        self
281    }
282}
283
284impl ObjectCompat for DeviceSampleReadConstructor {
285    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
286        let symbol = Symbol::qualified("core", "Function");
287        if let Some(value) = cx.registry().class_by_symbol(&symbol) {
288            return Ok(value.clone());
289        }
290        cx.factory().class_stub(CORE_FUNCTION_CLASS_ID, symbol)
291    }
292
293    fn as_read_constructor(&self) -> Option<&dyn ReadConstructor> {
294        Some(self)
295    }
296}
297
298impl ReadConstructor for DeviceSampleReadConstructor {
299    fn symbol(&self) -> Symbol {
300        device_sample_class_symbol()
301    }
302
303    fn args_shape(&self, cx: &mut Cx) -> Result<ShapeRef> {
304        cx.factory().nil()
305    }
306
307    fn construct_read(&self, cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
308        construct_device_sample_value(cx, args)
309    }
310}
311
312fn construct_device_sample_value(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
313    let [sample] = args.as_slice() else {
314        return Err(sim_kernel::Error::Eval(
315            "stream/DeviceSample expects one constructor argument".to_owned(),
316        ));
317    };
318    let expr = sample.object().as_expr(cx)?;
319    cx.factory().opaque(Arc::new(DeviceSampleValue::new(expr)?))
320}
321
322fn class_value_or_stub(cx: &mut Cx) -> Result<Value> {
323    if let Some(value) = cx.registry().class_by_symbol(&device_sample_class_symbol()) {
324        return Ok(value.clone());
325    }
326    cx.factory()
327        .class_stub(DEVICE_SAMPLE_CLASS_ID, device_sample_class_symbol())
328}