Skip to main content

rustmotion_core/vars/
scope.rs

1use crate::expr::Scope;
2
3use super::track::VarTable;
4
5pub struct VarScope<'a> {
6    scenario: &'a VarTable,
7    scene: Option<&'a VarTable>,
8    t: f64,
9}
10
11impl<'a> VarScope<'a> {
12    pub fn new(scenario: &'a VarTable, scene: Option<&'a VarTable>, t: f64) -> Self {
13        VarScope { scenario, scene, t }
14    }
15
16    pub fn resolve(&self, name: &str) -> Option<f64> {
17        self.scene
18            .and_then(|scene| scene.value_at(name, self.t))
19            .or_else(|| self.scenario.value_at(name, self.t))
20    }
21}
22
23impl Scope for VarScope<'_> {
24    fn var(&self, name: &str) -> Option<f64> {
25        self.resolve(name)
26    }
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32    use crate::expr::Expr;
33    use crate::schema::animation::EasingType;
34    use crate::schema::time::{TimeCtx, TimePoint};
35    use crate::vars::schema::{VarDef, VarKeyframe, VarSet};
36
37    fn ctx() -> TimeCtx {
38        TimeCtx {
39            bpm: None,
40            beat_offset: 0.0,
41            scene_start: 0.0,
42        }
43    }
44
45    fn table_with(name: &str, def: VarDef) -> VarTable {
46        let mut vars = VarSet::new();
47        vars.insert(name.to_string(), def);
48        VarTable::compile(&vars, &ctx()).unwrap()
49    }
50
51    #[test]
52    fn expression_reads_a_scenario_level_variable() {
53        let scenario = table_with(
54            "keyDraw",
55            VarDef {
56                default: 0.0,
57                animation: vec![VarKeyframe {
58                    at: TimePoint::Seconds(0.0),
59                    to: 1.0,
60                    duration: TimePoint::Seconds(1.0),
61                    easing: EasingType::Linear,
62                }],
63            },
64        );
65        let scope = VarScope::new(&scenario, None, 0.5);
66
67        let expr = Expr::parse("= $keyDraw * 360").unwrap();
68        assert!(expr.is_static());
69        assert_eq!(expr.eval(&scope).unwrap(), 180.0);
70    }
71
72    #[test]
73    fn scene_level_variable_shadows_scenario_level() {
74        let scenario = table_with(
75            "speed",
76            VarDef {
77                default: 1.0,
78                animation: Vec::new(),
79            },
80        );
81        let scene = table_with(
82            "speed",
83            VarDef {
84                default: 9.0,
85                animation: Vec::new(),
86            },
87        );
88
89        let shadowed = VarScope::new(&scenario, Some(&scene), 0.0);
90        assert_eq!(shadowed.resolve("speed"), Some(9.0));
91
92        let unshadowed = VarScope::new(&scenario, None, 0.0);
93        assert_eq!(unshadowed.resolve("speed"), Some(1.0));
94    }
95
96    #[test]
97    fn scene_local_variable_is_invisible_outside_its_scene() {
98        let scenario = VarTable::compile(&VarSet::new(), &ctx()).unwrap();
99        let scene_b = table_with(
100            "onlyInB",
101            VarDef {
102                default: 5.0,
103                animation: Vec::new(),
104            },
105        );
106        let _ = &scene_b;
107
108        let scope_in_scene_a = VarScope::new(&scenario, None, 0.0);
109        assert_eq!(scope_in_scene_a.resolve("onlyInB"), None);
110
111        let expr = Expr::parse("= $onlyInB").unwrap();
112        let err = expr.eval(&scope_in_scene_a).unwrap_err();
113        assert_eq!(
114            err,
115            crate::expr::ExprError::UnknownIdent("onlyInB".to_string())
116        );
117    }
118
119    #[test]
120    fn a_host_scope_can_hold_a_var_scope_and_delegate_to_resolve() {
121        struct EngineScope<'a> {
122            vars: VarScope<'a>,
123        }
124
125        impl crate::expr::Scope for EngineScope<'_> {
126            fn var(&self, name: &str) -> Option<f64> {
127                self.vars.resolve(name)
128            }
129
130            fn node_prop(&self, id: &str, prop: &str) -> Option<f64> {
131                let _ = (id, prop);
132                None
133            }
134        }
135
136        let scenario = table_with(
137            "W",
138            VarDef {
139                default: 1080.0,
140                animation: Vec::new(),
141            },
142        );
143        let host = EngineScope {
144            vars: VarScope::new(&scenario, None, 0.0),
145        };
146        assert_eq!(crate::expr::Scope::var(&host, "W"), Some(1080.0));
147        assert_eq!(crate::expr::Scope::node_prop(&host, "n", "x"), None);
148    }
149}