1use crate::commands::{CommandQueue, ScriptCommand};
7use gizmo_core::World;
8use gizmo_math::{Quat, Vec3};
9use mlua::prelude::*;
10use std::sync::Arc;
11
12pub fn register_entity_api(lua: &Lua, command_queue: Arc<CommandQueue>) -> Result<(), LuaError> {
14 crate::api_table::register_protected(lua, "entity", |entity_table| {
15
16 {
18 let cq = command_queue.clone();
19 entity_table.raw_set(
20 "set_position",
21 lua.create_function(move |_, (id, x, y, z): (u32, f32, f32, f32)| {
22 cq.push(ScriptCommand::SetPosition(id, Vec3::new(x, y, z)));
23 Ok(())
24 })?,
25 )?;
26 }
27
28 {
30 let cq = command_queue.clone();
31 entity_table.raw_set(
32 "set_rotation",
33 lua.create_function(move |_, (id, x, y, z, w): (u32, f32, f32, f32, f32)| {
34 cq.push(ScriptCommand::SetRotation(id, Quat::from_xyzw(x, y, z, w)));
35 Ok(())
36 })?,
37 )?;
38 }
39
40 {
42 let cq = command_queue.clone();
43 entity_table.raw_set(
44 "set_scale",
45 lua.create_function(move |_, (id, x, y, z): (u32, f32, f32, f32)| {
46 cq.push(ScriptCommand::SetScale(id, Vec3::new(x, y, z)));
47 Ok(())
48 })?,
49 )?;
50 }
51
52 {
54 let cq = command_queue.clone();
55 entity_table.raw_set(
56 "set_velocity",
57 lua.create_function(move |_, (id, x, y, z): (u32, f32, f32, f32)| {
58 cq.push(ScriptCommand::SetVelocity(id, Vec3::new(x, y, z)));
59 Ok(())
60 })?,
61 )?;
62 }
63
64 {
65 let cq = command_queue.clone();
66 entity_table.raw_set(
67 "set_angular_velocity",
68 lua.create_function(move |_, (id, x, y, z): (u32, f32, f32, f32)| {
69 cq.push(ScriptCommand::SetAngularVelocity(id, Vec3::new(x, y, z)));
70 Ok(())
71 })?,
72 )?;
73 }
74
75 {
77 let cq = command_queue.clone();
78 entity_table.raw_set(
79 "spawn",
80 lua.create_function(move |_, (name, x, y, z): (String, f32, f32, f32)| {
81 cq.push(ScriptCommand::SpawnEntity {
82 name,
83 position: Vec3::new(x, y, z),
84 });
85 Ok(())
86 })?,
87 )?;
88 }
89
90 {
92 let cq = command_queue.clone();
93 entity_table.raw_set(
94 "spawn_prefab",
95 lua.create_function(
96 move |_, (name, prefab_type, x, y, z): (String, String, f32, f32, f32)| {
97 cq.push(ScriptCommand::SpawnPrefab {
98 name,
99 prefab_type,
100 position: Vec3::new(x, y, z),
101 });
102 Ok(())
103 },
104 )?,
105 )?;
106 }
107
108 {
110 let cq = command_queue.clone();
111 entity_table.raw_set(
112 "destroy",
113 lua.create_function(move |_, id: u32| {
114 cq.push(ScriptCommand::DestroyEntity(id));
115 Ok(())
116 })?,
117 )?;
118 }
119
120 {
122 let cq = command_queue.clone();
123 entity_table.raw_set(
124 "set_name",
125 lua.create_function(move |_, (id, name): (u32, String)| {
126 cq.push(ScriptCommand::SetEntityName(id, name));
127 Ok(())
128 })?,
129 )?;
130 }
131
132 lua.load(
137 r#"
138 function entity.get_position(id)
139 return entity._positions[id] or {x=0, y=0, z=0}
140 end
141 function entity.get_velocity(id)
142 return entity._velocities[id] or {x=0, y=0, z=0}
143 end
144 function entity.get_rotation(id)
145 return entity._rotations[id] or {x=0, y=0, z=0, w=1}
146 end
147 function entity.get_scale(id)
148 return entity._scales[id] or {x=1, y=1, z=1}
149 end
150 function entity.get_name(id)
151 return entity._names[id] or ""
152 end
153 "#,
154 )
155 .exec()?;
156
157 Ok(())
158 })
159}
160
161#[tracing::instrument(skip_all, name = "script_entity_read")]
163pub fn update_entity_read_api(lua: &Lua, world: &World) -> Result<(), LuaError> {
164 let entity_table = crate::api_table::raw(lua, "entity")?;
167
168 let positions = lua.create_table()?;
171 let velocities = lua.create_table()?;
172 let rotations = lua.create_table()?;
173 let scales = lua.create_table()?;
174 let names = lua.create_table()?;
175
176 let transforms = world.borrow::<gizmo_physics_core::Transform>();
177 for (eid, _) in transforms.iter() {
178 if let Some(t) = transforms.get(eid) {
179 let pos = lua.create_table()?;
180 pos.set("x", t.position.x)?;
181 pos.set("y", t.position.y)?;
182 pos.set("z", t.position.z)?;
183 positions.set(eid, pos)?;
184
185 let rot = lua.create_table()?;
186 rot.set("x", t.rotation.x)?;
187 rot.set("y", t.rotation.y)?;
188 rot.set("z", t.rotation.z)?;
189 rot.set("w", t.rotation.w)?;
190 rotations.set(eid, rot)?;
191
192 let scl = lua.create_table()?;
193 scl.set("x", t.scale.x)?;
194 scl.set("y", t.scale.y)?;
195 scl.set("z", t.scale.z)?;
196 scales.set(eid, scl)?;
197 }
198 }
199
200 let vels = world.borrow::<gizmo_physics_rigid::components::Velocity>();
201 for (eid, _) in vels.iter() {
202 if let Some(v) = vels.get(eid) {
203 let vel = lua.create_table()?;
204 vel.set("x", v.linear.x)?;
205 vel.set("y", v.linear.y)?;
206 vel.set("z", v.linear.z)?;
207 velocities.set(eid, vel)?;
208 }
209 }
210
211 let entity_names = world.borrow::<gizmo_core::EntityName>();
212 for (eid, _) in entity_names.iter() {
213 if let Some(n) = entity_names.get(eid) {
214 names.set(eid, n.0.clone())?;
215 }
216 }
217
218 entity_table.raw_set("_positions", positions)?;
220 entity_table.raw_set("_velocities", velocities)?;
221 entity_table.raw_set("_rotations", rotations)?;
222 entity_table.raw_set("_scales", scales)?;
223 entity_table.raw_set("_names", names)?;
224
225
226 Ok(())
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232 use gizmo_physics_core::Transform;
233 use mlua::Lua;
234
235 #[test]
238 fn write_calls_push_expected_commands() {
239 let lua = Lua::new();
240 let cq = Arc::new(CommandQueue::new());
241 register_entity_api(&lua, cq.clone()).unwrap();
242
243 lua.load(
244 r#"
245 entity.set_position(1, 1.0, 2.0, 3.0)
246 entity.set_scale(1, 2.0, 2.0, 2.0)
247 entity.set_velocity(1, -5.0, 0.0, 0.0)
248 entity.set_angular_velocity(1, 0.0, 1.5, 0.0)
249 entity.set_rotation(1, 0.0, 0.0, 0.0, 1.0)
250 "#,
251 )
252 .exec()
253 .unwrap();
254
255 let cmds = cq.drain();
256 assert_eq!(cmds.len(), 5);
257 assert!(matches!(cmds[0], ScriptCommand::SetPosition(1, p) if p == Vec3::new(1.0, 2.0, 3.0)));
258 assert!(matches!(cmds[1], ScriptCommand::SetScale(1, s) if s == Vec3::new(2.0, 2.0, 2.0)));
259 assert!(matches!(cmds[2], ScriptCommand::SetVelocity(1, v) if v == Vec3::new(-5.0, 0.0, 0.0)));
260 assert!(matches!(cmds[3], ScriptCommand::SetAngularVelocity(1, v) if v == Vec3::new(0.0, 1.5, 0.0)));
261 match cmds[4] {
262 ScriptCommand::SetRotation(id, q) => {
263 assert_eq!(id, 1);
264 assert!((q.w - 1.0).abs() < 1e-6 && q.x.abs() < 1e-6);
266 }
267 ref other => panic!("beklenen SetRotation, gelen {other:?}"),
268 }
269 }
270
271 #[test]
273 fn lifecycle_calls_push_expected_commands() {
274 let lua = Lua::new();
275 let cq = Arc::new(CommandQueue::new());
276 register_entity_api(&lua, cq.clone()).unwrap();
277
278 lua.load(
279 r#"
280 entity.spawn("hero", 4.0, 5.0, 6.0)
281 entity.spawn_prefab("crate", "wooden_box", 0.0, 0.0, 0.0)
282 entity.set_name(9, "renamed")
283 entity.destroy(9)
284 "#,
285 )
286 .exec()
287 .unwrap();
288
289 let cmds = cq.drain();
290 assert_eq!(cmds.len(), 4);
291 match &cmds[0] {
292 ScriptCommand::SpawnEntity { name, position } => {
293 assert_eq!(name, "hero");
294 assert_eq!(*position, Vec3::new(4.0, 5.0, 6.0));
295 }
296 other => panic!("beklenen SpawnEntity, gelen {other:?}"),
297 }
298 match &cmds[1] {
299 ScriptCommand::SpawnPrefab { name, prefab_type, position } => {
300 assert_eq!(name, "crate");
301 assert_eq!(prefab_type, "wooden_box");
302 assert_eq!(*position, Vec3::ZERO);
303 }
304 other => panic!("beklenen SpawnPrefab, gelen {other:?}"),
305 }
306 match &cmds[2] {
307 ScriptCommand::SetEntityName(id, name) => {
308 assert_eq!(*id, 9);
309 assert_eq!(name, "renamed");
310 }
311 other => panic!("beklenen SetEntityName, gelen {other:?}"),
312 }
313 assert!(matches!(cmds[3], ScriptCommand::DestroyEntity(9)));
314 }
315
316 #[test]
319 fn read_api_reflects_world_transform() {
320 let lua = Lua::new();
321 let cq = Arc::new(CommandQueue::new());
322 register_entity_api(&lua, cq).unwrap();
323
324 let mut world = World::new();
325 let e = world.spawn();
326 world.add_component(e, Transform::new(Vec3::new(7.0, 8.0, 9.0)));
327 let id = e.id();
328
329 update_entity_read_api(&lua, &world).unwrap();
330
331 lua.load(format!(
332 r#"
333 local p = entity.get_position({id})
334 assert(math.abs(p.x - 7.0) < 1e-5, "x")
335 assert(math.abs(p.y - 8.0) < 1e-5, "y")
336 assert(math.abs(p.z - 9.0) < 1e-5, "z")
337 "#
338 ))
339 .exec()
340 .unwrap();
341 }
342
343 #[test]
346 fn read_api_returns_safe_defaults_for_unknown_id() {
347 let lua = Lua::new();
348 let cq = Arc::new(CommandQueue::new());
349 register_entity_api(&lua, cq).unwrap();
350
351 let world = World::new();
353 update_entity_read_api(&lua, &world).unwrap();
354
355 lua.load(
356 r#"
357 local p = entity.get_position(999)
358 assert(p.x == 0 and p.y == 0 and p.z == 0, "pozisyon varsayılanı sıfır")
359 local v = entity.get_velocity(999)
360 assert(v.x == 0 and v.y == 0 and v.z == 0, "hız varsayılanı sıfır")
361 local r = entity.get_rotation(999)
362 assert(r.x == 0 and r.y == 0 and r.z == 0 and r.w == 1, "rotasyon varsayılanı kimlik")
363 local s = entity.get_scale(999)
364 assert(s.x == 1 and s.y == 1 and s.z == 1, "ölçek varsayılanı birim")
365 assert(entity.get_name(999) == "", "isim varsayılanı boş string")
366 "#,
367 )
368 .exec()
369 .unwrap();
370 }
371}