Skip to main content

gizmo_scripting/
engine.rs

1use gizmo_core::input::Input;
2use gizmo_core::World;
3use mlua::prelude::*;
4use mlua::RegistryKey;
5use std::collections::HashMap;
6use std::sync::Arc;
7
8use crate::api_ai;
9use crate::api_audio;
10use crate::api_entity;
11use crate::api_input;
12use crate::api_physics;
13use crate::api_scene;
14use crate::api_time;
15use crate::api_vehicle;
16use crate::commands::{CommandQueue, ScriptCommand};
17
18/// Lua Scripting Motoru — Genişletilmiş API ile oyun mantığını yönetir
19pub struct ScriptEngine {
20    lua: Lua,
21    loaded_scripts: HashMap<String, (String, RegistryKey)>,
22    command_queue: Arc<CommandQueue>,
23    elapsed_time: f32,
24    pub log_queue: Arc<std::sync::Mutex<Vec<(String, String)>>>, // (Level, Message)
25}
26
27unsafe impl Send for ScriptEngine {}
28unsafe impl Sync for ScriptEngine {}
29
30/// ECS Componenti: Varlığın üzerine hangi Lua script'inin takılı olduğunu tutar
31#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
32pub struct Script {
33    pub file_path: String,
34    #[serde(default, skip)]
35    pub initialized: bool, // on_init çağrıldı mı?
36}
37
38impl Script {
39    pub fn new(path: &str) -> Self {
40        Self {
41            file_path: path.to_string(),
42            initialized: false,
43        }
44    }
45}
46
47/// Lua'ya geçirilecek entity verisi (geriye dönük uyumluluk için)
48#[derive(Clone, Debug)]
49pub struct ScriptContext {
50    pub entity_id: u32,
51    pub dt: f32,
52    pub position: [f32; 3],
53    pub velocity: [f32; 3],
54    pub key_w: bool,
55    pub key_a: bool,
56    pub key_s: bool,
57    pub key_d: bool,
58    pub key_space: bool,
59    pub key_up: bool,
60    pub key_down: bool,
61    pub key_left: bool,
62    pub key_right: bool,
63}
64
65/// Lua'dan dönen değişiklikler (geriye dönük uyumluluk)
66#[derive(Clone, Debug, Default)]
67pub struct ScriptResult {
68    pub new_position: Option<[f32; 3]>,
69    pub new_velocity: Option<[f32; 3]>,
70}
71
72impl ScriptEngine {
73    pub fn new() -> Result<Self, LuaError> {
74        let lua = Lua::new();
75        let command_queue = Arc::new(CommandQueue::new());
76        let log_queue = Arc::new(std::sync::Mutex::new(Vec::new()));
77
78        // === SANDBOX: Tehlikeli modülleri kapat ===
79        lua.globals().set("os", LuaNil)?;
80        lua.globals().set("io", LuaNil)?;
81        lua.globals().set("loadfile", LuaNil)?;
82        lua.globals().set("dofile", LuaNil)?;
83        lua.globals().set("require", LuaNil)?;
84        lua.globals().set("package", LuaNil)?;
85        lua.globals().set("debug", LuaNil)?;
86        lua.globals().set("loadstring", LuaNil)?;
87        lua.globals().set("load", LuaNil)?;
88
89        // === TEMEL PRINT FONKSİYONU ===
90        let lq_clone1 = log_queue.clone();
91        lua.globals().set(
92            "print_engine",
93            lua.create_function(move |_, msg: String| {
94                if let Ok(mut q) = lq_clone1.lock() {
95                    q.push(("info".to_string(), msg));
96                }
97                Ok(())
98            })?,
99        )?;
100
101        // Orijinal print'i de engine çıktısına yönlendir
102        let lq_clone2 = log_queue.clone();
103        lua.globals().set(
104            "print",
105            lua.create_function(move |_, values: LuaMultiValue| {
106                let parts: Vec<String> = values
107                    .iter()
108                    .map(|v| {
109                        if let mlua::Value::String(s) = v {
110                            s.to_str().unwrap_or("").to_string()
111                        } else if let mlua::Value::Number(n) = v {
112                            n.to_string()
113                        } else if let mlua::Value::Integer(i) = v {
114                            i.to_string()
115                        } else if let mlua::Value::Boolean(b) = v {
116                            b.to_string()
117                        } else {
118                            format!("{:?}", v)
119                        }
120                    })
121                    .collect();
122                if let Ok(mut q) = lq_clone2.lock() {
123                    q.push(("info".to_string(), parts.join("\t")));
124                }
125                Ok(())
126            })?,
127        )?;
128
129        // === VEC3 YARDIMCI FONKSİYONLARI ===
130        lua.load(
131            r#"
132            function vec3(x, y, z)
133                return { x = x or 0, y = y or 0, z = z or 0 }
134            end
135            
136            function vec3_add(a, b)
137                return vec3(a.x + b.x, a.y + b.y, a.z + b.z)
138            end
139            
140            function vec3_sub(a, b)
141                return vec3(a.x - b.x, a.y - b.y, a.z - b.z)
142            end
143            
144            function vec3_scale(v, s)
145                return vec3(v.x * s, v.y * s, v.z * s)
146            end
147            
148            function vec3_length(v)
149                return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
150            end
151            
152            function vec3_normalize(v)
153                local len = vec3_length(v)
154                if len > 0.0001 then
155                    return vec3(v.x / len, v.y / len, v.z / len)
156                end
157                return vec3(0, 0, 0)
158            end
159            
160            function vec3_dot(a, b)
161                return a.x * b.x + a.y * b.y + a.z * b.z
162            end
163            
164            function vec3_cross(a, b)
165                return vec3(
166                    a.y * b.z - a.z * b.y,
167                    a.z * b.x - a.x * b.z,
168                    a.x * b.y - a.y * b.x
169                )
170            end
171            
172            function vec3_lerp(a, b, t)
173                return vec3(
174                    a.x + (b.x - a.x) * t,
175                    a.y + (b.y - a.y) * t,
176                    a.z + (b.z - a.z) * t
177                )
178            end
179            
180            function vec3_distance(a, b)
181                return vec3_length(vec3_sub(a, b))
182            end
183            
184            -- Clamp utility
185            function clamp(value, min, max)
186                return math.max(min, math.min(max, value))
187            end
188            
189            -- Lerp utility
190            function lerp(a, b, t)
191                return a + (b - a) * t
192            end
193        "#,
194        )
195        .exec()?;
196
197        // === API MODÜLLERİNİ KAYDET ===
198        api_entity::register_entity_api(&lua, command_queue.clone())?;
199        api_input::register_input_api(&lua)?;
200        api_physics::register_physics_api(&lua, command_queue.clone())?;
201        api_scene::register_scene_api(&lua, command_queue.clone())?;
202        api_audio::register_audio_api(&lua, command_queue.clone())?;
203        api_time::register_time_api(&lua)?;
204        api_vehicle::register_vehicle_api(&lua, command_queue.clone())?;
205        api_ai::register_ai_api(&lua, command_queue.clone())?;
206
207        Ok(Self {
208            lua,
209            loaded_scripts: HashMap::new(),
210            command_queue,
211            elapsed_time: 0.0,
212            log_queue,
213        })
214    }
215
216    pub fn load_script(&mut self, path: &str) -> Result<(), String> {
217        let content = std::fs::read_to_string(path)
218            .map_err(|e| format!("Script okunamadı {}: {}", path, e))?;
219
220        let env = self.lua.create_table().map_err(|e| e.to_string())?;
221
222        // Link to _G via metatable
223        let meta = self.lua.create_table().map_err(|e| e.to_string())?;
224        meta.set("__index", self.lua.globals()).unwrap();
225        env.set_metatable(Some(meta));
226
227        // Script'i İzole env içinde çalıştır
228        self.lua
229            .load(&content)
230            .set_environment(env.clone())
231            .exec()
232            .map_err(|e| format!("Lua hata {}: {}", path, e))?;
233
234        let key = self
235            .lua
236            .create_registry_value(env)
237            .map_err(|e| e.to_string())?;
238
239        // Replace existing key if it exists to free old memory
240        if let Some((_, old_key)) = self.loaded_scripts.insert(path.to_string(), (content, key)) {
241            let _ = self.lua.remove_registry_value(old_key);
242        }
243
244        tracing::info!("🔧 ScriptEngine: Yüklendi ve İzole Edildi → {}", path);
245        Ok(())
246    }
247
248    /// Her frame çağrılan güncelleme — World verilerini Lua'ya aktarır, scriptleri çalıştırır
249    pub fn update(&mut self, world: &World, input: &Input, dt: f32) -> Result<(), String> {
250        self.elapsed_time += dt;
251
252        // 1. World verilerini Lua'ya aktar (read snapshot)
253        api_entity::update_entity_read_api(&self.lua, world)
254            .map_err(|e| format!("Entity API güncelleme hatası: {}", e))?;
255        api_input::update_input_api(&self.lua, input)
256            .map_err(|e| format!("Input API güncelleme hatası: {}", e))?;
257        api_scene::update_scene_api(&self.lua, world)
258            .map_err(|e| format!("Scene API güncelleme hatası: {}", e))?;
259        api_time::update_time_api(&self.lua, dt, self.elapsed_time, 1.0 / dt.max(0.0001))
260            .map_err(|e| format!("Time API güncelleme hatası: {}", e))?;
261        api_physics::update_physics_api(&self.lua, world)
262            .map_err(|e| format!("Physics API güncelleme hatası: {}", e))?;
263
264        // 2. on_update callback'ini çağır (varsa)
265        let globals = self.lua.globals();
266        if let Ok(func) = globals.get::<_, LuaFunction>("on_update") {
267            let ctx_table = self.lua.create_table().map_err(|e| e.to_string())?;
268            ctx_table.set("dt", dt).map_err(|e| e.to_string())?;
269            ctx_table
270                .set("elapsed", self.elapsed_time)
271                .map_err(|e| e.to_string())?;
272
273            func.call::<_, ()>(ctx_table)
274                .map_err(|e| format!("Lua on_update hatası: {}", e))?;
275        }
276
277        Ok(())
278    }
279
280    /// Per-entity script güncelleme — Script component'i olan entity'ler için izole ortamda çalıştırır
281    pub fn update_entity(
282        &mut self,
283        entity_id: u32,
284        script_path: &str,
285        dt: f32,
286    ) -> Result<(), String> {
287        if let Some((_, key)) = self.loaded_scripts.get(script_path) {
288            let env: mlua::Table = self.lua.registry_value(key).map_err(|e| e.to_string())?;
289
290            // on_entity_update(entity_id, dt) çağır (varsa)
291            if let Ok(func) = env.get::<_, LuaFunction>("on_entity_update") {
292                func.call::<_, ()>((entity_id, dt)).map_err(|e| {
293                    format!(
294                        "Lua on_entity_update hatası (entity {} mod {}): {}",
295                        entity_id, script_path, e
296                    )
297                })?;
298            }
299        }
300        Ok(())
301    }
302
303    /// Komut kuyruğundaki tüm komutları World'e uygular ve oyun mantığı için kalan komutları döndürür
304    pub fn flush_commands(&self, world: &mut World, dt: f32) -> Vec<ScriptCommand> {
305        let commands = self.command_queue.drain();
306        let mut unhandled = Vec::new();
307
308        for cmd in commands {
309            match cmd {
310                ScriptCommand::SetPosition(id, pos) => {
311                    let mut transforms = world.borrow_mut::<gizmo_physics::components::Transform>();
312                    if let Some(t) = transforms.get_mut(id) {
313                        t.position = pos;
314                    }
315                }
316                ScriptCommand::SetRotation(id, rot) => {
317                    let mut transforms = world.borrow_mut::<gizmo_physics::components::Transform>();
318                    if let Some(t) = transforms.get_mut(id) {
319                        t.rotation = rot;
320                    }
321                }
322                ScriptCommand::SetScale(id, scale) => {
323                    let mut transforms = world.borrow_mut::<gizmo_physics::components::Transform>();
324                    if let Some(t) = transforms.get_mut(id) {
325                        t.scale = scale;
326                    }
327                }
328                ScriptCommand::SetVelocity(id, vel) => {
329                    let mut velocities = world.borrow_mut::<gizmo_physics::components::Velocity>();
330                    if let Some(v) = velocities.get_mut(id) {
331                        v.linear = vel;
332                    }
333                }
334                ScriptCommand::SetAngularVelocity(id, ang_vel) => {
335                    let mut velocities = world.borrow_mut::<gizmo_physics::components::Velocity>();
336                    if let Some(v) = velocities.get_mut(id) {
337                        v.angular = ang_vel;
338                    }
339                }
340                ScriptCommand::ApplyForce(id, force) => {
341                    let rbs = world.borrow::<gizmo_physics::components::RigidBody>();
342                    if let Some(rb) = rbs.get(id) {
343                        if rb.mass > 0.0 {
344                            let accel = force * (1.0 / rb.mass);
345                            drop(rbs);
346                            let mut vels =
347                                world.borrow_mut::<gizmo_physics::components::Velocity>();
348                            if let Some(v) = vels.get_mut(id) {
349                                v.linear += accel * dt;
350                            }
351                        }
352                    }
353                }
354                ScriptCommand::ApplyImpulse(id, impulse) => {
355                    let rbs = world.borrow::<gizmo_physics::components::RigidBody>();
356                    if let Some(rb) = rbs.get(id) {
357                        if rb.mass > 0.0 {
358                            let delta_v = impulse * (1.0 / rb.mass);
359                            drop(rbs);
360                            let mut vels =
361                                world.borrow_mut::<gizmo_physics::components::Velocity>();
362                            if let Some(v) = vels.get_mut(id) {
363                                v.linear += delta_v;
364                            }
365                        }
366                    }
367                }
368                ScriptCommand::AddRigidBody {
369                    id,
370                    mass,
371                    restitution,
372                    friction,
373                    use_gravity,
374                } => {
375                    let entity = world
376                        .iter_alive_entities()
377                        .into_iter()
378                        .find(|e| e.id() == id);
379                    if let Some(e) = entity {
380                        let rb = gizmo_physics::components::RigidBody::new(
381                            mass,
382                            restitution,
383                            friction,
384                            use_gravity,
385                        );
386                        world.add_component(e, rb);
387                        // Make sure velocity exists so it can move
388                        if world
389                            .borrow::<gizmo_physics::components::Velocity>()
390                            .get(id)
391                            .is_none()
392                        {
393                            world.add_component(
394                                e,
395                                gizmo_physics::components::Velocity::new(gizmo_math::Vec3::ZERO),
396                            );
397                        }
398                    }
399                }
400                ScriptCommand::AddBoxCollider { id, hx, hy, hz } => {
401                    let entity = world
402                        .iter_alive_entities()
403                        .into_iter()
404                        .find(|e| e.id() == id);
405                    if let Some(e) = entity {
406                        let col =
407                            gizmo_physics::shape::Collider::aabb(gizmo_math::Vec3::new(hx, hy, hz));
408                        world.add_component(e, col);
409                    }
410                }
411                ScriptCommand::AddSphereCollider { id, radius } => {
412                    let entity = world
413                        .iter_alive_entities()
414                        .into_iter()
415                        .find(|e| e.id() == id);
416                    if let Some(e) = entity {
417                        let col = gizmo_physics::shape::Collider::sphere(radius);
418                        world.add_component(e, col);
419                    }
420                }
421
422                ScriptCommand::SetVehicleEngineForce(_id, _force) => {}
423                ScriptCommand::SetVehicleSteering(_id, _angle) => {}
424                ScriptCommand::SetVehicleBrake(_id, _force) => {}
425
426                ScriptCommand::SpawnEntity { name, position } => {
427                    let entity = world.spawn();
428                    world.add_component(entity, gizmo_core::EntityName::new(&name));
429                    world
430                        .add_component(entity, gizmo_physics::components::Transform::new(position));
431                    let msg = format!(
432                        "Entity spawn: '{}' at ({:.1}, {:.1}, {:.1})",
433                        name, position.x, position.y, position.z
434                    );
435                    if let Ok(mut q) = self.log_queue.lock() {
436                        q.push(("info".to_string(), msg));
437                    }
438                }
439                ScriptCommand::SpawnPrefab {
440                    name,
441                    prefab_type,
442                    position,
443                } => {
444                    let entity = world.spawn();
445                    world.add_component(entity, gizmo_core::EntityName::new(&name));
446                    world
447                        .add_component(entity, gizmo_physics::components::Transform::new(position));
448                    world.add_component(entity, gizmo_core::PrefabRequest(prefab_type.clone()));
449                }
450                ScriptCommand::DestroyEntity(id) => {
451                    world.despawn_by_id(id);
452                    if let Ok(mut q) = self.log_queue.lock() {
453                        q.push(("info".to_string(), format!("Entity destroyed: {}", id)));
454                    }
455                }
456                ScriptCommand::SetEntityName(id, name) => {
457                    let mut names = world.borrow_mut::<gizmo_core::EntityName>();
458                    if let Some(n) = names.get_mut(id) {
459                        n.0 = name;
460                    }
461                }
462                ScriptCommand::AddNavAgent(id) => {
463                    let entity = world
464                        .iter_alive_entities()
465                        .into_iter()
466                        .find(|e| e.id() == id);
467                    if let Some(e) = entity {
468                        world.add_component(e, gizmo_ai::components::NavAgent::default());
469                    }
470                }
471                ScriptCommand::SetAiTarget(id, target) => {
472                    let mut agents = world.borrow_mut::<gizmo_ai::components::NavAgent>();
473                    if let Some(agent) = agents.get_mut(id) {
474                        agent.set_target(target);
475                    }
476                }
477                ScriptCommand::ClearAiTarget(id) => {
478                    let mut agents = world.borrow_mut::<gizmo_ai::components::NavAgent>();
479                    if let Some(agent) = agents.get_mut(id) {
480                        agent.clear_path();
481                    }
482                }
483                ScriptCommand::SaveScene(_)
484                | ScriptCommand::ShowDialogue { .. }
485                | ScriptCommand::HideDialogue
486                | ScriptCommand::TriggerCutscene(_)
487                | ScriptCommand::EndCutscene
488                | ScriptCommand::AddCheckpoint { .. }
489                | ScriptCommand::ActivateCheckpoint(_)
490                | ScriptCommand::StartRace
491                | ScriptCommand::FinishRace { .. }
492                | ScriptCommand::ResetRace
493                | ScriptCommand::SetCameraTarget(_)
494                | ScriptCommand::SetCameraFov(_) => {
495                    // Ignored
496                }
497                other => {
498                    unhandled.push(other);
499                }
500            }
501        }
502
503        unhandled
504    }
505
506    /// Runtime'da bekleyen ses/sahne komutlarını döndürür (demo tarafında ele alınır)
507    pub fn get_pending_audio_scene_commands(&self) -> Vec<ScriptCommand> {
508        // Flush zaten çağrıldıysa bu boş dönecek
509        // Alternatif: flush'tan önce çağrılmalı
510        Vec::new()
511    }
512
513    /// Script'in hot-reload edilip edilmeyeceğini kontrol eder
514    pub fn reload_if_changed(&mut self, path: &str) -> Result<bool, String> {
515        let current =
516            std::fs::read_to_string(path).map_err(|e| format!("Script okunamadı: {}", e))?;
517
518        if let Some((cached_code, _)) = self.loaded_scripts.get(path) {
519            if *cached_code == current {
520                return Ok(false);
521            }
522        }
523
524        self.load_script(path)?;
525        Ok(true)
526    }
527
528    /// Belirli bir isimdeki Lua fonksiyonunun var olup olmadığını kontrol eder
529    pub fn has_function(&self, path: &str, name: &str) -> bool {
530        if let Some((_, key)) = self.loaded_scripts.get(path) {
531            if let Ok(env) = self.lua.registry_value::<mlua::Table>(key) {
532                return env.get::<_, LuaFunction>(name).is_ok();
533            }
534        }
535        false
536    }
537
538    /// Belirli bir isimdeki Lua fonksiyonunu çağırır (per-entity scriptler için)
539    pub fn run_entity_update(
540        &self,
541        path: &str,
542        func_name: &str,
543        ctx: &ScriptContext,
544    ) -> Result<ScriptResult, String> {
545        let env: mlua::Table = if let Some((_, key)) = self.loaded_scripts.get(path) {
546            self.lua.registry_value(key).map_err(|e| e.to_string())?
547        } else {
548            return Err(format!("Script not loaded: {}", path));
549        };
550
551        let func: LuaFunction = match env.get(func_name) {
552            Ok(f) => f,
553            Err(_) => return Ok(ScriptResult::default()),
554        };
555
556        let ctx_table = self.lua.create_table().map_err(|e| e.to_string())?;
557        ctx_table
558            .set("entity_id", ctx.entity_id)
559            .map_err(|e| e.to_string())?;
560        ctx_table.set("dt", ctx.dt).map_err(|e| e.to_string())?;
561        ctx_table
562            .set("elapsed", self.elapsed_time)
563            .map_err(|e| e.to_string())?;
564
565        let pos = self.lua.create_table().map_err(|e| e.to_string())?;
566        pos.set("x", ctx.position[0]).map_err(|e| e.to_string())?;
567        pos.set("y", ctx.position[1]).map_err(|e| e.to_string())?;
568        pos.set("z", ctx.position[2]).map_err(|e| e.to_string())?;
569        ctx_table.set("position", pos).map_err(|e| e.to_string())?;
570
571        let vel = self.lua.create_table().map_err(|e| e.to_string())?;
572        vel.set("x", ctx.velocity[0]).map_err(|e| e.to_string())?;
573        vel.set("y", ctx.velocity[1]).map_err(|e| e.to_string())?;
574        vel.set("z", ctx.velocity[2]).map_err(|e| e.to_string())?;
575        ctx_table.set("velocity", vel).map_err(|e| e.to_string())?;
576
577        let input = self.lua.create_table().map_err(|e| e.to_string())?;
578        input.set("w", ctx.key_w).map_err(|e| e.to_string())?;
579        input.set("a", ctx.key_a).map_err(|e| e.to_string())?;
580        input.set("s", ctx.key_s).map_err(|e| e.to_string())?;
581        input.set("d", ctx.key_d).map_err(|e| e.to_string())?;
582        input
583            .set("space", ctx.key_space)
584            .map_err(|e| e.to_string())?;
585        input.set("up", ctx.key_up).map_err(|e| e.to_string())?;
586        input.set("down", ctx.key_down).map_err(|e| e.to_string())?;
587        input.set("left", ctx.key_left).map_err(|e| e.to_string())?;
588        input
589            .set("right", ctx.key_right)
590            .map_err(|e| e.to_string())?;
591        ctx_table.set("input", input).map_err(|e| e.to_string())?;
592
593        let result_table: LuaTable = func
594            .call(ctx_table)
595            .map_err(|e| format!("Lua runtime: {}", e))?;
596
597        let mut result = ScriptResult::default();
598
599        if let Ok(pos) = result_table.get::<_, LuaTable>("position") {
600            let x: f32 = pos.get("x").unwrap_or(0.0);
601            let y: f32 = pos.get("y").unwrap_or(0.0);
602            let z: f32 = pos.get("z").unwrap_or(0.0);
603            result.new_position = Some([x, y, z]);
604        }
605
606        if let Ok(vel) = result_table.get::<_, LuaTable>("velocity") {
607            let x: f32 = vel.get("x").unwrap_or(0.0);
608            let y: f32 = vel.get("y").unwrap_or(0.0);
609            let z: f32 = vel.get("z").unwrap_or(0.0);
610            result.new_velocity = Some([x, y, z]);
611        }
612
613        Ok(result)
614    }
615
616    /// Komut kuyruğuna doğrudan erişim (internals)
617    pub fn command_queue(&self) -> &Arc<CommandQueue> {
618        &self.command_queue
619    }
620}
621
622gizmo_core::impl_component!(Script);