use std::{marker::PhantomData, sync::Arc}; use mlua_sys as lua; pub mod state; use state::*; pub mod table; use table::*; pub mod function; use function::*; pub mod value; use value::*; pub mod guard; use guard::*; pub mod userdata; use userdata::*; pub mod util; use util::*; pub mod chunk; use chunk::*; fn main() -> Result<()> { let lua = State::new(); lua.expose_libraries(&[StdLibrary::Debug, StdLibrary::Package]); let globals = lua.globals()?; let a = |_lua: &State, (num,): (i32,)| -> Result { println!("Rust got number from lua: {}", num); Ok(999) }; let f = lua.create_function(a)?; globals.set("native_test", f)?; let ud = lua.create_userdata(UserdataProxy::::new())?; globals.set("Vec2", ud)?; let tbl = lua.create_table()?; tbl.set("x", 10)?; //let globals = lua.globals()?; globals.set("X", tbl)?; let chunk = lua.load("text.lua", r#" require "util" --[[function dump_table(tbl) for k, v in pairs(tbl) do if type(v) == "table" then dump_table(v) elseif type(v) == "function" then else print(k .. "=" .. tostring(v)) end end end for k, v in pairs(_G) do --print("Found global named " .. k) if k == "X" then --dump_table(v) end end]]-- function multiply_print(a, b) print(a .. " * " .. b .. " = " .. a*b) end function multiply_ret(a, b) return a * b end function say_number(a) print("Lua says " .. a) end cool_num = 50 print("Lua is about to exec native_test") local res = native_test(50) print("Lua got " .. res .. " back from rust!") print("Vec2: " .. dump_table(Vec2)) print("Meta Vec2: " .. dump_table(getmetatable(Vec2))) --print("Vec2 is (" .. Vec2.x .. ", " .. Vec2.y .. ")") local v1 = Vec2.new(50, 50) print("v1 is (" .. v1.x .. ", " .. v1.y .. ")") local v2 = Vec2.new(500, 500) print("v2 is (" .. v2.x .. ", " .. v2.y .. ")") local v_add = v1 + v2 v_add.x = 90 print("v_add is (" .. v_add.x .. ", " .. v_add.y .. ")") function f1(v1, v2) --print("f1 tb: " .. debug.traceback(1)) local v_add = v1:add(v2) end function f2(v1, v2) --print("f2 tb: " .. debug.traceback(1)) f1(v1, v2) end function f3(v1, v2) --print("f3 tb: " .. debug.traceback(1)) f2(v1, v2) end --f3(v1, v2) "#)?; // I don't care about the result of this execution, so I set the result as a // Value which can be anything // // Chunks can also be executed with: `chunk.execute(())?;` let res = lua.execute_chunk::<_, Value>(&chunk, ()); // if unwrapped, the new lines in the message would be escaped making // the traceback in the error difficult to read. if let Err(e) = res { panic!("{}", e); } unsafe { assert_eq!(lua::lua_gettop(lua.state_ptr()), 0); // ensure that nothing is left on the stack } Ok(()) } /// A LuaRef is a a handle to something in Lua. /// /// These are created with `luaL_ref`, they can be created from anything on the top of the stack /// with [`LuaRef::from_stack`]. The references are automatically freed with `luaL_unref` when /// the inner Arc detects a single strong count. #[derive(Clone)] pub struct LuaRef<'a>(Arc, &'a State); impl<'a> Drop for LuaRef<'a> { fn drop(&mut self) { unsafe { let s = self.1.state_ptr(); if Arc::strong_count(&self.0) == 1 { lua::luaL_unref(s, lua::LUA_REGISTRYINDEX, *self.0); } } } } impl<'a> LuaRef<'a> { pub fn new(lua_ref: i32, state: &'a State) -> Self { Self(Arc::new(lua_ref), state) } /// Creates a reference to what is at the top of the stack. pub unsafe fn from_stack(state: &'a State) -> Result { let s = state.state_ptr(); let r = lua::luaL_ref(s, lua::LUA_REGISTRYINDEX); if r == lua::LUA_REFNIL { Err(Error::Nil) } else { Ok(LuaRef::new(r, state)) } } } impl<'a> PushToLuaStack<'a> for LuaRef<'a> { unsafe fn push_to_lua_stack(&self, state: &State) -> Result<()> { let s = state.state_ptr(); state.ensure_stack(1)?; let top = lua::lua_gettop(s); let ty = lua::lua_rawgeti(s, lua::LUA_REGISTRYINDEX, *self.0 as i64); let new_top = lua::lua_gettop(s); if ty == lua::LUA_TNIL || ty == lua::LUA_TNONE || top == new_top { return Err(Error::Nil); } Ok(()) } } #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Syntax error: {0}")] Syntax(String), /// An error returned from lua #[error("Lua runtime error: {0}")] Runtime(String), #[error("Error when running a __gc metamethod")] GcFailure(String), /// Ran into a not enough memory error when trying to grow the lua stack. #[error("Failed to allocate memory")] MemoryAlloc, #[error("Ran into a nill value on the stack")] Nil, #[error("Unexpected type, expected {0} but got {1}")] UnexpectedType(String, String), #[error("bad argument #{arg_index}{} to `{}` ({error})", .arg_name.clone().map(|a| format!(" (name: {})", a)).unwrap_or("".to_string()), .func.clone().unwrap_or("Unknown".to_string()) )] BadArgument { func: Option, arg_index: i32, arg_name: Option, /// the error that describes what was wrong for this argument #[source] error: Arc }, #[error("Incorrect number of arguments, expected {arg_expected}, got {arg_count}")] IncorrectArgCount { arg_expected: i32, arg_count: i32, }, #[error("There is already a registry entry with the key {0}")] RegistryConflict(String), #[error("Userdata types did not match")] UserdataMismatch, #[error("Missing meta table for userdata")] MissingMetatable, #[error("An error occurred when attempting to convert from a ValueVec at value index {value_idx}, cause: {error}")] ValueVecError { value_idx: i32, #[source] error: Arc, }, } impl Error { pub fn runtime(msg: &str) -> Self { Self::Runtime(msg.to_string()) } pub fn unexpected_type(expected: &str, got: &str) -> Self { Self::UnexpectedType(expected.to_string(), got.to_string()) } /// Throw the error in lua. /// /// This method never returns pub unsafe fn throw_lua(self, lua: *mut lua::lua_State) -> ! { let msg = format!("{}\0", self); let msg_c = msg.as_ptr() as *const i8; lua::luaL_error(lua, msg_c); unreachable!(); } } /// A result for use with lua functions type Result = core::result::Result; pub trait PushToLuaStack<'a> { unsafe fn push_to_lua_stack(&self, state: &'a State) -> Result<()>; } pub trait FromLuaStack<'a>: Sized { unsafe fn from_lua_stack(state: &'a State) -> Result; } impl<'a> PushToLuaStack<'a> for () { unsafe fn push_to_lua_stack(&self, _state: &'a State) -> Result<()> { Ok(()) } } impl<'a, T: PushToLuaStack<'a>> PushToLuaStack<'a> for Option { unsafe fn push_to_lua_stack(&self, state: &'a State) -> Result<()> { if let Some(v) = self { v.push_to_lua_stack(state)?; } else { unsafe { lua::lua_pushnil(state.state_ptr()); } } Ok(()) } } /// Implements PushToLuaStack for a number macro_rules! impl_as_lua_number { ($ty: ident) => { impl<'a> AsLua<'a> for $ty { fn as_lua(&self, _lua: &'a State) -> crate::Result> { Ok(Value::Number(*self as f64)) } } impl<'a> FromLua<'a> for $ty { fn from_lua(_lua: &State, val: Value) -> crate::Result { match val { Value::Number(v) => Ok(v as $ty), _ => { Err(Error::UnexpectedType("Number".to_string(), val.type_name().to_string())) }, } } } impl<'a> PushToLuaStack<'a> for $ty { unsafe fn push_to_lua_stack(&self, state: &'a State) -> crate::Result<()> { let v = self.as_lua(state)?; v.push_to_lua_stack(state) } } }; } impl_as_lua_number!(i8); impl_as_lua_number!(i16); impl_as_lua_number!(i32); impl_as_lua_number!(i64); impl_as_lua_number!(u8); impl_as_lua_number!(u16); impl_as_lua_number!(u32); impl_as_lua_number!(u64); impl_as_lua_number!(f32); impl_as_lua_number!(f64); impl<'a> PushToLuaStack<'a> for String { unsafe fn push_to_lua_stack(&self, state: &State) -> Result<()> { state.ensure_stack(1)?; let s = format!("{}\0", self); let cstr = s.as_ptr() as *const i8; lua::lua_pushstring(state.state_ptr(), cstr); Ok(()) } } impl<'a> FromLua<'a> for String { fn from_lua(_lua: &State, val: Value) -> crate::Result { val.as_string().cloned() } } impl<'a> PushToLuaStack<'a> for &str { unsafe fn push_to_lua_stack(&self, state: &State) -> Result<()> { state.ensure_stack(1)?; let s = format!("{}\0", self); let cstr = s.as_ptr() as *const i8; lua::lua_pushstring(state.state_ptr(), cstr); Ok(()) } } #[allow(dead_code)] pub struct Vec3 { x: f32, y: f32, z: f32, } impl Userdata for Vec3 { fn build<'a>(_builder: &mut UserdataBuilder<'a, Self>) -> crate::Result<()> { todo!() } fn name() -> String { todo!() } } pub struct Vec2 { x: f32, y: f32, } impl Userdata for Vec2 { fn build<'a>(builder: &mut UserdataBuilder<'a, Vec2>) -> crate::Result<()> { builder .field_getter("x", |_, this| Ok(this.x)) .field_getter("y", |_, this| Ok(this.y)) .field_setter("x", |_, this, x: f32| this.x = x) .field_setter("y", |_, this, y: f32| this.y = y) .function("new", |lua, (x, y)| { lua.create_userdata(Vec2 { x, y, }) }) // method test .method("add", |lua, lhs: &Vec2, (rhs,): (&Vec3,)| { let lx = lhs.x; let ly = lhs.y; let rx = rhs.x; let ry = rhs.y; lua.create_userdata(Vec2 { x: lx + rx, y: ly + ry, }) }) .meta_method(MetaMethod::Add, |lua, lhs: &Vec2, (rhs,): (&Vec2,)| { let lx = lhs.x; let ly = lhs.y; let rx = rhs.x; let ry = rhs.y; lua.create_userdata(Vec2 { x: lx + rx, y: ly + ry, }) }); Ok(()) } fn name() -> String { "Vec2".to_string() } } pub struct UserdataProxy(PhantomData); impl UserdataProxy { pub fn new() -> Self { Self(PhantomData) } } impl<'a, T: Userdata> AsLua<'a> for UserdataProxy { fn as_lua(&self, _lua: &'a State) -> crate::Result> { todo!() } } impl Userdata for UserdataProxy { fn build<'a>(builder: &mut UserdataBuilder<'a, Self>) -> crate::Result<()> { let mut other = UserdataBuilder::::new(); T::build(&mut other)?; // only the functions need to be added since they're the only thing usable from a proxy builder.functions = other.functions; Ok(()) } fn name() -> String { let name = format!("{}Proxy", T::name()); name } }