Skip to main content

concinnity_core/behavior/
compile.rs

1// Name resolution: the authored body walked once, every name replaced by the
2// slot that holds its value at tick time.
3//
4// Compilation is total. The world crate's checker has already rejected unknown
5// names and mistyped expressions, so anything unresolvable here becomes
6// `CExpr::Never` or `COp::Never`, which evaluates to nothing and skips its node
7// rather than panicking.
8
9use alloc::boxed::Box;
10use alloc::string::{String, ToString};
11use alloc::vec::Vec;
12
13use crate::behavior::program::{CExpr, CNode, COp, Program, VarTable};
14use crate::behavior::value::{Arith, Cmp, Val};
15use crate::components::{Behavior, BehaviorExpr, BehaviorNode, BehaviorSource};
16use crate::ecs::{ComponentTag, TracePath, TraceStep};
17
18// Compile-time name scope, mirroring the world crate's checker.
19struct Names<'a> {
20    locals: &'a [String],
21    queries: &'a [String],
22    bindings: Vec<String>,
23    // High-water mark of concurrently live bindings, which sizes the frame.
24    peak: usize,
25}
26
27impl Names<'_> {
28    fn bind(&mut self, name: &str) -> u16 {
29        self.bindings.push(name.to_string());
30        self.peak = self.peak.max(self.bindings.len());
31        (self.bindings.len() - 1) as u16
32    }
33
34    fn binding(&self, name: &str) -> Option<u16> {
35        self.bindings
36            .iter()
37            .rposition(|n| n == name)
38            .map(|i| i as u16)
39    }
40
41    fn local(&self, name: &str) -> Option<u16> {
42        self.locals.iter().position(|n| n == name).map(|i| i as u16)
43    }
44
45    fn query(&self, name: &str) -> Option<u16> {
46        self.queries
47            .iter()
48            .position(|n| n == name)
49            .map(|i| i as u16)
50    }
51}
52
53// Resolve an authored component name to the tag that still holds its entities
54// at tick time. A load-time pass drains some columns during `World::start`;
55// `surviving_tag` maps those to the runtime component that replaces them
56// (`Prop` -> `PropInstance`) and drops the ones nothing replaces, which the
57// world checker rejects before a build gets here.
58fn surviving_tag(name: &str) -> Option<u8> {
59    ComponentTag::parse(name)
60        .and_then(ComponentTag::surviving_tag)
61        .map(|t| t as u8)
62}
63
64/// Compile one authored behavior against the world's shared variable table,
65/// interning any variable name it mentions that no `Variables` asset declared.
66pub fn compile(def: Behavior, vars: &mut VarTable) -> Program {
67    let scope: Vec<u8> = def.scope.iter().filter_map(|c| surviving_tag(c)).collect();
68    let local_names: Vec<String> = def.locals.iter().map(|l| l.name.clone()).collect();
69    let local_inits: Vec<Val> = def
70        .locals
71        .iter()
72        .map(|l| Val::from_literal(&l.value))
73        .collect();
74    let query_names: Vec<String> = def.queries.iter().map(|q| q.name.clone()).collect();
75    let queries: Vec<Vec<u8>> = def
76        .queries
77        .iter()
78        .map(|q| q.has.iter().filter_map(|c| surviving_tag(c)).collect())
79        .collect();
80
81    // A variable source names a variable that must have a slot even if no node
82    // ever writes it.
83    if let BehaviorSource::Variable(name) = &def.on {
84        vars.intern(name);
85    }
86
87    let mut names = Names {
88        locals: &local_names,
89        queries: &query_names,
90        bindings: Vec::new(),
91        peak: 0,
92    };
93    let mut paths = Vec::new();
94    let body = compile_nodes(
95        &def.body,
96        &mut names,
97        vars,
98        &[TraceStep::Field("do")],
99        &mut paths,
100    );
101    let bindings = names.peak;
102
103    Program {
104        def,
105        scope,
106        local_inits,
107        queries,
108        body,
109        paths,
110        bindings,
111    }
112}
113
114// A branch list's path base: the parent node's path plus the verb and branch
115// keys the authored JSON nests it under (matching the world checker's fault
116// paths, so a traced node lands on the same row / card a fault would).
117fn branch(path: &[TraceStep], verb: &'static str, list: &'static str) -> Vec<TraceStep> {
118    let mut base = path.to_vec();
119    base.push(TraceStep::Field(verb));
120    base.push(TraceStep::Field(list));
121    base
122}
123
124fn compile_nodes(
125    nodes: &[BehaviorNode],
126    names: &mut Names<'_>,
127    vars: &mut VarTable,
128    base: &[TraceStep],
129    paths: &mut Vec<TracePath>,
130) -> Vec<CNode> {
131    let depth = names.bindings.len();
132    let out = nodes
133        .iter()
134        .enumerate()
135        .map(|(i, n)| {
136            let mut path = base.to_vec();
137            path.push(TraceStep::Index(i as u32));
138            // Pre-order: the node claims its id (and path slot) before its
139            // branches compile, so ids read top-down.
140            let id = paths.len() as u32;
141            paths.push(path.clone());
142            let op = compile_node(n, names, vars, &path, paths);
143            CNode { id, op }
144        })
145        .collect();
146    names.bindings.truncate(depth);
147    out
148}
149
150fn compile_node(
151    node: &BehaviorNode,
152    names: &mut Names<'_>,
153    vars: &mut VarTable,
154    path: &[TraceStep],
155    paths: &mut Vec<TracePath>,
156) -> COp {
157    match node {
158        BehaviorNode::If {
159            cond,
160            then,
161            otherwise,
162        } => COp::If {
163            cond: compile_expr(cond, names, vars),
164            then: compile_nodes(then, names, vars, &branch(path, "if", "then"), paths),
165            otherwise: compile_nodes(otherwise, names, vars, &branch(path, "if", "else"), paths),
166        },
167        BehaviorNode::ForEach { query, bind, body } => {
168            let Some(query) = names.query(query) else {
169                return COp::Never;
170            };
171            let depth = names.bindings.len();
172            let bind = names.bind(bind);
173            let body = compile_nodes(body, names, vars, &branch(path, "for_each", "do"), paths);
174            names.bindings.truncate(depth);
175            COp::ForEach { query, bind, body }
176        }
177        BehaviorNode::Let { name, value } => {
178            let value = compile_expr(value, names, vars);
179            COp::Let {
180                bind: names.bind(name),
181                value,
182            }
183        }
184        BehaviorNode::Set { var, value, add } => COp::SetVar {
185            slot: vars.intern(var),
186            value: compile_expr(value, names, vars),
187            add: *add,
188        },
189        BehaviorNode::SetLocal { local, value, add } => match names.local(local) {
190            Some(slot) => COp::SetLocal {
191                slot,
192                value: compile_expr(value, names, vars),
193                add: *add,
194            },
195            None => COp::Never,
196        },
197        BehaviorNode::SetTransform {
198            entity,
199            position,
200            rotation_deg,
201            scale,
202        } => COp::SetTransform {
203            entity: compile_expr(entity, names, vars),
204            position: position.as_ref().map(|e| compile_expr(e, names, vars)),
205            rotation_deg: rotation_deg.as_ref().map(|e| compile_expr(e, names, vars)),
206            scale: scale.as_ref().map(|e| compile_expr(e, names, vars)),
207        },
208        BehaviorNode::Spawn {
209            template,
210            position,
211            rotation_deg,
212            scale,
213            lifetime,
214            bind,
215        } => match template {
216            Some(template) => COp::Spawn {
217                template: *template,
218                position: *position,
219                rotation_deg: *rotation_deg,
220                // A zero scale would make the copy invisible; treat it as unit
221                // scale, like the spawn request path.
222                scale: if *scale == [0.0; 3] { [1.0; 3] } else { *scale },
223                lifetime: *lifetime,
224                bind: bind.as_ref().map(|b| names.bind(b)),
225            },
226            None => COp::Never,
227        },
228        BehaviorNode::Despawn { target } => COp::Despawn(compile_expr(target, names, vars)),
229        BehaviorNode::Reparent { child, parent } => COp::Reparent {
230            child: compile_expr(child, names, vars),
231            parent: parent.as_ref().map(|e| compile_expr(e, names, vars)),
232        },
233        BehaviorNode::Show { target } => COp::Visible(compile_expr(target, names, vars), true),
234        BehaviorNode::Hide { target } => COp::Visible(compile_expr(target, names, vars), false),
235        BehaviorNode::Sound { clip, kind, volume } => match clip {
236            Some(clip) => COp::Sound {
237                clip: *clip,
238                kind: *kind,
239                volume: *volume,
240            },
241            None => COp::Never,
242        },
243        BehaviorNode::Scene { scene, transition } => match scene {
244            Some(scene) => COp::Scene {
245                scene: *scene,
246                transition: transition.clone(),
247            },
248            None => COp::Never,
249        },
250        BehaviorNode::Screen { screen } => match screen {
251            Some(screen) => COp::Screen(*screen),
252            None => COp::Never,
253        },
254        BehaviorNode::Story(playback) => COp::Story(*playback),
255        BehaviorNode::Save => COp::Save,
256    }
257}
258
259fn compile_expr(expr: &BehaviorExpr, names: &mut Names<'_>, vars: &mut VarTable) -> CExpr {
260    let binary =
261        |a: &BehaviorExpr, b: &BehaviorExpr, names: &mut Names<'_>, vars: &mut VarTable| {
262            (
263                Box::new(compile_expr(a, names, vars)),
264                Box::new(compile_expr(b, names, vars)),
265            )
266        };
267    match expr {
268        BehaviorExpr::Bool(b) => CExpr::Lit(Val::Bool(*b)),
269        BehaviorExpr::Int(i) => CExpr::Lit(Val::Int(*i)),
270        BehaviorExpr::Float(f) => CExpr::Lit(Val::Float(*f)),
271        BehaviorExpr::Vec3(v) => CExpr::Lit(Val::Vec3(*v)),
272        BehaviorExpr::Var(name) => CExpr::Var(vars.intern(name)),
273        BehaviorExpr::Local(name) => names.local(name).map_or(CExpr::Never, CExpr::Local),
274        BehaviorExpr::Bind(name) => names.binding(name).map_or(CExpr::Never, CExpr::Bind),
275        BehaviorExpr::Named(id) => id.map_or(CExpr::Never, CExpr::Named),
276        BehaviorExpr::SelfEntity => CExpr::SelfEntity,
277        BehaviorExpr::Dt => CExpr::Dt,
278        BehaviorExpr::Elapsed => CExpr::Elapsed,
279        BehaviorExpr::Position(e) => CExpr::Position(Box::new(compile_expr(e, names, vars))),
280        BehaviorExpr::Alive(e) => CExpr::Alive(Box::new(compile_expr(e, names, vars))),
281        BehaviorExpr::Normalize(e) => CExpr::Normalize(Box::new(compile_expr(e, names, vars))),
282        BehaviorExpr::Not(e) => CExpr::Not(Box::new(compile_expr(e, names, vars))),
283        BehaviorExpr::Distance(a, b) => {
284            let (a, b) = binary(a, b, names, vars);
285            CExpr::Distance(a, b)
286        }
287        BehaviorExpr::First(q) => names.query(q).map_or(CExpr::Never, CExpr::First),
288        BehaviorExpr::Count(q) => names.query(q).map_or(CExpr::Never, CExpr::Count),
289        BehaviorExpr::Add(a, b) => {
290            let (a, b) = binary(a, b, names, vars);
291            CExpr::Arith(Arith::Add, a, b)
292        }
293        BehaviorExpr::Sub(a, b) => {
294            let (a, b) = binary(a, b, names, vars);
295            CExpr::Arith(Arith::Sub, a, b)
296        }
297        BehaviorExpr::Mul(a, b) => {
298            let (a, b) = binary(a, b, names, vars);
299            CExpr::Arith(Arith::Mul, a, b)
300        }
301        BehaviorExpr::Div(a, b) => {
302            let (a, b) = binary(a, b, names, vars);
303            CExpr::Arith(Arith::Div, a, b)
304        }
305        BehaviorExpr::Eq(a, b) => {
306            let (a, b) = binary(a, b, names, vars);
307            CExpr::Compare(Cmp::Eq, a, b)
308        }
309        BehaviorExpr::Ne(a, b) => {
310            let (a, b) = binary(a, b, names, vars);
311            CExpr::Compare(Cmp::Ne, a, b)
312        }
313        BehaviorExpr::Lt(a, b) => {
314            let (a, b) = binary(a, b, names, vars);
315            CExpr::Compare(Cmp::Lt, a, b)
316        }
317        BehaviorExpr::Le(a, b) => {
318            let (a, b) = binary(a, b, names, vars);
319            CExpr::Compare(Cmp::Le, a, b)
320        }
321        BehaviorExpr::Gt(a, b) => {
322            let (a, b) = binary(a, b, names, vars);
323            CExpr::Compare(Cmp::Gt, a, b)
324        }
325        BehaviorExpr::Ge(a, b) => {
326            let (a, b) = binary(a, b, names, vars);
327            CExpr::Compare(Cmp::Ge, a, b)
328        }
329        BehaviorExpr::All(items) => {
330            CExpr::All(items.iter().map(|e| compile_expr(e, names, vars)).collect())
331        }
332        BehaviorExpr::Any(items) => {
333            CExpr::Any(items.iter().map(|e| compile_expr(e, names, vars)).collect())
334        }
335    }
336}