Skip to main content

proof_engine/editor/
scripting.rs

1// scripting.rs — Event/hook scripting system for proof-engine editor
2// Provides a typed event bus, script hooks (pre/post render, on-update,
3// entity lifecycle), a simple expression evaluator, and a condition graph
4// for behavior authoring without a full scripting language.
5
6use std::collections::{HashMap, VecDeque};
7use std::fmt;
8
9// ─── Event types ─────────────────────────────────────────────────────────────
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum EventKind {
13    // Entity lifecycle
14    EntitySpawned,
15    EntityDespawned,
16    EntitySelected,
17    EntityDeselected,
18    EntityMoved,
19    EntityRotated,
20    EntityScaled,
21    EntityReparented,
22    EntityRenamed,
23    EntityVisibilityChanged,
24
25    // Scene lifecycle
26    SceneOpened,
27    SceneClosed,
28    SceneSaved,
29    SceneLoaded,
30    SceneModified,
31
32    // Playback
33    PlaybackStarted,
34    PlaybackStopped,
35    PlaybackPaused,
36    PlaybackResumed,
37    FrameAdvanced,
38    TimelineKeyframeAdded,
39    TimelineKeyframeRemoved,
40    TimelineKeyframeMoved,
41    AnimClipChanged,
42
43    // Editor actions
44    UndoPerformed,
45    RedoPerformed,
46    SelectionChanged,
47    GizmoModeChanged,
48    CameraSnapped,
49    PanelFocused,
50    ShaderCompiled,
51    AssetImported,
52    AssetDeleted,
53    AssetRenamed,
54
55    // Kit events
56    KitParamChanged,
57    SdfGraphModified,
58    MaterialPainted,
59    BoneAdded,
60    BoneRemoved,
61    IkChainSolved,
62
63    // Physics
64    CollisionEntered,
65    CollisionExited,
66    TriggerEntered,
67    TriggerExited,
68    ConstraintBroken,
69
70    // Custom
71    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// ─── Event payload ────────────────────────────────────────────────────────────
144
145#[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// ─── Script value ─────────────────────────────────────────────────────────────
216
217#[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// ─── Expression AST ──────────────────────────────────────────────────────────
337
338#[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
373// ─── Expression evaluator ────────────────────────────────────────────────────
374
375pub 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// ─── Condition node graph ─────────────────────────────────────────────────────
670
671#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
672pub struct CondNodeId(pub u32);
673
674#[derive(Debug, Clone)]
675pub enum CondNode {
676    /// Always true / false
677    Constant(bool),
678    /// Compare a context variable to a literal
679    Compare {
680        variable: String,
681        op: CompareOp,
682        value: ScriptValue,
683    },
684    /// Logic gate
685    Gate {
686        op: GateOp,
687        inputs: Vec<CondNodeId>,
688    },
689    /// Evaluate an expression
690    Expression(Expr),
691    /// Random trigger with probability [0,1]
692    Random(f64),
693    /// Time-based condition: true after elapsed > threshold
694    TimeThreshold { elapsed_var: String, threshold: f64 },
695    /// Count-based: true after event fired N times
696    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                // Deterministic hash-based random using time variable
766                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// ─── Script hook ─────────────────────────────────────────────────────────────
789
790#[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// ─── Script action ────────────────────────────────────────────────────────────
855
856#[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                // Simplified: just run sequentially (true parallel needs threading)
951                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
959// ─── Event bus ────────────────────────────────────────────────────────────────
960
961pub 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
1026// ─── Scripting manager ────────────────────────────────────────────────────────
1027
1028pub 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        // Process queued events
1097        let events = self.bus.drain();
1098        self.stats.events_processed += events.len() as u64;
1099
1100        for event in events {
1101            // Set event context vars
1102            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            // Collect matching hooks sorted by priority
1111            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        // Re-queue any events generated by hooks
1137        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// ─── Tests ───────────────────────────────────────────────────────────────────
1155
1156#[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}