1use alloc::string::String;
10use alloc::vec::Vec;
11
12use crate::behavior::program::{CExpr, CNode, COp};
13use crate::behavior::value::{Arith, Cmp, Val};
14use crate::components::{PlayCue, StoryPlayback, Transform};
15use crate::ecs::{Entity, asset_id::AssetId};
16use crate::math::sqrt;
17
18pub struct View<'a> {
20 pub dt: f32,
22 pub elapsed: f32,
24 pub vars: &'a [Val],
26 pub locals: &'a [Val],
28 pub bindings: &'a mut [Option<Val>],
31 pub queries: &'a [Vec<Entity>],
33 pub by_name: &'a dyn Fn(AssetId) -> Option<Entity>,
35 pub transforms: &'a dyn Fn(Entity) -> Option<Transform>,
37 pub alive: &'a dyn Fn(Entity) -> bool,
39 pub self_entity: Option<Entity>,
41 pub trace: &'a mut Option<Vec<u32>>,
44}
45
46#[derive(Debug, Clone)]
48pub enum Effect {
49 SetVar {
51 slot: u16,
53 value: Val,
55 add: bool,
57 },
58 SetLocal {
60 slot: u16,
62 value: Val,
64 add: bool,
66 },
67 SetTransform {
69 entity: Entity,
71 transform: Transform,
73 },
74 Spawn(SpawnEffect),
76 Despawn(Entity),
78 Reparent {
80 child: Entity,
82 parent: Option<Entity>,
84 },
85 Visible(Entity, bool),
87 Sound(PlayCue),
89 Scene {
91 scene: AssetId,
93 transition: String,
95 },
96 Screen(AssetId),
98 Story(StoryPlayback),
100 Save,
102}
103
104#[derive(Debug, Clone)]
106pub struct SpawnEffect {
107 pub template: AssetId,
109 pub transform: Transform,
111 pub lifetime: Option<f32>,
113}
114
115fn eval(expr: &CExpr, view: &View<'_>) -> Option<Val> {
116 match expr {
117 CExpr::Lit(v) => Some(*v),
118 CExpr::Var(slot) => view.vars.get(*slot as usize).copied(),
119 CExpr::Local(slot) => view.locals.get(*slot as usize).copied(),
120 CExpr::Bind(slot) => view.bindings.get(*slot as usize).copied().flatten(),
121 CExpr::Named(id) => (view.by_name)(*id).map(Val::Entity),
122 CExpr::SelfEntity => view.self_entity.map(Val::Entity),
123 CExpr::Dt => Some(Val::Float(view.dt)),
124 CExpr::Elapsed => Some(Val::Float(view.elapsed)),
125 CExpr::Position(e) => {
126 let entity = eval(e, view)?.as_entity()?;
127 Some(Val::Vec3((view.transforms)(entity)?.position))
128 }
129 CExpr::Alive(e) => {
130 let alive = eval(e, view)
132 .and_then(Val::as_entity)
133 .is_some_and(view.alive);
134 Some(Val::Bool(alive))
135 }
136 CExpr::Distance(a, b) => {
137 let a = (view.transforms)(eval(a, view)?.as_entity()?)?.position;
138 let b = (view.transforms)(eval(b, view)?.as_entity()?)?.position;
139 let d = [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
140 Some(Val::Float(sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2])))
141 }
142 CExpr::First(slot) => view
143 .queries
144 .get(*slot as usize)?
145 .first()
146 .copied()
147 .map(Val::Entity),
148 CExpr::Count(slot) => Some(Val::Int(view.queries.get(*slot as usize)?.len() as i32)),
149 CExpr::Normalize(e) => {
150 let v = eval(e, view)?.as_vec3()?;
151 let len = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
152 Some(Val::Vec3(if len > f32::EPSILON {
153 [v[0] / len, v[1] / len, v[2] / len]
154 } else {
155 [0.0; 3]
156 }))
157 }
158 CExpr::Arith(op, a, b) => arith(*op, eval(a, view)?, eval(b, view)?),
159 CExpr::Compare(op, a, b) => compare(*op, eval(a, view)?, eval(b, view)?),
160 CExpr::Not(e) => Some(Val::Bool(!eval(e, view)?.as_bool()?)),
161 CExpr::All(items) => {
162 for item in items {
163 if !eval(item, view)?.as_bool()? {
164 return Some(Val::Bool(false));
165 }
166 }
167 Some(Val::Bool(true))
168 }
169 CExpr::Any(items) => {
170 for item in items {
171 if eval(item, view)?.as_bool()? {
172 return Some(Val::Bool(true));
173 }
174 }
175 Some(Val::Bool(false))
176 }
177 CExpr::Never => None,
178 }
179}
180
181fn arith(op: Arith, a: Val, b: Val) -> Option<Val> {
182 let scalar = |x: f32, y: f32| match op {
183 Arith::Add => x + y,
184 Arith::Sub => x - y,
185 Arith::Mul => x * y,
186 Arith::Div => {
189 if y.abs() > f32::EPSILON {
190 x / y
191 } else {
192 0.0
193 }
194 }
195 };
196 match (a, b) {
197 (Val::Int(x), Val::Int(y)) => Some(Val::Int(scalar(x as f32, y as f32) as i32)),
198 (Val::Vec3(x), Val::Vec3(y)) => Some(Val::Vec3([
199 scalar(x[0], y[0]),
200 scalar(x[1], y[1]),
201 scalar(x[2], y[2]),
202 ])),
203 (Val::Vec3(v), other) => {
204 let s = other.as_f32()?;
205 Some(Val::Vec3([
206 scalar(v[0], s),
207 scalar(v[1], s),
208 scalar(v[2], s),
209 ]))
210 }
211 (other, Val::Vec3(v)) => {
212 let s = other.as_f32()?;
213 Some(Val::Vec3([
214 scalar(s, v[0]),
215 scalar(s, v[1]),
216 scalar(s, v[2]),
217 ]))
218 }
219 (x, y) => Some(Val::Float(scalar(x.as_f32()?, y.as_f32()?))),
220 }
221}
222
223fn compare(op: Cmp, a: Val, b: Val) -> Option<Val> {
224 let equal = match (a, b) {
225 (Val::Entity(x), Val::Entity(y)) => x == y,
226 (Val::Bool(x), Val::Bool(y)) => x == y,
227 (x, y) => x.as_f32()? == y.as_f32()?,
228 };
229 Some(Val::Bool(match op {
230 Cmp::Eq => equal,
231 Cmp::Ne => !equal,
232 Cmp::Lt => a.as_f32()? < b.as_f32()?,
233 Cmp::Le => a.as_f32()? <= b.as_f32()?,
234 Cmp::Gt => a.as_f32()? > b.as_f32()?,
235 Cmp::Ge => a.as_f32()? >= b.as_f32()?,
236 }))
237}
238
239pub fn exec(nodes: &[CNode], view: &mut View<'_>, out: &mut Vec<Effect>) {
241 for node in nodes {
242 exec_node(node, view, out);
243 }
244}
245
246fn exec_node(node: &CNode, view: &mut View<'_>, out: &mut Vec<Effect>) {
247 if let Some(t) = view.trace.as_mut() {
248 t.push(node.id);
249 }
250 match &node.op {
251 COp::If {
252 cond,
253 then,
254 otherwise,
255 } => {
256 let Some(Val::Bool(pass)) = eval(cond, view) else {
257 return;
258 };
259 exec(if pass { then } else { otherwise }, view, out);
260 }
261 COp::ForEach { query, bind, body } => {
262 let Some(entities) = view.queries.get(*query as usize) else {
263 return;
264 };
265 for entity in entities.clone() {
268 set_binding(view, *bind, Some(Val::Entity(entity)));
269 exec(body, view, out);
270 }
271 }
272 COp::Let { bind, value } => {
273 let value = eval(value, view);
274 set_binding(view, *bind, value);
275 }
276 COp::SetVar { slot, value, add } => {
277 let Some(value) = eval(value, view) else {
278 return;
279 };
280 out.push(Effect::SetVar {
281 slot: *slot,
282 value,
283 add: *add,
284 });
285 }
286 COp::SetLocal { slot, value, add } => {
287 let Some(value) = eval(value, view) else {
288 return;
289 };
290 out.push(Effect::SetLocal {
291 slot: *slot,
292 value,
293 add: *add,
294 });
295 }
296 COp::SetTransform {
297 entity,
298 position,
299 rotation_deg,
300 scale,
301 } => {
302 let Some(entity) = eval(entity, view).and_then(Val::as_entity) else {
303 return;
304 };
305 let Some(mut transform) = (view.transforms)(entity) else {
306 return;
307 };
308 let field = |expr: &Option<CExpr>, into: &mut [f32; 3]| {
309 if let Some(expr) = expr
310 && let Some(v) = eval(expr, view).and_then(Val::as_vec3)
311 {
312 *into = v;
313 }
314 };
315 field(position, &mut transform.position);
316 field(rotation_deg, &mut transform.rotation_deg);
317 field(scale, &mut transform.scale);
318 out.push(Effect::SetTransform { entity, transform });
319 }
320 COp::Spawn {
321 template,
322 position,
323 rotation_deg,
324 scale,
325 lifetime,
326 bind,
327 } => {
328 out.push(Effect::Spawn(SpawnEffect {
329 template: *template,
330 transform: Transform {
331 position: *position,
332 rotation_deg: *rotation_deg,
333 scale: *scale,
334 },
335 lifetime: (*lifetime > 0.0).then_some(*lifetime),
336 }));
337 if let Some(bind) = bind {
340 set_binding(view, *bind, None);
341 }
342 }
343 COp::Despawn(target) => {
344 if let Some(entity) = eval(target, view).and_then(Val::as_entity) {
345 out.push(Effect::Despawn(entity));
346 }
347 }
348 COp::Reparent { child, parent } => {
349 let Some(child) = eval(child, view).and_then(Val::as_entity) else {
350 return;
351 };
352 let parent = match parent {
355 Some(expr) => match eval(expr, view).and_then(Val::as_entity) {
356 Some(entity) => Some(entity),
357 None => return,
358 },
359 None => None,
360 };
361 out.push(Effect::Reparent { child, parent });
362 }
363 COp::Visible(target, visible) => {
364 if let Some(entity) = eval(target, view).and_then(Val::as_entity) {
365 out.push(Effect::Visible(entity, *visible));
366 }
367 }
368 COp::Sound { clip, kind, volume } => out.push(Effect::Sound(PlayCue {
369 clip: *clip,
370 kind: *kind,
371 volume: *volume,
372 priority: 0,
373 })),
374 COp::Scene { scene, transition } => out.push(Effect::Scene {
375 scene: *scene,
376 transition: transition.clone(),
377 }),
378 COp::Screen(screen) => out.push(Effect::Screen(*screen)),
379 COp::Story(playback) => out.push(Effect::Story(*playback)),
380 COp::Save => out.push(Effect::Save),
381 COp::Never => {}
382 }
383}
384
385fn set_binding(view: &mut View<'_>, slot: u16, value: Option<Val>) {
386 if let Some(slot) = view.bindings.get_mut(slot as usize) {
387 *slot = value;
388 }
389}