1use std::collections::{HashMap, VecDeque};
7use std::fmt;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum EventKind {
13 EntitySpawned,
15 EntityDespawned,
16 EntitySelected,
17 EntityDeselected,
18 EntityMoved,
19 EntityRotated,
20 EntityScaled,
21 EntityReparented,
22 EntityRenamed,
23 EntityVisibilityChanged,
24
25 SceneOpened,
27 SceneClosed,
28 SceneSaved,
29 SceneLoaded,
30 SceneModified,
31
32 PlaybackStarted,
34 PlaybackStopped,
35 PlaybackPaused,
36 PlaybackResumed,
37 FrameAdvanced,
38 TimelineKeyframeAdded,
39 TimelineKeyframeRemoved,
40 TimelineKeyframeMoved,
41 AnimClipChanged,
42
43 UndoPerformed,
45 RedoPerformed,
46 SelectionChanged,
47 GizmoModeChanged,
48 CameraSnapped,
49 PanelFocused,
50 ShaderCompiled,
51 AssetImported,
52 AssetDeleted,
53 AssetRenamed,
54
55 KitParamChanged,
57 SdfGraphModified,
58 MaterialPainted,
59 BoneAdded,
60 BoneRemoved,
61 IkChainSolved,
62
63 CollisionEntered,
65 CollisionExited,
66 TriggerEntered,
67 TriggerExited,
68 ConstraintBroken,
69
70 Custom,
72}
73
74impl EventKind {
75 pub fn label(&self) -> &'static str {
76 match self {
77 Self::EntitySpawned => "Entity Spawned",
78 Self::EntityDespawned => "Entity Despawned",
79 Self::EntitySelected => "Entity Selected",
80 Self::EntityDeselected => "Entity Deselected",
81 Self::EntityMoved => "Entity Moved",
82 Self::EntityRotated => "Entity Rotated",
83 Self::EntityScaled => "Entity Scaled",
84 Self::EntityReparented => "Entity Reparented",
85 Self::EntityRenamed => "Entity Renamed",
86 Self::EntityVisibilityChanged => "Entity Visibility Changed",
87 Self::SceneOpened => "Scene Opened",
88 Self::SceneClosed => "Scene Closed",
89 Self::SceneSaved => "Scene Saved",
90 Self::SceneLoaded => "Scene Loaded",
91 Self::SceneModified => "Scene Modified",
92 Self::PlaybackStarted => "Playback Started",
93 Self::PlaybackStopped => "Playback Stopped",
94 Self::PlaybackPaused => "Playback Paused",
95 Self::PlaybackResumed => "Playback Resumed",
96 Self::FrameAdvanced => "Frame Advanced",
97 Self::TimelineKeyframeAdded => "Keyframe Added",
98 Self::TimelineKeyframeRemoved => "Keyframe Removed",
99 Self::TimelineKeyframeMoved => "Keyframe Moved",
100 Self::AnimClipChanged => "Anim Clip Changed",
101 Self::UndoPerformed => "Undo",
102 Self::RedoPerformed => "Redo",
103 Self::SelectionChanged => "Selection Changed",
104 Self::GizmoModeChanged => "Gizmo Mode Changed",
105 Self::CameraSnapped => "Camera Snapped",
106 Self::PanelFocused => "Panel Focused",
107 Self::ShaderCompiled => "Shader Compiled",
108 Self::AssetImported => "Asset Imported",
109 Self::AssetDeleted => "Asset Deleted",
110 Self::AssetRenamed => "Asset Renamed",
111 Self::KitParamChanged => "Kit Param Changed",
112 Self::SdfGraphModified => "SDF Graph Modified",
113 Self::MaterialPainted => "Material Painted",
114 Self::BoneAdded => "Bone Added",
115 Self::BoneRemoved => "Bone Removed",
116 Self::IkChainSolved => "IK Chain Solved",
117 Self::CollisionEntered => "Collision Entered",
118 Self::CollisionExited => "Collision Exited",
119 Self::TriggerEntered => "Trigger Entered",
120 Self::TriggerExited => "Trigger Exited",
121 Self::ConstraintBroken => "Constraint Broken",
122 Self::Custom => "Custom",
123 }
124 }
125
126 pub fn is_entity_event(&self) -> bool {
127 matches!(self,
128 Self::EntitySpawned | Self::EntityDespawned | Self::EntitySelected
129 | Self::EntityDeselected | Self::EntityMoved | Self::EntityRotated
130 | Self::EntityScaled | Self::EntityReparented | Self::EntityRenamed
131 | Self::EntityVisibilityChanged
132 )
133 }
134
135 pub fn is_playback_event(&self) -> bool {
136 matches!(self,
137 Self::PlaybackStarted | Self::PlaybackStopped | Self::PlaybackPaused
138 | Self::PlaybackResumed | Self::FrameAdvanced
139 )
140 }
141}
142
143#[derive(Debug, Clone)]
146pub enum EventPayload {
147 None,
148 EntityId(u32),
149 EntityPair { a: u32, b: u32 },
150 String(String),
151 Float(f32),
152 Vec3([f32; 3]),
153 Bool(bool),
154 Int(i64),
155 KitParam { name: String, old: f32, new: f32 },
156 AssetPath { old: Option<String>, new: String },
157 Selection { ids: Vec<u32>, count: usize },
158}
159
160impl EventPayload {
161 pub fn as_entity_id(&self) -> Option<u32> {
162 match self { Self::EntityId(id) => Some(*id), _ => None }
163 }
164
165 pub fn as_float(&self) -> Option<f32> {
166 match self { Self::Float(v) => Some(*v), _ => None }
167 }
168
169 pub fn as_string(&self) -> Option<&str> {
170 match self { Self::String(s) => Some(s), _ => None }
171 }
172}
173
174#[derive(Debug, Clone)]
175pub struct EditorEvent {
176 pub kind: EventKind,
177 pub payload: EventPayload,
178 pub timestamp: f64,
179 pub source: EventSource,
180 pub propagate: bool,
181}
182
183impl EditorEvent {
184 pub fn new(kind: EventKind) -> Self {
185 Self {
186 kind,
187 payload: EventPayload::None,
188 timestamp: 0.0,
189 source: EventSource::Editor,
190 propagate: true,
191 }
192 }
193
194 pub fn with_payload(mut self, payload: EventPayload) -> Self {
195 self.payload = payload;
196 self
197 }
198
199 pub fn with_source(mut self, source: EventSource) -> Self {
200 self.source = source;
201 self
202 }
203}
204
205#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206pub enum EventSource {
207 Editor,
208 Script,
209 Physics,
210 User,
211 System,
212 Plugin,
213}
214
215#[derive(Debug, Clone, PartialEq)]
218pub enum ScriptValue {
219 Null,
220 Bool(bool),
221 Int(i64),
222 Float(f64),
223 String(String),
224 Vec2([f64; 2]),
225 Vec3([f64; 3]),
226 Vec4([f64; 4]),
227 List(Vec<ScriptValue>),
228 Map(Vec<(String, ScriptValue)>),
229 EntityRef(u32),
230}
231
232impl ScriptValue {
233 pub fn as_float(&self) -> Option<f64> {
234 match self {
235 Self::Float(v) => Some(*v),
236 Self::Int(v) => Some(*v as f64),
237 Self::Bool(v) => Some(if *v { 1.0 } else { 0.0 }),
238 _ => None,
239 }
240 }
241
242 pub fn as_bool(&self) -> bool {
243 match self {
244 Self::Null => false,
245 Self::Bool(v) => *v,
246 Self::Int(v) => *v != 0,
247 Self::Float(v) => *v != 0.0,
248 Self::String(s) => !s.is_empty(),
249 _ => true,
250 }
251 }
252
253 pub fn type_name(&self) -> &'static str {
254 match self {
255 Self::Null => "null",
256 Self::Bool(_) => "bool",
257 Self::Int(_) => "int",
258 Self::Float(_) => "float",
259 Self::String(_) => "string",
260 Self::Vec2(_) => "vec2",
261 Self::Vec3(_) => "vec3",
262 Self::Vec4(_) => "vec4",
263 Self::List(_) => "list",
264 Self::Map(_) => "map",
265 Self::EntityRef(_) => "entity",
266 }
267 }
268
269 pub fn add(&self, other: &Self) -> Self {
270 match (self, other) {
271 (Self::Int(a), Self::Int(b)) => Self::Int(a + b),
272 (Self::Float(a), Self::Float(b)) => Self::Float(a + b),
273 (Self::Int(a), Self::Float(b)) => Self::Float(*a as f64 + b),
274 (Self::Float(a), Self::Int(b)) => Self::Float(a + *b as f64),
275 (Self::String(a), Self::String(b)) => Self::String(format!("{}{}", a, b)),
276 (Self::Vec3(a), Self::Vec3(b)) => Self::Vec3([a[0]+b[0], a[1]+b[1], a[2]+b[2]]),
277 _ => Self::Null,
278 }
279 }
280
281 pub fn mul(&self, other: &Self) -> Self {
282 match (self, other) {
283 (Self::Int(a), Self::Int(b)) => Self::Int(a * b),
284 (Self::Float(a), Self::Float(b)) => Self::Float(a * b),
285 (Self::Int(a), Self::Float(b)) => Self::Float(*a as f64 * b),
286 (Self::Float(a), Self::Int(b)) => Self::Float(a * *b as f64),
287 (Self::Vec3(a), Self::Float(b)) => Self::Vec3([a[0]*b, a[1]*b, a[2]*b]),
288 _ => Self::Null,
289 }
290 }
291
292 pub fn equal(&self, other: &Self) -> bool {
293 match (self, other) {
294 (Self::Null, Self::Null) => true,
295 (Self::Bool(a), Self::Bool(b)) => a == b,
296 (Self::Int(a), Self::Int(b)) => a == b,
297 (Self::Float(a), Self::Float(b)) => a == b,
298 (Self::String(a), Self::String(b)) => a == b,
299 _ => false,
300 }
301 }
302}
303
304impl fmt::Display for ScriptValue {
305 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306 match self {
307 Self::Null => write!(f, "null"),
308 Self::Bool(v) => write!(f, "{}", v),
309 Self::Int(v) => write!(f, "{}", v),
310 Self::Float(v) => write!(f, "{:.6}", v),
311 Self::String(s) => write!(f, "{}", s),
312 Self::Vec2(v) => write!(f, "({:.3}, {:.3})", v[0], v[1]),
313 Self::Vec3(v) => write!(f, "({:.3}, {:.3}, {:.3})", v[0], v[1], v[2]),
314 Self::Vec4(v) => write!(f, "({:.3}, {:.3}, {:.3}, {:.3})", v[0],v[1],v[2],v[3]),
315 Self::List(items) => {
316 write!(f, "[")?;
317 for (i, item) in items.iter().enumerate() {
318 if i > 0 { write!(f, ", ")?; }
319 write!(f, "{}", item)?;
320 }
321 write!(f, "]")
322 }
323 Self::Map(entries) => {
324 write!(f, "{{")?;
325 for (i, (k, v)) in entries.iter().enumerate() {
326 if i > 0 { write!(f, ", ")?; }
327 write!(f, "{}: {}", k, v)?;
328 }
329 write!(f, "}}")
330 }
331 Self::EntityRef(id) => write!(f, "Entity({})", id),
332 }
333 }
334}
335
336#[derive(Debug, Clone)]
339pub enum Expr {
340 Literal(ScriptValue),
341 Variable(String),
342 Not(Box<Expr>),
343 Negate(Box<Expr>),
344 BinOp { op: BinOp, lhs: Box<Expr>, rhs: Box<Expr> },
345 Call { name: String, args: Vec<Expr> },
346 Index { expr: Box<Expr>, index: Box<Expr> },
347 Field { expr: Box<Expr>, field: String },
348 If { cond: Box<Expr>, then: Box<Expr>, else_: Box<Expr> },
349}
350
351#[derive(Debug, Clone, Copy, PartialEq, Eq)]
352pub enum BinOp {
353 Add, Sub, Mul, Div, Mod,
354 Eq, Ne, Lt, Le, Gt, Ge,
355 And, Or,
356 BitAnd, BitOr, BitXor, Shl, Shr,
357}
358
359impl BinOp {
360 pub fn symbol(&self) -> &'static str {
361 match self {
362 Self::Add => "+", Self::Sub => "-", Self::Mul => "*",
363 Self::Div => "/", Self::Mod => "%",
364 Self::Eq => "==", Self::Ne => "!=",
365 Self::Lt => "<", Self::Le => "<=", Self::Gt => ">", Self::Ge => ">=",
366 Self::And => "&&", Self::Or => "||",
367 Self::BitAnd => "&", Self::BitOr => "|", Self::BitXor => "^",
368 Self::Shl => "<<", Self::Shr => ">>",
369 }
370 }
371}
372
373pub struct EvalContext {
376 vars: HashMap<String, ScriptValue>,
377 functions: HashMap<String, Box<dyn Fn(&[ScriptValue]) -> ScriptValue>>,
378}
379
380impl EvalContext {
381 pub fn new() -> Self {
382 let mut ctx = Self {
383 vars: HashMap::new(),
384 functions: HashMap::new(),
385 };
386 ctx.register_builtins();
387 ctx
388 }
389
390 fn register_builtins(&mut self) {
391 self.functions.insert("abs".into(), Box::new(|args| {
392 args.first().and_then(|v| v.as_float()).map(|f| ScriptValue::Float(f.abs())).unwrap_or(ScriptValue::Null)
393 }));
394 self.functions.insert("sqrt".into(), Box::new(|args| {
395 args.first().and_then(|v| v.as_float()).map(|f| ScriptValue::Float(f.sqrt())).unwrap_or(ScriptValue::Null)
396 }));
397 self.functions.insert("floor".into(), Box::new(|args| {
398 args.first().and_then(|v| v.as_float()).map(|f| ScriptValue::Float(f.floor())).unwrap_or(ScriptValue::Null)
399 }));
400 self.functions.insert("ceil".into(), Box::new(|args| {
401 args.first().and_then(|v| v.as_float()).map(|f| ScriptValue::Float(f.ceil())).unwrap_or(ScriptValue::Null)
402 }));
403 self.functions.insert("round".into(), Box::new(|args| {
404 args.first().and_then(|v| v.as_float()).map(|f| ScriptValue::Float(f.round())).unwrap_or(ScriptValue::Null)
405 }));
406 self.functions.insert("min".into(), Box::new(|args| {
407 if args.len() < 2 { return ScriptValue::Null; }
408 let a = match args[0].as_float() { Some(v) => v, None => return ScriptValue::Null };
409 let b = match args[1].as_float() { Some(v) => v, None => return ScriptValue::Null };
410 ScriptValue::Float(a.min(b))
411 }));
412 self.functions.insert("max".into(), Box::new(|args| {
413 if args.len() < 2 { return ScriptValue::Null; }
414 let a = match args[0].as_float() { Some(v) => v, None => return ScriptValue::Null };
415 let b = match args[1].as_float() { Some(v) => v, None => return ScriptValue::Null };
416 ScriptValue::Float(a.max(b))
417 }));
418 self.functions.insert("clamp".into(), Box::new(|args| {
419 if args.len() < 3 { return ScriptValue::Null; }
420 let v = match args[0].as_float() { Some(v) => v, None => return ScriptValue::Null };
421 let lo = match args[1].as_float() { Some(v) => v, None => return ScriptValue::Null };
422 let hi = match args[2].as_float() { Some(v) => v, None => return ScriptValue::Null };
423 ScriptValue::Float(v.clamp(lo, hi))
424 }));
425 self.functions.insert("lerp".into(), Box::new(|args| {
426 if args.len() < 3 { return ScriptValue::Null; }
427 let a = match args[0].as_float() { Some(v) => v, None => return ScriptValue::Null };
428 let b = match args[1].as_float() { Some(v) => v, None => return ScriptValue::Null };
429 let t = match args[2].as_float() { Some(v) => v, None => return ScriptValue::Null };
430 ScriptValue::Float(a + (b - a) * t)
431 }));
432 self.functions.insert("sin".into(), Box::new(|args| {
433 args.first().and_then(|v| v.as_float()).map(|f| ScriptValue::Float(f.sin())).unwrap_or(ScriptValue::Null)
434 }));
435 self.functions.insert("cos".into(), Box::new(|args| {
436 args.first().and_then(|v| v.as_float()).map(|f| ScriptValue::Float(f.cos())).unwrap_or(ScriptValue::Null)
437 }));
438 self.functions.insert("len".into(), Box::new(|args| {
439 match args.first() {
440 Some(ScriptValue::List(v)) => ScriptValue::Int(v.len() as i64),
441 Some(ScriptValue::String(s)) => ScriptValue::Int(s.len() as i64),
442 _ => ScriptValue::Null,
443 }
444 }));
445 self.functions.insert("str".into(), Box::new(|args| {
446 ScriptValue::String(args.first().map(|v| v.to_string()).unwrap_or_default())
447 }));
448 self.functions.insert("int".into(), Box::new(|args| {
449 match args.first() {
450 Some(v) => ScriptValue::Int(v.as_float().unwrap_or(0.0) as i64),
451 None => ScriptValue::Null,
452 }
453 }));
454 self.functions.insert("float".into(), Box::new(|args| {
455 match args.first() {
456 Some(v) => ScriptValue::Float(v.as_float().unwrap_or(0.0)),
457 None => ScriptValue::Null,
458 }
459 }));
460 self.functions.insert("bool".into(), Box::new(|args| {
461 ScriptValue::Bool(args.first().map(|v| v.as_bool()).unwrap_or(false))
462 }));
463 self.functions.insert("print".into(), Box::new(|args| {
464 let s: Vec<String> = args.iter().map(|v| v.to_string()).collect();
465 println!("[script] {}", s.join(" "));
466 ScriptValue::Null
467 }));
468 self.functions.insert("vec3".into(), Box::new(|args| {
469 let x = args.get(0).and_then(|v| v.as_float()).unwrap_or(0.0);
470 let y = args.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
471 let z = args.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
472 ScriptValue::Vec3([x, y, z])
473 }));
474 self.functions.insert("dot".into(), Box::new(|args| {
475 if let (Some(ScriptValue::Vec3(a)), Some(ScriptValue::Vec3(b))) =
476 (args.get(0), args.get(1))
477 {
478 ScriptValue::Float(a[0]*b[0] + a[1]*b[1] + a[2]*b[2])
479 } else {
480 ScriptValue::Null
481 }
482 }));
483 self.functions.insert("length".into(), Box::new(|args| {
484 if let Some(ScriptValue::Vec3(v)) = args.first() {
485 ScriptValue::Float((v[0]*v[0] + v[1]*v[1] + v[2]*v[2]).sqrt())
486 } else {
487 ScriptValue::Null
488 }
489 }));
490 self.functions.insert("normalize".into(), Box::new(|args| {
491 if let Some(ScriptValue::Vec3(v)) = args.first() {
492 let len = (v[0]*v[0] + v[1]*v[1] + v[2]*v[2]).sqrt();
493 if len > 1e-10 {
494 ScriptValue::Vec3([v[0]/len, v[1]/len, v[2]/len])
495 } else {
496 ScriptValue::Vec3([0.0, 1.0, 0.0])
497 }
498 } else {
499 ScriptValue::Null
500 }
501 }));
502 }
503
504 pub fn set_var(&mut self, name: &str, value: ScriptValue) {
505 self.vars.insert(name.to_string(), value);
506 }
507
508 pub fn get_var(&self, name: &str) -> &ScriptValue {
509 self.vars.get(name).unwrap_or(&ScriptValue::Null)
510 }
511
512 pub fn eval(&self, expr: &Expr) -> ScriptValue {
513 match expr {
514 Expr::Literal(v) => v.clone(),
515
516 Expr::Variable(name) => self.get_var(name).clone(),
517
518 Expr::Not(e) => ScriptValue::Bool(!self.eval(e).as_bool()),
519
520 Expr::Negate(e) => match self.eval(e) {
521 ScriptValue::Int(v) => ScriptValue::Int(-v),
522 ScriptValue::Float(v) => ScriptValue::Float(-v),
523 _ => ScriptValue::Null,
524 },
525
526 Expr::BinOp { op, lhs, rhs } => {
527 let l = self.eval(lhs);
528 let r = self.eval(rhs);
529 match op {
530 BinOp::Add => l.add(&r),
531 BinOp::Mul => l.mul(&r),
532 BinOp::Sub => {
533 match (&l, &r) {
534 (ScriptValue::Int(a), ScriptValue::Int(b)) => ScriptValue::Int(a - b),
535 _ => {
536 let a = l.as_float().unwrap_or(0.0);
537 let b = r.as_float().unwrap_or(0.0);
538 ScriptValue::Float(a - b)
539 }
540 }
541 }
542 BinOp::Div => {
543 let a = l.as_float().unwrap_or(0.0);
544 let b = r.as_float().unwrap_or(0.0);
545 if b.abs() < 1e-300 { ScriptValue::Null } else { ScriptValue::Float(a / b) }
546 }
547 BinOp::Mod => {
548 match (&l, &r) {
549 (ScriptValue::Int(a), ScriptValue::Int(b)) =>
550 if *b != 0 { ScriptValue::Int(a % b) } else { ScriptValue::Null },
551 _ => {
552 let a = l.as_float().unwrap_or(0.0);
553 let b = r.as_float().unwrap_or(0.0);
554 ScriptValue::Float(a % b)
555 }
556 }
557 }
558 BinOp::Eq => ScriptValue::Bool(l.equal(&r)),
559 BinOp::Ne => ScriptValue::Bool(!l.equal(&r)),
560 BinOp::Lt => ScriptValue::Bool(l.as_float().unwrap_or(0.0) < r.as_float().unwrap_or(0.0)),
561 BinOp::Le => ScriptValue::Bool(l.as_float().unwrap_or(0.0) <= r.as_float().unwrap_or(0.0)),
562 BinOp::Gt => ScriptValue::Bool(l.as_float().unwrap_or(0.0) > r.as_float().unwrap_or(0.0)),
563 BinOp::Ge => ScriptValue::Bool(l.as_float().unwrap_or(0.0) >= r.as_float().unwrap_or(0.0)),
564 BinOp::And => ScriptValue::Bool(l.as_bool() && r.as_bool()),
565 BinOp::Or => ScriptValue::Bool(l.as_bool() || r.as_bool()),
566 BinOp::BitAnd => {
567 match (&l, &r) {
568 (ScriptValue::Int(a), ScriptValue::Int(b)) => ScriptValue::Int(a & b),
569 _ => ScriptValue::Null,
570 }
571 }
572 BinOp::BitOr => {
573 match (&l, &r) {
574 (ScriptValue::Int(a), ScriptValue::Int(b)) => ScriptValue::Int(a | b),
575 _ => ScriptValue::Null,
576 }
577 }
578 BinOp::BitXor => {
579 match (&l, &r) {
580 (ScriptValue::Int(a), ScriptValue::Int(b)) => ScriptValue::Int(a ^ b),
581 _ => ScriptValue::Null,
582 }
583 }
584 BinOp::Shl => {
585 match (&l, &r) {
586 (ScriptValue::Int(a), ScriptValue::Int(b)) => {
587 let shift = (*b).clamp(0, 63) as u32;
588 ScriptValue::Int(a << shift)
589 }
590 _ => ScriptValue::Null,
591 }
592 }
593 BinOp::Shr => {
594 match (&l, &r) {
595 (ScriptValue::Int(a), ScriptValue::Int(b)) => {
596 let shift = (*b).clamp(0, 63) as u32;
597 ScriptValue::Int(a >> shift)
598 }
599 _ => ScriptValue::Null,
600 }
601 }
602 }
603 }
604
605 Expr::Call { name, args } => {
606 let arg_vals: Vec<ScriptValue> = args.iter().map(|a| self.eval(a)).collect();
607 if let Some(f) = self.functions.get(name) {
608 f(&arg_vals)
609 } else {
610 ScriptValue::Null
611 }
612 }
613
614 Expr::Index { expr, index } => {
615 let v = self.eval(expr);
616 let i = self.eval(index);
617 match (v, i) {
618 (ScriptValue::List(items), ScriptValue::Int(idx)) => {
619 items.get(idx as usize).cloned().unwrap_or(ScriptValue::Null)
620 }
621 (ScriptValue::Map(entries), ScriptValue::String(key)) => {
622 entries.iter().find(|(k, _)| k == &key)
623 .map(|(_, v)| v.clone())
624 .unwrap_or(ScriptValue::Null)
625 }
626 _ => ScriptValue::Null,
627 }
628 }
629
630 Expr::Field { expr, field } => {
631 match self.eval(expr) {
632 ScriptValue::Vec3(v) => match field.as_str() {
633 "x" => ScriptValue::Float(v[0]),
634 "y" => ScriptValue::Float(v[1]),
635 "z" => ScriptValue::Float(v[2]),
636 _ => ScriptValue::Null,
637 },
638 ScriptValue::Vec4(v) => match field.as_str() {
639 "x" => ScriptValue::Float(v[0]),
640 "y" => ScriptValue::Float(v[1]),
641 "z" => ScriptValue::Float(v[2]),
642 "w" => ScriptValue::Float(v[3]),
643 _ => ScriptValue::Null,
644 },
645 ScriptValue::Map(entries) => {
646 entries.iter().find(|(k, _)| k == field)
647 .map(|(_, v)| v.clone())
648 .unwrap_or(ScriptValue::Null)
649 }
650 _ => ScriptValue::Null,
651 }
652 }
653
654 Expr::If { cond, then, else_ } => {
655 if self.eval(cond).as_bool() {
656 self.eval(then)
657 } else {
658 self.eval(else_)
659 }
660 }
661 }
662 }
663}
664
665impl Default for EvalContext {
666 fn default() -> Self { Self::new() }
667}
668
669#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
672pub struct CondNodeId(pub u32);
673
674#[derive(Debug, Clone)]
675pub enum CondNode {
676 Constant(bool),
678 Compare {
680 variable: String,
681 op: CompareOp,
682 value: ScriptValue,
683 },
684 Gate {
686 op: GateOp,
687 inputs: Vec<CondNodeId>,
688 },
689 Expression(Expr),
691 Random(f64),
693 TimeThreshold { elapsed_var: String, threshold: f64 },
695 CountThreshold { count_var: String, threshold: u64 },
697}
698
699#[derive(Debug, Clone, Copy, PartialEq, Eq)]
700pub enum CompareOp { Eq, Ne, Lt, Le, Gt, Ge }
701
702#[derive(Debug, Clone, Copy, PartialEq, Eq)]
703pub enum GateOp { And, Or, Nand, Nor, Xor, Not }
704
705#[derive(Debug, Clone)]
706pub struct ConditionGraph {
707 nodes: HashMap<CondNodeId, CondNode>,
708 root: Option<CondNodeId>,
709 next_id: u32,
710}
711
712impl ConditionGraph {
713 pub fn new() -> Self {
714 Self { nodes: HashMap::new(), root: None, next_id: 1 }
715 }
716
717 pub fn add(&mut self, node: CondNode) -> CondNodeId {
718 let id = CondNodeId(self.next_id);
719 self.next_id += 1;
720 self.nodes.insert(id, node);
721 id
722 }
723
724 pub fn set_root(&mut self, id: CondNodeId) { self.root = Some(id); }
725
726 pub fn evaluate(&self, ctx: &EvalContext) -> bool {
727 if let Some(root) = self.root {
728 self.eval_node(root, ctx)
729 } else {
730 true
731 }
732 }
733
734 fn eval_node(&self, id: CondNodeId, ctx: &EvalContext) -> bool {
735 let node = match self.nodes.get(&id) {
736 Some(n) => n,
737 None => return false,
738 };
739 match node {
740 CondNode::Constant(v) => *v,
741 CondNode::Compare { variable, op, value } => {
742 let var = ctx.get_var(variable);
743 match op {
744 CompareOp::Eq => var.equal(value),
745 CompareOp::Ne => !var.equal(value),
746 CompareOp::Lt => var.as_float().unwrap_or(0.0) < value.as_float().unwrap_or(0.0),
747 CompareOp::Le => var.as_float().unwrap_or(0.0) <= value.as_float().unwrap_or(0.0),
748 CompareOp::Gt => var.as_float().unwrap_or(0.0) > value.as_float().unwrap_or(0.0),
749 CompareOp::Ge => var.as_float().unwrap_or(0.0) >= value.as_float().unwrap_or(0.0),
750 }
751 }
752 CondNode::Gate { op, inputs } => {
753 let vals: Vec<bool> = inputs.iter().map(|&i| self.eval_node(i, ctx)).collect();
754 match op {
755 GateOp::And => vals.iter().all(|&v| v),
756 GateOp::Or => vals.iter().any(|&v| v),
757 GateOp::Nand => !vals.iter().all(|&v| v),
758 GateOp::Nor => !vals.iter().any(|&v| v),
759 GateOp::Xor => vals.iter().filter(|&&v| v).count() % 2 == 1,
760 GateOp::Not => !vals.first().copied().unwrap_or(false),
761 }
762 }
763 CondNode::Expression(e) => ctx.eval(e).as_bool(),
764 CondNode::Random(prob) => {
765 let t = ctx.get_var("time").as_float().unwrap_or(0.0);
767 let h = (t * 2654435761.0) as u64;
768 let r = (h ^ (h >> 32)) as f64 / u64::MAX as f64;
769 r < *prob
770 }
771 CondNode::TimeThreshold { elapsed_var, threshold } => {
772 ctx.get_var(elapsed_var).as_float().unwrap_or(0.0) > *threshold
773 }
774 CondNode::CountThreshold { count_var, threshold } => {
775 let count = ctx.get_var(count_var).as_float().unwrap_or(0.0) as u64;
776 count >= *threshold
777 }
778 }
779 }
780
781 pub fn node_count(&self) -> usize { self.nodes.len() }
782}
783
784impl Default for ConditionGraph {
785 fn default() -> Self { Self::new() }
786}
787
788#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
791pub struct HookId(pub u32);
792
793#[derive(Debug, Clone, Copy, PartialEq, Eq)]
794pub enum HookTiming {
795 Before,
796 After,
797 Instead,
798}
799
800#[derive(Debug, Clone)]
801pub struct ScriptHook {
802 pub id: HookId,
803 pub name: String,
804 pub event: EventKind,
805 pub timing: HookTiming,
806 pub enabled: bool,
807 pub condition: Option<ConditionGraph>,
808 pub actions: Vec<ScriptAction>,
809 pub priority: i32,
810 pub run_in_editor: bool,
811 pub run_in_play: bool,
812 pub max_executions: Option<u64>,
813 pub execution_count: u64,
814}
815
816impl ScriptHook {
817 pub fn new(id: HookId, name: String, event: EventKind) -> Self {
818 Self {
819 id,
820 name,
821 event,
822 timing: HookTiming::After,
823 enabled: true,
824 condition: None,
825 actions: Vec::new(),
826 priority: 0,
827 run_in_editor: true,
828 run_in_play: true,
829 max_executions: None,
830 execution_count: 0,
831 }
832 }
833
834 pub fn can_execute(&self, is_playing: bool) -> bool {
835 if !self.enabled { return false; }
836 if is_playing && !self.run_in_play { return false; }
837 if !is_playing && !self.run_in_editor { return false; }
838 if let Some(max) = self.max_executions {
839 if self.execution_count >= max { return false; }
840 }
841 true
842 }
843
844 pub fn should_fire(&self, ctx: &EvalContext, is_playing: bool) -> bool {
845 if !self.can_execute(is_playing) { return false; }
846 if let Some(cond) = &self.condition {
847 cond.evaluate(ctx)
848 } else {
849 true
850 }
851 }
852}
853
854#[derive(Debug, Clone)]
857pub enum ScriptAction {
858 SetVar { name: String, expr: Expr },
859 FireEvent { kind: EventKind, payload_expr: Option<Expr> },
860 Print(Expr),
861 If { cond: Expr, then: Vec<ScriptAction>, else_: Vec<ScriptAction> },
862 Repeat { count: Expr, body: Vec<ScriptAction> },
863 While { cond: Expr, body: Vec<ScriptAction>, max_iters: u32 },
864 Break,
865 Continue,
866 Return(Option<Expr>),
867 CallHook(String),
868 LogInfo(Expr),
869 LogWarn(Expr),
870 LogError(Expr),
871 Sequence(Vec<ScriptAction>),
872 Parallel(Vec<ScriptAction>),
873}
874
875impl ScriptAction {
876 pub fn execute(&self, ctx: &mut EvalContext, out_events: &mut Vec<EditorEvent>) -> bool {
877 match self {
878 Self::SetVar { name, expr } => {
879 let v = ctx.eval(expr);
880 ctx.set_var(name, v);
881 true
882 }
883 Self::FireEvent { kind, payload_expr } => {
884 let mut evt = EditorEvent::new(*kind);
885 if let Some(e) = payload_expr {
886 let v = ctx.eval(e);
887 evt.payload = match v {
888 ScriptValue::EntityRef(id) => EventPayload::EntityId(id),
889 ScriptValue::Float(f) => EventPayload::Float(f as f32),
890 ScriptValue::String(s) => EventPayload::String(s),
891 ScriptValue::Bool(b) => EventPayload::Bool(b),
892 _ => EventPayload::None,
893 };
894 }
895 evt.source = EventSource::Script;
896 out_events.push(evt);
897 true
898 }
899 Self::Print(e) => {
900 println!("[script] {}", ctx.eval(e));
901 true
902 }
903 Self::LogInfo(e) => {
904 log::info!("[script] {}", ctx.eval(e));
905 true
906 }
907 Self::LogWarn(e) => {
908 log::warn!("[script] {}", ctx.eval(e));
909 true
910 }
911 Self::LogError(e) => {
912 log::error!("[script] {}", ctx.eval(e));
913 true
914 }
915 Self::If { cond, then, else_ } => {
916 let branch = if ctx.eval(cond).as_bool() { then } else { else_ };
917 for action in branch {
918 if !action.execute(ctx, out_events) { return false; }
919 }
920 true
921 }
922 Self::Repeat { count, body } => {
923 let n = ctx.eval(count).as_float().unwrap_or(0.0) as u32;
924 for i in 0..n {
925 ctx.set_var("_i", ScriptValue::Int(i as i64));
926 for action in body {
927 let cont = action.execute(ctx, out_events);
928 if !cont { break; }
929 }
930 }
931 true
932 }
933 Self::While { cond, body, max_iters } => {
934 let mut iters = 0u32;
935 while ctx.eval(cond).as_bool() && iters < *max_iters {
936 for action in body {
937 if !action.execute(ctx, out_events) { break; }
938 }
939 iters += 1;
940 }
941 true
942 }
943 Self::Sequence(actions) => {
944 for a in actions {
945 if !a.execute(ctx, out_events) { return false; }
946 }
947 true
948 }
949 Self::Parallel(actions) => {
950 for a in actions { a.execute(ctx, out_events); }
952 true
953 }
954 Self::Break | Self::Continue | Self::Return(_) | Self::CallHook(_) => true,
955 }
956 }
957}
958
959pub struct EventBus {
962 queue: VecDeque<EditorEvent>,
963 history: Vec<EditorEvent>,
964 max_history: usize,
965 pub event_count: u64,
966 pub dropped_count: u64,
967 max_queue: usize,
968}
969
970impl EventBus {
971 pub fn new() -> Self {
972 Self {
973 queue: VecDeque::new(),
974 history: Vec::new(),
975 max_history: 512,
976 event_count: 0,
977 dropped_count: 0,
978 max_queue: 4096,
979 }
980 }
981
982 pub fn push(&mut self, event: EditorEvent) {
983 if self.queue.len() >= self.max_queue {
984 self.dropped_count += 1;
985 return;
986 }
987 self.queue.push_back(event);
988 self.event_count += 1;
989 }
990
991 pub fn pop(&mut self) -> Option<EditorEvent> {
992 let evt = self.queue.pop_front()?;
993 if self.history.len() >= self.max_history {
994 self.history.remove(0);
995 }
996 self.history.push(evt.clone());
997 Some(evt)
998 }
999
1000 pub fn drain(&mut self) -> Vec<EditorEvent> {
1001 let mut out = Vec::new();
1002 while let Some(e) = self.pop() {
1003 out.push(e);
1004 }
1005 out
1006 }
1007
1008 pub fn peek(&self) -> Option<&EditorEvent> {
1009 self.queue.front()
1010 }
1011
1012 pub fn is_empty(&self) -> bool { self.queue.is_empty() }
1013 pub fn len(&self) -> usize { self.queue.len() }
1014
1015 pub fn history(&self) -> &[EditorEvent] { &self.history }
1016
1017 pub fn last_of_kind(&self, kind: EventKind) -> Option<&EditorEvent> {
1018 self.history.iter().rev().find(|e| e.kind == kind)
1019 }
1020}
1021
1022impl Default for EventBus {
1023 fn default() -> Self { Self::new() }
1024}
1025
1026pub struct ScriptingManager {
1029 pub hooks: HashMap<HookId, ScriptHook>,
1030 next_hook_id: u32,
1031 pub context: EvalContext,
1032 pub bus: EventBus,
1033 pub is_playing: bool,
1034 pending_events: Vec<EditorEvent>,
1035 pub stats: ScriptingStats,
1036}
1037
1038#[derive(Debug, Default, Clone)]
1039pub struct ScriptingStats {
1040 pub hooks_fired: u64,
1041 pub events_processed: u64,
1042 pub events_dropped: u64,
1043 pub eval_errors: u64,
1044 pub total_actions: u64,
1045}
1046
1047impl ScriptingManager {
1048 pub fn new() -> Self {
1049 let mut ctx = EvalContext::new();
1050 ctx.set_var("time", ScriptValue::Float(0.0));
1051 ctx.set_var("frame", ScriptValue::Int(0));
1052 ctx.set_var("is_playing", ScriptValue::Bool(false));
1053 ctx.set_var("dt", ScriptValue::Float(0.0));
1054 ctx.set_var("selection_count", ScriptValue::Int(0));
1055 Self {
1056 hooks: HashMap::new(),
1057 next_hook_id: 1,
1058 context: ctx,
1059 bus: EventBus::new(),
1060 is_playing: false,
1061 pending_events: Vec::new(),
1062 stats: ScriptingStats::default(),
1063 }
1064 }
1065
1066 pub fn register_hook(&mut self, hook: ScriptHook) -> HookId {
1067 let id = hook.id;
1068 self.hooks.insert(id, hook);
1069 id
1070 }
1071
1072 pub fn new_hook(&mut self, name: &str, event: EventKind) -> HookId {
1073 let id = HookId(self.next_hook_id);
1074 self.next_hook_id += 1;
1075 let hook = ScriptHook::new(id, name.to_string(), event);
1076 self.hooks.insert(id, hook);
1077 id
1078 }
1079
1080 pub fn remove_hook(&mut self, id: HookId) {
1081 self.hooks.remove(&id);
1082 }
1083
1084 pub fn fire_event(&mut self, event: EditorEvent) {
1085 self.bus.push(event);
1086 }
1087
1088 pub fn update(&mut self, dt: f32) {
1089 self.context.set_var("dt", ScriptValue::Float(dt as f64));
1090 let time = self.context.get_var("time").as_float().unwrap_or(0.0) + dt as f64;
1091 self.context.set_var("time", ScriptValue::Float(time));
1092 let frame = self.context.get_var("frame").as_float().unwrap_or(0.0) as i64 + 1;
1093 self.context.set_var("frame", ScriptValue::Int(frame));
1094 self.context.set_var("is_playing", ScriptValue::Bool(self.is_playing));
1095
1096 let events = self.bus.drain();
1098 self.stats.events_processed += events.len() as u64;
1099
1100 for event in events {
1101 self.context.set_var("event_kind", ScriptValue::String(event.kind.label().to_string()));
1103 if let Some(id) = event.payload.as_entity_id() {
1104 self.context.set_var("event_entity", ScriptValue::EntityRef(id));
1105 }
1106 if let Some(f) = event.payload.as_float() {
1107 self.context.set_var("event_value", ScriptValue::Float(f as f64));
1108 }
1109
1110 let mut matching: Vec<HookId> = self.hooks.iter()
1112 .filter(|(_, h)| h.event == event.kind)
1113 .map(|(&id, _)| id)
1114 .collect();
1115 matching.sort_by_key(|id| self.hooks[id].priority);
1116
1117 for hook_id in matching {
1118 let should_fire = {
1119 let hook = &self.hooks[&hook_id];
1120 hook.should_fire(&self.context, self.is_playing)
1121 };
1122 if should_fire {
1123 let actions: Vec<ScriptAction> = self.hooks[&hook_id].actions.clone();
1124 for action in &actions {
1125 action.execute(&mut self.context, &mut self.pending_events);
1126 self.stats.total_actions += 1;
1127 }
1128 if let Some(hook) = self.hooks.get_mut(&hook_id) {
1129 hook.execution_count += 1;
1130 }
1131 self.stats.hooks_fired += 1;
1132 }
1133 }
1134 }
1135
1136 for evt in self.pending_events.drain(..) {
1138 self.bus.push(evt);
1139 }
1140
1141 self.stats.events_dropped = self.bus.dropped_count;
1142 }
1143
1144 pub fn hook_count(&self) -> usize { self.hooks.len() }
1145 pub fn enabled_hook_count(&self) -> usize {
1146 self.hooks.values().filter(|h| h.enabled).count()
1147 }
1148}
1149
1150impl Default for ScriptingManager {
1151 fn default() -> Self { Self::new() }
1152}
1153
1154#[cfg(test)]
1157mod tests {
1158 use super::*;
1159
1160 #[test]
1161 fn eval_arithmetic() {
1162 let ctx = EvalContext::new();
1163 let expr = Expr::BinOp {
1164 op: BinOp::Add,
1165 lhs: Box::new(Expr::Literal(ScriptValue::Float(3.0))),
1166 rhs: Box::new(Expr::Literal(ScriptValue::Float(4.0))),
1167 };
1168 let result = ctx.eval(&expr);
1169 assert_eq!(result.as_float(), Some(7.0));
1170 }
1171
1172 #[test]
1173 fn eval_builtin_clamp() {
1174 let ctx = EvalContext::new();
1175 let expr = Expr::Call {
1176 name: "clamp".into(),
1177 args: vec![
1178 Expr::Literal(ScriptValue::Float(1.5)),
1179 Expr::Literal(ScriptValue::Float(0.0)),
1180 Expr::Literal(ScriptValue::Float(1.0)),
1181 ],
1182 };
1183 let result = ctx.eval(&expr);
1184 assert!((result.as_float().unwrap() - 1.0).abs() < 1e-9);
1185 }
1186
1187 #[test]
1188 fn condition_graph_and() {
1189 let mut g = ConditionGraph::new();
1190 let t = g.add(CondNode::Constant(true));
1191 let f = g.add(CondNode::Constant(false));
1192 let gate = g.add(CondNode::Gate { op: GateOp::And, inputs: vec![t, f] });
1193 g.set_root(gate);
1194 let ctx = EvalContext::new();
1195 assert!(!g.evaluate(&ctx));
1196 }
1197
1198 #[test]
1199 fn condition_graph_or() {
1200 let mut g = ConditionGraph::new();
1201 let t = g.add(CondNode::Constant(true));
1202 let f = g.add(CondNode::Constant(false));
1203 let gate = g.add(CondNode::Gate { op: GateOp::Or, inputs: vec![t, f] });
1204 g.set_root(gate);
1205 let ctx = EvalContext::new();
1206 assert!(g.evaluate(&ctx));
1207 }
1208
1209 #[test]
1210 fn event_bus_fifo() {
1211 let mut bus = EventBus::new();
1212 bus.push(EditorEvent::new(EventKind::EntitySpawned));
1213 bus.push(EditorEvent::new(EventKind::EntityDespawned));
1214 let first = bus.pop().unwrap();
1215 assert_eq!(first.kind, EventKind::EntitySpawned);
1216 }
1217
1218 #[test]
1219 fn script_set_var() {
1220 let mut ctx = EvalContext::new();
1221 let mut events = Vec::new();
1222 let action = ScriptAction::SetVar {
1223 name: "x".into(),
1224 expr: Expr::Literal(ScriptValue::Float(42.0)),
1225 };
1226 action.execute(&mut ctx, &mut events);
1227 assert_eq!(ctx.get_var("x").as_float(), Some(42.0));
1228 }
1229
1230 #[test]
1231 fn scripting_manager_update() {
1232 let mut mgr = ScriptingManager::new();
1233 mgr.fire_event(EditorEvent::new(EventKind::EntitySpawned));
1234 mgr.update(0.016);
1235 assert!(mgr.stats.events_processed > 0);
1236 }
1237
1238 #[test]
1239 fn eval_vec3_field() {
1240 let ctx = EvalContext::new();
1241 let expr = Expr::Field {
1242 expr: Box::new(Expr::Literal(ScriptValue::Vec3([1.0, 2.0, 3.0]))),
1243 field: "y".into(),
1244 };
1245 let result = ctx.eval(&expr);
1246 assert!((result.as_float().unwrap() - 2.0).abs() < 1e-9);
1247 }
1248
1249 #[test]
1250 fn interpolation_args() {
1251 use super::super::localization::TranslationArgs;
1252 let args = TranslationArgs::new().set("name", "World");
1253 assert_eq!(args.interpolate("Hello, {name}!"), "Hello, World!");
1254 }
1255}