Skip to main content

sim_lib_scene/
shapes.rs

1//! Shapes for scene node kinds.
2//!
3//! Each baseline scene node kind gets a Shape that matches an `Expr::Map` whose
4//! `kind` tag equals that kind. View selection (WEBUI_4 P3) is overload
5//! selection over these Shapes, so the same matcher the kernel already uses for
6//! dispatch chooses lenses; there is no separate selection ladder. An umbrella
7//! `scene/Scene` Shape matches any recognized scene node and is used as the
8//! `codec:scene` expression shape.
9
10use sim_kernel::{Cx, Expr, MatchScore, Result, Shape, ShapeDoc, ShapeMatch, Symbol, Value};
11
12use crate::kinds::{SCENE_KINDS, SCENE_NAMESPACE, is_known_kind, scene_kind};
13use crate::model::node_kind;
14
15/// A Shape that accepts exactly one scene node kind.
16pub struct SceneNodeShape {
17    kind: Symbol,
18    symbol: Symbol,
19}
20
21impl SceneNodeShape {
22    /// Build the Shape for the scene node kind named `name` (e.g. `graph`).
23    pub fn new(name: &str) -> Self {
24        Self {
25            kind: scene_kind(name),
26            symbol: Symbol::qualified(SCENE_NAMESPACE, capitalize(name)),
27        }
28    }
29}
30
31impl Shape for SceneNodeShape {
32    fn symbol(&self) -> Option<Symbol> {
33        Some(self.symbol.clone())
34    }
35
36    fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
37        let expr = value.object().as_expr(cx)?;
38        self.check_expr(cx, &expr)
39    }
40
41    fn check_expr(&self, _cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
42        match node_kind(expr) {
43            Some(kind) if kind == self.kind => Ok(ShapeMatch::accept(MatchScore::exact(20))),
44            Some(kind) => Ok(ShapeMatch::reject(format!(
45                "scene node kind '{kind}' does not match '{}'",
46                self.kind
47            ))),
48            None => Ok(ShapeMatch::reject("value is not a scene node")),
49        }
50    }
51
52    fn describe(&self, _cx: &mut Cx) -> Result<ShapeDoc> {
53        Ok(ShapeDoc::new(self.symbol.name.to_string())
54            .with_detail(format!("matches scene nodes tagged '{}'", self.kind)))
55    }
56}
57
58/// The umbrella Shape that accepts any recognized scene node.
59pub struct SceneShape;
60
61impl Shape for SceneShape {
62    fn symbol(&self) -> Option<Symbol> {
63        Some(Symbol::qualified(SCENE_NAMESPACE, "Scene"))
64    }
65
66    fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
67        let expr = value.object().as_expr(cx)?;
68        self.check_expr(cx, &expr)
69    }
70
71    fn check_expr(&self, _cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
72        match node_kind(expr) {
73            Some(kind) if is_known_kind(&kind) => Ok(ShapeMatch::accept(MatchScore::exact(5))),
74            Some(kind) => Ok(ShapeMatch::reject(format!(
75                "unrecognized scene kind '{kind}'"
76            ))),
77            None => Ok(ShapeMatch::reject("value is not a scene node")),
78        }
79    }
80
81    fn describe(&self, _cx: &mut Cx) -> Result<ShapeDoc> {
82        Ok(ShapeDoc::new("Scene").with_detail("any recognized scene node (a kind-tagged map)"))
83    }
84}
85
86/// The symbol for the umbrella `scene/Scene` Shape.
87pub fn scene_shape_symbol() -> Symbol {
88    Symbol::qualified(SCENE_NAMESPACE, "Scene")
89}
90
91/// Build `(symbol, shape)` registrations for the umbrella Shape plus every
92/// baseline scene node kind Shape.
93pub fn scene_shape_specs() -> Vec<(Symbol, std::sync::Arc<dyn Shape>)> {
94    let mut specs: Vec<(Symbol, std::sync::Arc<dyn Shape>)> =
95        vec![(scene_shape_symbol(), std::sync::Arc::new(SceneShape))];
96    for name in SCENE_KINDS {
97        let shape = SceneNodeShape::new(name);
98        specs.push((shape.symbol.clone(), std::sync::Arc::new(shape)));
99    }
100    specs
101}
102
103fn capitalize(name: &str) -> String {
104    let mut chars = name.chars();
105    match chars.next() {
106        Some(first) => first.to_ascii_uppercase().to_string() + chars.as_str(),
107        None => String::new(),
108    }
109}