concinnity_core/behavior/program.rs
1// The compiled form of a behavior body: every authored name resolved to a dense
2// slot, so evaluating the body touches no strings.
3
4use alloc::boxed::Box;
5use alloc::string::{String, ToString};
6use alloc::vec::Vec;
7
8use crate::behavior::value::{Arith, Cmp, Val};
9use crate::components::{Behavior, CueKind, StoryPlayback};
10use crate::ecs::{AudioClipHandle, TracePath, asset_id::AssetId};
11
12/// A slot-resolved expression.
13#[derive(Debug)]
14pub enum CExpr {
15 /// A constant.
16 Lit(Val),
17 /// A world variable, by slot.
18 Var(u16),
19 /// One of the behavior's locals, by slot.
20 Local(u16),
21 /// A binding introduced by `let` or `for_each`, by slot.
22 Bind(u16),
23 /// The entity a name resolves to.
24 Named(AssetId),
25 /// The entity this run is scoped to.
26 SelfEntity,
27 /// Seconds this tick advances.
28 Dt,
29 /// Seconds of simulated time so far.
30 Elapsed,
31 /// An entity's world position.
32 Position(Box<CExpr>),
33 /// Distance between two entities.
34 Distance(Box<CExpr>, Box<CExpr>),
35 /// Whether an entity still exists.
36 Alive(Box<CExpr>),
37 /// The first entity a declared query selects, by slot.
38 First(u16),
39 /// How many entities a declared query selects, by slot.
40 Count(u16),
41 /// Two numbers combined.
42 Arith(Arith, Box<CExpr>, Box<CExpr>),
43 /// A vector scaled to unit length.
44 Normalize(Box<CExpr>),
45 /// Two values compared.
46 Compare(Cmp, Box<CExpr>, Box<CExpr>),
47 /// True when every operand is true.
48 All(Vec<CExpr>),
49 /// True when any operand is true.
50 Any(Vec<CExpr>),
51 /// Logical negation.
52 Not(Box<CExpr>),
53 /// An expression the checker should have rejected. Evaluates to nothing.
54 Never,
55}
56
57/// A slot-resolved node with its compile-assigned identity: `id` is the node's
58/// pre-order position across the whole body, indexing the program's `paths`
59/// table so execution tracing can address the node the way the world checker's
60/// faults do.
61#[derive(Debug)]
62pub struct CNode {
63 /// Pre-order position across the body, and index into [`Program::paths`].
64 pub id: u32,
65 /// What the node does.
66 pub op: COp,
67}
68
69/// A slot-resolved node operation.
70#[derive(Debug)]
71pub enum COp {
72 /// Run one branch or the other.
73 If {
74 /// The tested condition.
75 cond: CExpr,
76 /// Branch taken when the condition holds.
77 then: Vec<CNode>,
78 /// Branch taken otherwise.
79 otherwise: Vec<CNode>,
80 },
81 /// Run the body once per entity a declared query selects.
82 ForEach {
83 /// The query's slot.
84 query: u16,
85 /// Binding slot the iterated entity lands in.
86 bind: u16,
87 /// Nodes run per entity.
88 body: Vec<CNode>,
89 },
90 /// Introduce a binding for the rest of the enclosing list.
91 Let {
92 /// Binding slot to fill.
93 bind: u16,
94 /// Value the binding takes.
95 value: CExpr,
96 },
97 /// Write a world variable.
98 SetVar {
99 /// The variable's slot.
100 slot: u16,
101 /// Value to write.
102 value: CExpr,
103 /// Add to the current value rather than replacing it.
104 add: bool,
105 },
106 /// Write one of the behavior's locals.
107 SetLocal {
108 /// The local's slot.
109 slot: u16,
110 /// Value to write.
111 value: CExpr,
112 /// Add to the current value rather than replacing it.
113 add: bool,
114 },
115 /// Overwrite the named parts of an entity's transform.
116 SetTransform {
117 /// The entity to move.
118 entity: CExpr,
119 /// New position, when authored.
120 position: Option<CExpr>,
121 /// New rotation in degrees, when authored.
122 rotation_deg: Option<CExpr>,
123 /// New scale, when authored.
124 scale: Option<CExpr>,
125 },
126 /// Request a copy of a template.
127 Spawn {
128 /// The template to copy.
129 template: AssetId,
130 /// Where the copy starts.
131 position: [f32; 3],
132 /// The copy's starting rotation, in degrees.
133 rotation_deg: [f32; 3],
134 /// The copy's starting scale.
135 scale: [f32; 3],
136 /// Seconds the copy lives, or zero for no limit.
137 lifetime: f32,
138 /// Binding slot the copy would land in, once it exists.
139 bind: Option<u16>,
140 },
141 /// Request an entity's removal.
142 Despawn(CExpr),
143 /// Move an entity under another parent, or to the root.
144 Reparent {
145 /// The entity to move.
146 child: CExpr,
147 /// Its new parent, or `None` for the root.
148 parent: Option<CExpr>,
149 },
150 /// Show or hide an entity.
151 Visible(CExpr, bool),
152 /// Play an audio cue.
153 Sound {
154 /// The clip to play.
155 clip: AudioClipHandle,
156 /// How the cue is voiced.
157 kind: CueKind,
158 /// Playback volume.
159 volume: f32,
160 },
161 /// Request a scene change.
162 Scene {
163 /// The scene to load.
164 scene: AssetId,
165 /// The transition to play.
166 transition: String,
167 },
168 /// Request a screen change.
169 Screen(AssetId),
170 /// Drive story playback.
171 Story(StoryPlayback),
172 /// Persist the world's behavior state.
173 Save,
174 /// A node the checker should have rejected. Does nothing.
175 Never,
176}
177
178/// One compiled behavior.
179#[derive(Debug)]
180pub struct Program {
181 /// The authored definition this was compiled from.
182 pub def: Behavior,
183 /// Components an entity must carry for this behavior to run against it.
184 /// Empty runs the body once, world-scoped.
185 pub scope: Vec<u8>,
186 /// Starting value of each local, indexed by slot.
187 pub local_inits: Vec<Val>,
188 /// Component tags each declared query selects on, indexed by slot.
189 pub queries: Vec<Vec<u8>>,
190 /// The compiled body.
191 pub body: Vec<CNode>,
192 /// Each node's authored-tree path, indexed by [`CNode::id`].
193 pub paths: Vec<TracePath>,
194 /// How many binding slots a run of this body needs.
195 pub bindings: usize,
196}
197
198impl Program {
199 /// Whether the body runs per matching entity rather than once for the world.
200 pub fn is_scoped(&self) -> bool {
201 !self.scope.is_empty()
202 }
203}
204
205/// The world's variables, in slot order: shared across behaviors, so slots are
206/// assigned once across the whole set. A name the world's `Variables` asset
207/// declares carries that declaration's type and starting value; any other name a
208/// behavior mentions is an integer starting at zero.
209#[derive(Debug, Default)]
210pub struct VarTable {
211 names: Vec<String>,
212 inits: Vec<Val>,
213}
214
215impl VarTable {
216 /// Declare a variable with its authored type and starting value. A repeated
217 /// name keeps its first declaration; the world checker rejects duplicates.
218 pub fn declare(&mut self, name: &str, init: Val) {
219 if self.slot_of(name).is_some() {
220 return;
221 }
222 self.names.push(name.to_string());
223 self.inits.push(init);
224 }
225
226 // The slot for a name, assigning an undeclared integer if this is its first
227 // mention.
228 pub(crate) fn intern(&mut self, name: &str) -> u16 {
229 if let Some(i) = self.slot_of(name) {
230 return i;
231 }
232 self.declare(name, Val::Int(0));
233 (self.names.len() - 1) as u16
234 }
235
236 /// The slot a name was assigned, if it has one.
237 pub fn slot_of(&self, name: &str) -> Option<u16> {
238 self.names.iter().position(|n| n == name).map(|i| i as u16)
239 }
240
241 /// Every declared name, in slot order.
242 pub fn names(&self) -> &[String] {
243 &self.names
244 }
245
246 /// The starting values, in slot order.
247 pub fn initial(&self) -> Vec<Val> {
248 self.inits.clone()
249 }
250
251 /// The starting value a name was declared with, if it has a slot.
252 pub fn init_of(&self, name: &str) -> Option<Val> {
253 self.slot_of(name)
254 .and_then(|slot| self.inits.get(slot as usize))
255 .copied()
256 }
257}