Skip to main content

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}