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        println!("🔧 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
262        // 2. on_update callback'ini çağır (varsa)
263        let globals = self.lua.globals();
264        if let Ok(func) = globals.get::<_, LuaFunction>("on_update") {
265            let ctx_table = self.lua.create_table().map_err(|e| e.to_string())?;
266            ctx_table.set("dt", dt).map_err(|e| e.to_string())?;
267            ctx_table
268                .set("elapsed", self.elapsed_time)
269                .map_err(|e| e.to_string())?;
270
271            func.call::<_, ()>(ctx_table)
272                .map_err(|e| format!("Lua on_update hatası: {}", e))?;
273        }
274
275        Ok(())
276    }
277
278    /// Per-entity script güncelleme — Script component'i olan entity'ler için izole ortamda çalıştırır
279    pub fn update_entity(
280        &mut self,
281        entity_id: u32,
282        script_path: &str,
283        dt: f32,
284    ) -> Result<(), String> {
285        if let Some((_, key)) = self.loaded_scripts.get(script_path) {
286            let env: mlua::Table = self.lua.registry_value(key).map_err(|e| e.to_string())?;
287
288            // on_entity_update(entity_id, dt) çağır (varsa)
289            if let Ok(func) = env.get::<_, LuaFunction>("on_entity_update") {
290                func.call::<_, ()>((entity_id, dt)).map_err(|e| {
291                    format!(
292                        "Lua on_entity_update hatası (entity {} mod {}): {}",
293                        entity_id, script_path, e
294                    )
295                })?;
296            }
297        }
298        Ok(())
299    }
300
301    /// Komut kuyruğundaki tüm komutları World'e uygular ve oyun mantığı için kalan komutları döndürür
302    pub fn flush_commands(&self, world: &mut World, dt: f32) -> Vec<ScriptCommand> {
303        let commands = self.command_queue.drain();
304        let mut unhandled = Vec::new();
305
306        for cmd in commands {
307            match cmd {
308                ScriptCommand::SetPosition(id, pos) => {
309                    let mut transforms = world.borrow_mut::<gizmo_physics::components::Transform>();
310                    if let Some(t) = transforms.get_mut(id) {
311                        t.position = pos;
312                    }
313                }
314                ScriptCommand::SetRotation(id, rot) => {
315                    let mut transforms = world.borrow_mut::<gizmo_physics::components::Transform>();
316                    if let Some(t) = transforms.get_mut(id) {
317                        t.rotation = rot;
318                    }
319                }
320                ScriptCommand::SetScale(id, scale) => {
321                    let mut transforms = world.borrow_mut::<gizmo_physics::components::Transform>();
322                    if let Some(t) = transforms.get_mut(id) {
323                        t.scale = scale;
324                    }
325                }
326                ScriptCommand::SetVelocity(id, vel) => {
327                    let mut velocities = world.borrow_mut::<gizmo_physics::components::Velocity>();
328                    if let Some(v) = velocities.get_mut(id) {
329                        v.linear = vel;
330                    }
331                }
332                ScriptCommand::SetAngularVelocity(id, ang_vel) => {
333                    let mut velocities = world.borrow_mut::<gizmo_physics::components::Velocity>();
334                    if let Some(v) = velocities.get_mut(id) {
335                        v.angular = ang_vel;
336                    }
337                }
338                ScriptCommand::ApplyForce(id, force) => {
339                    let rbs = world.borrow::<gizmo_physics::components::RigidBody>();
340                    if let Some(rb) = rbs.get(id) {
341                        if rb.mass > 0.0 {
342                            let accel = force * (1.0 / rb.mass);
343                            drop(rbs);
344                            let mut vels =
345                                world.borrow_mut::<gizmo_physics::components::Velocity>();
346                            if let Some(v) = vels.get_mut(id) {
347                                v.linear += accel * dt;
348                            }
349                        }
350                    }
351                }
352                ScriptCommand::ApplyImpulse(id, impulse) => {
353                    let rbs = world.borrow::<gizmo_physics::components::RigidBody>();
354                    if let Some(rb) = rbs.get(id) {
355                        if rb.mass > 0.0 {
356                            let delta_v = impulse * (1.0 / rb.mass);
357                            drop(rbs);
358                            let mut vels =
359                                world.borrow_mut::<gizmo_physics::components::Velocity>();
360                            if let Some(v) = vels.get_mut(id) {
361                                v.linear += delta_v;
362                            }
363                        }
364                    }
365                }
366                ScriptCommand::AddRigidBody {
367                    id,
368                    mass,
369                    restitution,
370                    friction,
371                    use_gravity,
372                } => {
373                    let entity = world
374                        .iter_alive_entities()
375                        .into_iter()
376                        .find(|e| e.id() == id);
377                    if let Some(e) = entity {
378                        let rb = gizmo_physics::components::RigidBody::new(
379                            mass,
380                            restitution,
381                            friction,
382                            use_gravity,
383                        );
384                        world.add_component(e, rb);
385                        // Make sure velocity exists so it can move
386                        if world
387                            .borrow::<gizmo_physics::components::Velocity>()
388                            .get(id)
389                            .is_none()
390                        {
391                            world.add_component(
392                                e,
393                                gizmo_physics::components::Velocity::new(gizmo_math::Vec3::ZERO),
394                            );
395                        }
396                    }
397                }
398                ScriptCommand::AddBoxCollider { id, hx, hy, hz } => {
399                    let entity = world
400                        .iter_alive_entities()
401                        .into_iter()
402                        .find(|e| e.id() == id);
403                    if let Some(e) = entity {
404                        let col =
405                            gizmo_physics::shape::Collider::aabb(gizmo_math::Vec3::new(hx, hy, hz));
406                        world.add_component(e, col);
407                    }
408                }
409                ScriptCommand::AddSphereCollider { id, radius } => {
410                    let entity = world
411                        .iter_alive_entities()
412                        .into_iter()
413                        .find(|e| e.id() == id);
414                    if let Some(e) = entity {
415                        let col = gizmo_physics::shape::Collider::sphere(radius);
416                        world.add_component(e, col);
417                    }
418                }
419
420                ScriptCommand::SetVehicleEngineForce(_id, _force) => {}
421                ScriptCommand::SetVehicleSteering(_id, _angle) => {}
422                ScriptCommand::SetVehicleBrake(_id, _force) => {}
423
424                ScriptCommand::SpawnEntity { name, position } => {
425                    let entity = world.spawn();
426                    world.add_component(entity, gizmo_core::EntityName::new(&name));
427                    world
428                        .add_component(entity, gizmo_physics::components::Transform::new(position));
429                    let msg = format!(
430                        "Entity spawn: '{}' at ({:.1}, {:.1}, {:.1})",
431                        name, position.x, position.y, position.z
432                    );
433                    if let Ok(mut q) = self.log_queue.lock() {
434                        q.push(("info".to_string(), msg));
435                    }
436                }
437                ScriptCommand::SpawnPrefab {
438                    name,
439                    prefab_type,
440                    position,
441                } => {
442                    let entity = world.spawn();
443                    world.add_component(entity, gizmo_core::EntityName::new(&name));
444                    world
445                        .add_component(entity, gizmo_physics::components::Transform::new(position));
446                    world.add_component(entity, gizmo_core::PrefabRequest(prefab_type.clone()));
447                }
448                ScriptCommand::DestroyEntity(id) => {
449                    world.despawn_by_id(id);
450                    if let Ok(mut q) = self.log_queue.lock() {
451                        q.push(("info".to_string(), format!("Entity destroyed: {}", id)));
452                    }
453                }
454                ScriptCommand::SetEntityName(id, name) => {
455                    let mut names = world.borrow_mut::<gizmo_core::EntityName>();
456                    if let Some(n) = names.get_mut(id) {
457                        n.0 = name;
458                    }
459                }
460                ScriptCommand::AddNavAgent(id) => {
461                    let entity = world
462                        .iter_alive_entities()
463                        .into_iter()
464                        .find(|e| e.id() == id);
465                    if let Some(e) = entity {
466                        world.add_component(e, gizmo_ai::components::NavAgent::default());
467                    }
468                }
469                ScriptCommand::SetAiTarget(id, target) => {
470                    let mut agents = world.borrow_mut::<gizmo_ai::components::NavAgent>();
471                    if let Some(agent) = agents.get_mut(id) {
472                        agent.set_target(target);
473                    }
474                }
475                ScriptCommand::ClearAiTarget(id) => {
476                    let mut agents = world.borrow_mut::<gizmo_ai::components::NavAgent>();
477                    if let Some(agent) = agents.get_mut(id) {
478                        agent.clear_path();
479                    }
480                }
481                ScriptCommand::SaveScene(_)
482                | ScriptCommand::ShowDialogue { .. }
483                | ScriptCommand::HideDialogue
484                | ScriptCommand::TriggerCutscene(_)
485                | ScriptCommand::EndCutscene
486                | ScriptCommand::AddCheckpoint { .. }
487                | ScriptCommand::ActivateCheckpoint(_)
488                | ScriptCommand::StartRace
489                | ScriptCommand::FinishRace { .. }
490                | ScriptCommand::ResetRace
491                | ScriptCommand::SetCameraTarget(_)
492                | ScriptCommand::SetCameraFov(_) => {
493                    // Ignored
494                }
495                other => {
496                    unhandled.push(other);
497                }
498            }
499        }
500
501        unhandled
502    }
503
504    /// Runtime'da bekleyen ses/sahne komutlarını döndürür (demo tarafında ele alınır)
505    pub fn get_pending_audio_scene_commands(&self) -> Vec<ScriptCommand> {
506        // Flush zaten çağrıldıysa bu boş dönecek
507        // Alternatif: flush'tan önce çağrılmalı
508        Vec::new()
509    }
510
511    /// Script'in hot-reload edilip edilmeyeceğini kontrol eder
512    pub fn reload_if_changed(&mut self, path: &str) -> Result<bool, String> {
513        let current =
514            std::fs::read_to_string(path).map_err(|e| format!("Script okunamadı: {}", e))?;
515
516        if let Some((cached_code, _)) = self.loaded_scripts.get(path) {
517            if *cached_code == current {
518                return Ok(false);
519            }
520        }
521
522        self.load_script(path)?;
523        Ok(true)
524    }
525
526    /// Belirli bir isimdeki Lua fonksiyonunun var olup olmadığını kontrol eder
527    pub fn has_function(&self, path: &str, name: &str) -> bool {
528        if let Some((_, key)) = self.loaded_scripts.get(path) {
529            if let Ok(env) = self.lua.registry_value::<mlua::Table>(key) {
530                return env.get::<_, LuaFunction>(name).is_ok();
531            }
532        }
533        false
534    }
535
536    /// Belirli bir isimdeki Lua fonksiyonunu çağırır (per-entity scriptler için)
537    pub fn run_entity_update(
538        &self,
539        path: &str,
540        func_name: &str,
541        ctx: &ScriptContext,
542    ) -> Result<ScriptResult, String> {
543        let env: mlua::Table = if let Some((_, key)) = self.loaded_scripts.get(path) {
544            self.lua.registry_value(key).map_err(|e| e.to_string())?
545        } else {
546            return Err(format!("Script not loaded: {}", path));
547        };
548
549        let func: LuaFunction = match env.get(func_name) {
550            Ok(f) => f,
551            Err(_) => return Ok(ScriptResult::default()),
552        };
553
554        let ctx_table = self.lua.create_table().map_err(|e| e.to_string())?;
555        ctx_table
556            .set("entity_id", ctx.entity_id)
557            .map_err(|e| e.to_string())?;
558        ctx_table.set("dt", ctx.dt).map_err(|e| e.to_string())?;
559        ctx_table
560            .set("elapsed", self.elapsed_time)
561            .map_err(|e| e.to_string())?;
562
563        let pos = self.lua.create_table().map_err(|e| e.to_string())?;
564        pos.set("x", ctx.position[0]).map_err(|e| e.to_string())?;
565        pos.set("y", ctx.position[1]).map_err(|e| e.to_string())?;
566        pos.set("z", ctx.position[2]).map_err(|e| e.to_string())?;
567        ctx_table.set("position", pos).map_err(|e| e.to_string())?;
568
569        let vel = self.lua.create_table().map_err(|e| e.to_string())?;
570        vel.set("x", ctx.velocity[0]).map_err(|e| e.to_string())?;
571        vel.set("y", ctx.velocity[1]).map_err(|e| e.to_string())?;
572        vel.set("z", ctx.velocity[2]).map_err(|e| e.to_string())?;
573        ctx_table.set("velocity", vel).map_err(|e| e.to_string())?;
574
575        let input = self.lua.create_table().map_err(|e| e.to_string())?;
576        input.set("w", ctx.key_w).map_err(|e| e.to_string())?;
577        input.set("a", ctx.key_a).map_err(|e| e.to_string())?;
578        input.set("s", ctx.key_s).map_err(|e| e.to_string())?;
579        input.set("d", ctx.key_d).map_err(|e| e.to_string())?;
580        input
581            .set("space", ctx.key_space)
582            .map_err(|e| e.to_string())?;
583        input.set("up", ctx.key_up).map_err(|e| e.to_string())?;
584        input.set("down", ctx.key_down).map_err(|e| e.to_string())?;
585        input.set("left", ctx.key_left).map_err(|e| e.to_string())?;
586        input
587            .set("right", ctx.key_right)
588            .map_err(|e| e.to_string())?;
589        ctx_table.set("input", input).map_err(|e| e.to_string())?;
590
591        let result_table: LuaTable = func
592            .call(ctx_table)
593            .map_err(|e| format!("Lua runtime: {}", e))?;
594
595        let mut result = ScriptResult::default();
596
597        if let Ok(pos) = result_table.get::<_, LuaTable>("position") {
598            let x: f32 = pos.get("x").unwrap_or(0.0);
599            let y: f32 = pos.get("y").unwrap_or(0.0);
600            let z: f32 = pos.get("z").unwrap_or(0.0);
601            result.new_position = Some([x, y, z]);
602        }
603
604        if let Ok(vel) = result_table.get::<_, LuaTable>("velocity") {
605            let x: f32 = vel.get("x").unwrap_or(0.0);
606            let y: f32 = vel.get("y").unwrap_or(0.0);
607            let z: f32 = vel.get("z").unwrap_or(0.0);
608            result.new_velocity = Some([x, y, z]);
609        }
610
611        Ok(result)
612    }
613
614    /// Komut kuyruğuna doğrudan erişim (internals)
615    pub fn command_queue(&self) -> &Arc<CommandQueue> {
616        &self.command_queue
617    }
618}
619
620gizmo_core::impl_component!(Script);