feat(script): add Lua engine API bindings (spawn, position, entity_count)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-25 18:30:26 +09:00
parent 3d985ba803
commit 7a5e06bd24
4 changed files with 123 additions and 0 deletions

View File

@@ -4,6 +4,8 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
voltex_math.workspace = true
voltex_ecs.workspace = true
[build-dependencies] [build-dependencies]
cc = "1" cc = "1"

View File

@@ -0,0 +1,117 @@
use crate::ffi;
use crate::state::LuaState;
use voltex_ecs::World;
use voltex_ecs::Transform;
use voltex_math::Vec3;
use std::os::raw::c_int;
/// Register engine API functions into the Lua state.
/// The World pointer is stored in a global "__voltex_world" as a light userdata.
pub fn register_engine_api(lua: &LuaState, world: *mut World) {
// Store world pointer as light userdata in global
unsafe {
ffi::lua_pushlightuserdata(lua.raw(), world as *mut std::ffi::c_void);
let name = std::ffi::CString::new("__voltex_world").unwrap();
ffi::lua_setglobal(lua.raw(), name.as_ptr());
}
lua.register_fn("spawn_entity", lua_spawn_entity);
lua.register_fn("set_position", lua_set_position);
lua.register_fn("get_position", lua_get_position);
lua.register_fn("entity_count", lua_entity_count);
}
/// Helper to retrieve the World pointer from the Lua registry global.
unsafe fn get_world(L: *mut ffi::lua_State) -> &'static mut World {
let name = std::ffi::CString::new("__voltex_world").unwrap();
ffi::lua_getglobal(L, name.as_ptr());
let ptr = ffi::lua_touserdata(L, -1) as *mut World;
ffi::lua_pop(L, 1);
&mut *ptr
}
unsafe extern "C" fn lua_spawn_entity(L: *mut ffi::lua_State) -> c_int {
let world = get_world(L);
let entity = world.spawn();
world.add(entity, Transform::new());
ffi::lua_pushnumber(L, entity.id as f64);
1
}
unsafe extern "C" fn lua_set_position(L: *mut ffi::lua_State) -> c_int {
let world = get_world(L);
let mut isnum = 0;
let id = ffi::lua_tonumberx(L, 1, &mut isnum) as u32;
let x = ffi::lua_tonumberx(L, 2, &mut isnum) as f32;
let y = ffi::lua_tonumberx(L, 3, &mut isnum) as f32;
let z = ffi::lua_tonumberx(L, 4, &mut isnum) as f32;
let entity = voltex_ecs::Entity { id, generation: 0 };
if let Some(t) = world.get_mut::<Transform>(entity) {
t.position = Vec3::new(x, y, z);
}
0
}
unsafe extern "C" fn lua_get_position(L: *mut ffi::lua_State) -> c_int {
let world = get_world(L);
let mut isnum = 0;
let id = ffi::lua_tonumberx(L, 1, &mut isnum) as u32;
let entity = voltex_ecs::Entity { id, generation: 0 };
if let Some(t) = world.get::<Transform>(entity) {
ffi::lua_pushnumber(L, t.position.x as f64);
ffi::lua_pushnumber(L, t.position.y as f64);
ffi::lua_pushnumber(L, t.position.z as f64);
3
} else {
0
}
}
unsafe extern "C" fn lua_entity_count(L: *mut ffi::lua_State) -> c_int {
let world = get_world(L);
ffi::lua_pushnumber(L, world.entity_count() as f64);
1
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::LuaState;
#[test]
fn test_spawn_and_get_position() {
let lua = LuaState::new();
let mut world = World::new();
register_engine_api(&lua, &mut world as *mut World);
lua.exec("
local id = spawn_entity()
set_position(id, 5.0, 10.0, 15.0)
local x, y, z = get_position(id)
result_x = x
result_y = y
result_z = z
").unwrap();
assert_eq!(lua.get_global_number("result_x"), Some(5.0));
assert_eq!(lua.get_global_number("result_y"), Some(10.0));
assert_eq!(lua.get_global_number("result_z"), Some(15.0));
}
#[test]
fn test_entity_count() {
let lua = LuaState::new();
let mut world = World::new();
register_engine_api(&lua, &mut world as *mut World);
lua.exec("
spawn_entity()
spawn_entity()
count = entity_count()
").unwrap();
assert_eq!(lua.get_global_number("count"), Some(2.0));
}
}

View File

@@ -31,10 +31,12 @@ extern "C" {
pub fn lua_pushnumber(L: *mut lua_State, n: lua_Number); pub fn lua_pushnumber(L: *mut lua_State, n: lua_Number);
pub fn lua_pushstring(L: *mut lua_State, s: *const c_char) -> *const c_char; pub fn lua_pushstring(L: *mut lua_State, s: *const c_char) -> *const c_char;
pub fn lua_pushcclosure(L: *mut lua_State, f: lua_CFunction, n: c_int); pub fn lua_pushcclosure(L: *mut lua_State, f: lua_CFunction, n: c_int);
pub fn lua_pushlightuserdata(L: *mut lua_State, p: *mut c_void);
// Get // Get
pub fn lua_tonumberx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Number; pub fn lua_tonumberx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Number;
pub fn lua_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char; pub fn lua_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char;
pub fn lua_touserdata(L: *mut lua_State, idx: c_int) -> *mut c_void;
// Globals // Globals
pub fn lua_getglobal(L: *mut lua_State, name: *const c_char) -> c_int; pub fn lua_getglobal(L: *mut lua_State, name: *const c_char) -> c_int;

View File

@@ -1,6 +1,8 @@
pub mod ffi; pub mod ffi;
pub mod state; pub mod state;
pub mod bindings; pub mod bindings;
pub mod engine_api;
pub use state::LuaState; pub use state::LuaState;
pub use bindings::register_default_bindings; pub use bindings::register_default_bindings;
pub use engine_api::register_engine_api;