Skip to main content

sim_lib_scene/
codec.rs

1//! `codec:scene`: the domain codec for Scene values.
2//!
3//! Modelled on `codec:chat`, this codec round-trips Scene values only and fails
4//! closed outside its domain: it validates the scene on the way in and on the
5//! way out, and the canonical text form (see [`crate::text`]) refuses non-data
6//! `Expr` forms. A malformed scene becomes a structured `CodecError`, never a
7//! panic. It is built on the shared [`DomainCodecLib`] scaffold and registers
8//! the scene node Shapes through `with_shapes`.
9
10use std::sync::Arc;
11
12use sim_codec::{Decoder, DomainCodecLib, Encoder, Input, Output, ReadCx, domain_input_text};
13use sim_kernel::{CodecId, Error, Lib, LibManifest, Linker, LoadCx, Result, Symbol, WriteCx};
14use sim_shape::shape_value;
15
16use crate::model::validate_scene;
17use crate::shapes::{scene_shape_specs, scene_shape_symbol};
18use crate::text;
19
20/// Stable codec symbol for the scene domain codec.
21pub fn scene_codec_symbol() -> Symbol {
22    Symbol::qualified("codec", "scene")
23}
24
25/// The Scene domain codec object.
26pub struct SceneCodec;
27
28impl Decoder for SceneCodec {
29    fn decode(&self, cx: &mut ReadCx<'_>, input: Input) -> Result<sim_kernel::Expr> {
30        let source = domain_input_text(cx.codec, input)?;
31        let expr = text::decode(cx.codec, &source)?;
32        validate(cx.codec, &expr)?;
33        Ok(expr)
34    }
35}
36
37impl Encoder for SceneCodec {
38    fn encode(&self, cx: &mut WriteCx<'_>, expr: &sim_kernel::Expr) -> Result<Output> {
39        validate(cx.codec, expr)?;
40        Ok(Output::Text(text::encode(cx.codec, expr)?))
41    }
42}
43
44fn validate(codec: CodecId, expr: &sim_kernel::Expr) -> Result<()> {
45    validate_scene(expr).map_err(|error| Error::CodecError {
46        codec,
47        message: format!("malformed scene at {error}"),
48    })
49}
50
51/// Library that registers the scene node Shapes and `codec:scene`.
52pub struct SceneCodecLib {
53    symbol: Symbol,
54    codec_id: CodecId,
55}
56
57impl SceneCodecLib {
58    /// Build the lib with a freshly allocated codec id.
59    pub fn new(codec_id: CodecId) -> Self {
60        Self {
61            symbol: scene_codec_symbol(),
62            codec_id,
63        }
64    }
65
66    fn domain_lib(&self) -> DomainCodecLib {
67        let shapes = scene_shape_specs()
68            .into_iter()
69            .map(|(symbol, shape)| (symbol.clone(), shape_value(symbol, shape)))
70            .collect();
71        DomainCodecLib::new(
72            self.symbol.clone(),
73            self.codec_id,
74            Arc::new(SceneCodec),
75            Arc::new(SceneCodec),
76            scene_shape_symbol(),
77        )
78        .with_shapes(shapes)
79    }
80}
81
82impl Lib for SceneCodecLib {
83    fn manifest(&self) -> LibManifest {
84        self.domain_lib().manifest()
85    }
86
87    fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
88        self.domain_lib().load(cx, linker)
89    }
90}