Compare commits

...

10 Commits

23 changed files with 1846 additions and 647 deletions

21
.vscode/launch.json vendored
View File

@ -40,6 +40,27 @@
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug a specific unit tests in executable 'lua-ffi'",
"cargo": {
"args": [
"test",
"--no-run",
"--package=lua-ffi",
"userdata::borrow::tests::ud_methods_borrow",
"--",
"--exact"
]/* ,
"filter": {
"name": "lua-ffi",
"kind": "bin"
} */
},
"args": [],
"cwd": "${workspaceFolder}"
}
]
}

18
.woodpecker/.debug.yml Executable file
View File

@ -0,0 +1,18 @@
variables:
- &rust_image 'rust:1.76-alpine'
steps:
Build - Debug:
image: *rust_image
commands:
- apt update
- apt install libudev-dev lua5.4 liblua5.4-dev -y
- cargo build
Test - Debug:
image: *rust_image
commands:
- apt update
- apt install libudev-dev lua5.4 liblua5.4-dev -y
- cargo test --all

20
.woodpecker/.release.yml Executable file
View File

@ -0,0 +1,20 @@
variables:
- &rust_image 'rust:1.76-alpine'
when:
- event: pull_request
steps:
Build - Release:
image: *rust_image
commands:
- apt update
- apt install libudev-dev lua5.4 liblua5.4-dev -y
- cargo build --release
Test - Release:
image: *rust_image
commands:
- apt update
- apt install libudev-dev lua5.4 liblua5.4-dev -y
- cargo test --all --release

19
Cargo.lock generated
View File

@ -18,13 +18,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cstr"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8aa998c33a6d3271e3678950a22134cd7dd27cef86dee1b611b5b14207d1d90b"
name = "elua"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"mlua-sys",
"thiserror",
]
[[package]]
@ -33,15 +31,6 @@ version = "0.2.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
[[package]]
name = "lua-ffi"
version = "0.1.0"
dependencies = [
"cstr",
"mlua-sys",
"thiserror",
]
[[package]]
name = "mlua-sys"
version = "0.5.0"

View File

@ -1,11 +1,10 @@
[package]
name = "lua-ffi"
name = "elua"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cstr = "0.2.11"
mlua-sys = { version = "0.5.0", features = ["lua54"] }
thiserror = "1.0.56"

9
README.md Executable file
View File

@ -0,0 +1,9 @@
# elua
The `elua` crate is meant to provide high-level bindings to [Lua 5.4](https://www.lua.org/manual/5.4/manual.html).
Although most of the crate is safe, there can be some unsafe aspects. Anytime something
could be unsafe, they are marked as so clearly in documentation.
## State
This library is still early in development. Although it provides bindings to most things needed
for embedding Lua into your Rust application, there has not been enough testing done to show that
it is stable. There also may be things missing, if so, please create an issue!

View File

@ -1,6 +1,6 @@
use std::borrow::{Borrow, Cow};
use crate::{AsLua, FromLua, Function, State};
use crate::{FromLua, Function, IntoLuaVec, State};
pub struct Chunk<'a> {
state: &'a State,
@ -38,7 +38,7 @@ impl<'a> Chunk<'a> {
/// Execute the chunk in the Lua context
pub fn execute<A, R>(&'a self, args: A) -> crate::Result<R>
where
A: AsLua<'a>,
A: IntoLuaVec<'a>,
R: FromLua<'a>
{
self.state.execute_chunk::<A, R>(self, args)

73
src/error.rs Executable file
View File

@ -0,0 +1,73 @@
use std::sync::Arc;
use mlua_sys as lua;
#[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")]
UserdataGc(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<String>,
arg_index: i32,
arg_name: Option<String>,
/// the error that describes what was wrong for this argument
#[source]
error: Arc<Error>
},
#[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<Error>,
},
}
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
pub type Result<T> = core::result::Result<T, Error>;

View File

@ -1,6 +1,6 @@
use std::sync::Arc;
use crate::{AsLua, FromLua, FromLuaStack, LuaRef, PushToLuaStack, StackGuard, State, Value, ValueVec};
use crate::{AsLua, FromLua, FromLuaStack, IntoLuaVec, LuaRef, PushToLuaStack, StackGuard, State, Value, ValueVec};
use mlua_sys as lua;
@ -38,19 +38,15 @@ impl<'a> Function<'a> {
pub fn exec<A, R>(&self, args: A) -> crate::Result<R>
where
A: AsLua<'a>,
A: IntoLuaVec<'a>,
R: FromLua<'a>,
{
unsafe {
let _g = StackGuard::new(self.state);
let s = self.state.state_ptr();
let args_val = args.as_lua(self.state)?;
let args_len = match args_val {
Value::Variable(v) => v.len(),
Value::None => 0,
_ => 1,
} as _;
let args_val = args.into_lua_value_vec(self.state)?;
let args_len = args_val.len() as _;
self.state.ensure_stack(2 + args_len)?;
@ -63,8 +59,6 @@ impl<'a> Function<'a> {
};
self.push_to_lua_stack(self.state)?;
let args_val = args.as_lua(self.state)?;
args_val.push_to_lua_stack(self.state)?;
match lua::lua_pcall(s, args_len, lua::LUA_MULTRET, handler_idx) {
@ -87,8 +81,10 @@ impl<'a> Function<'a> {
let val = if ret_count > 1 {
let vals = ValueVec::from_lua_stack(self.state)?;
Value::Variable(vals)
} else {
} else if ret_count == 1 {
Value::from_lua_stack(self.state)?
} else {
Value::None
};
R::from_lua(self.state, val)
@ -100,4 +96,10 @@ impl<'a> AsLua<'a> for Function<'a> {
fn as_lua(&self, _lua: &'a State) -> crate::Result<crate::Value<'a>> {
Ok(crate::Value::Function(self.clone()))
}
}
impl<'a> FromLua<'a> for Function<'a> {
fn from_lua(_lua: &'a State, val: Value<'a>) -> crate::Result<Self> {
val.into_function()
}
}

135
src/lib.rs Executable file
View File

@ -0,0 +1,135 @@
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::*;
pub mod error;
use error::*;
pub mod lref;
use lref::*;
#[cfg(test)]
pub mod tests;
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<Self>;
}
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<T> {
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<Value<'a>> {
Ok(Value::Number(*self as f64))
}
}
impl<'a> FromLua<'a> for $ty {
fn from_lua(_lua: &State, val: Value) -> crate::Result<Self> {
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)?;
self.as_str()
.push_to_lua_stack(state)?;
Ok(())
}
}
impl<'a> FromLua<'a> for String {
fn from_lua(_lua: &State, val: Value) -> crate::Result<Self> {
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 copies the string, so its okay if the string pointer is dropped
lua::lua_pushstring(state.state_ptr(), cstr);
Ok(())
}
}

65
src/lref.rs Executable file
View File

@ -0,0 +1,65 @@
use std::sync::Arc;
use crate::{Error, PushToLuaStack, Result, State};
use mlua_sys as lua;
/// 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> {
lref: Arc<i32>,
state: &'a State,
}
impl<'a> Drop for LuaRef<'a> {
fn drop(&mut self) {
unsafe {
let s = self.state.state_ptr();
if Arc::strong_count(&self.lref) == 1 {
lua::luaL_unref(s, lua::LUA_REGISTRYINDEX, *self.lref);
}
}
}
}
impl<'a> LuaRef<'a> {
pub fn new(lua_ref: i32, state: &'a State) -> Self {
Self {
lref: 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<Self> {
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.lref 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(())
}
}

View File

@ -1,465 +0,0 @@
use std::{cell::Ref, 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<i32> {
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::<Vec2>::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<i32>, &'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<Self> {
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<String>,
arg_index: i32,
arg_name: Option<String>,
/// the error that describes what was wrong for this argument
#[source]
error: Arc<Error>
},
#[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<Error>,
},
}
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<T> = core::result::Result<T, Error>;
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<Self>;
}
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<T> {
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<Value<'a>> {
Ok(Value::Number(*self as f64))
}
}
impl<'a> FromLua<'a> for $ty {
fn from_lua(_lua: &State, val: Value) -> crate::Result<Self> {
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<Self> {
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,): (Ref<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,): (Ref<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<T: Userdata>(PhantomData<T>);
impl<T: Userdata> UserdataProxy<T> {
pub fn new() -> Self {
Self(PhantomData)
}
}
impl<'a, T: Userdata> AsLua<'a> for UserdataProxy<T> {
fn as_lua(&self, _lua: &'a State) -> crate::Result<Value<'a>> {
todo!()
}
}
impl<T: Userdata> Userdata for UserdataProxy<T> {
fn build<'a>(builder: &mut UserdataBuilder<'a, Self>) -> crate::Result<()> {
let mut other = UserdataBuilder::<T>::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
}
}

View File

@ -1,9 +1,10 @@
use core::ffi;
use std::{alloc::{self, Layout}, any::TypeId, cell::RefCell, collections::HashMap, ffi::{CStr, CString}, mem, ptr::{self, NonNull}, str::Utf8Error, sync::Arc};
use lua::lua_gc;
use mlua_sys as lua;
use crate::{lua_error_guard, AnyUserdata, IntoChunkData, AsLua, Chunk, Error, FromLua, FromLuaStack, FromLuaVec, Function, LuaRef, MetaMethod, PushToLuaStack, Result, StackGuard, Table, Userdata, UserdataBuilder, Value, ValueVec};
use crate::{lua_error_guard, proxy::UserdataProxy, AnyUserdata, AsLua, Chunk, Error, FromLua, FromLuaStack, FromLuaVec, Function, IntoChunkData, IntoLuaVec, LuaRef, MetaMethod, PushToLuaStack, Result, StackGuard, Table, Userdata, UserdataBuilder, Value, ValueVec};
pub fn ptr_to_string(ptr: *const i8) -> std::result::Result<String, Utf8Error> {
let c = unsafe { CStr::from_ptr(ptr) };
@ -88,8 +89,8 @@ struct ClosureData<'a> {
}
#[derive(Default)]
struct ExtraSpace<'a> {
userdata_metatables: HashMap<TypeId, LuaRef<'a>>,
pub struct ExtraSpace<'a> {
pub userdata_metatables: HashMap<TypeId, LuaRef<'a>>,
}
pub struct State {
@ -101,10 +102,17 @@ impl Drop for State {
unsafe {
let extra = self.get_extra_space_ptr();
{
// clear the refs to anything in lua before we close it and
// attempt to drop extra after
let extra = &mut *extra;
extra.userdata_metatables.clear();
}
lua::lua_close(self.lua.as_ptr());
extra.drop_in_place();
// must be dealloced since it wasn't memory created from lua (like userdata)
// must be dealloced since it wasn't memory created from lua (i.e. userdata)
alloc::dealloc(extra.cast(), Layout::new::<ExtraSpace>());
}
}
@ -152,7 +160,7 @@ impl State {
}
}
fn get_extra_space(&self) -> &mut ExtraSpace {
pub fn get_extra_space(&self) -> &mut ExtraSpace {
unsafe {
self.get_extra_space_ptr().as_mut()
.expect("Somehow the Lua state's extra data got deleted!")
@ -212,7 +220,7 @@ impl State {
pub fn execute_chunk<'a, A, R>(&'a self, chunk: &'a Chunk, args: A) -> Result<R>
where
A: AsLua<'a>,
A: IntoLuaVec<'a>,
R: FromLua<'a>
{
let handler = self.create_function(|lua, msg: String| {
@ -431,7 +439,7 @@ impl State {
pub(crate) fn create_userdata_metatable<'a, T: Userdata + 'static>(&'a self) -> Result<Table<'a>> {
let mut builder = UserdataBuilder::<T>::new();
T::build(&mut builder)?;
T::build(self, &mut builder)?;
let getters = builder.field_getters;
let setters = builder.field_setters;
@ -496,22 +504,16 @@ impl State {
// if a Gc metamethod was not manually defined, set it
if !mt.raw_has_key(MetaMethod::Gc)? {
let gc_func = self.create_function(move |lua: &State, ud: AnyUserdata| {
let gc_func = self.create_function(|_lua, ud: AnyUserdata| {
unsafe {
// if this panics, there's a weird bug.
let ud_ptr = ud.as_ptr_unchecked::<T>().unwrap();
let ud_ptr = ud.as_ptr_unchecked::<T>()?;
ud_ptr.drop_in_place();
lua::luaL_unref(lua.state_ptr(), lua::LUA_REGISTRYINDEX, *ud.lref.0);
let extra = lua.get_extra_space();
extra.userdata_metatables.remove(&TypeId::of::<T>());
}
Ok(())
})?;
mt.set_meta(MetaMethod::Gc, gc_func)?;
mt.set(MetaMethod::Gc, gc_func)?;
}
let extra = self.get_extra_space();
@ -520,6 +522,13 @@ impl State {
Ok(mt)
}
/// Creates a userdata proxy. This proxy has the same metatable as `T`, but has no default value.
/// This eliminates the need for `T` to have a default value.
pub fn create_proxy<T: Userdata + 'static>(&self) -> Result<AnyUserdata> {
self.create_userdata(UserdataProxy::<T>::new())
}
/// Retrieves the debug info from the Lua stack.
pub(crate) unsafe fn debug_info(&self) -> Box<lua::lua_Debug> {
let s = self.state_ptr();
@ -533,6 +542,7 @@ impl State {
Box::from_raw(ar)
}
/// Retrieves the current line of the script
pub fn current_line(&self) -> usize {
unsafe {
let ar = self.debug_info();
@ -540,6 +550,7 @@ impl State {
}
}
/// Retrieves a traceback of the Lua stack, optionally prepends the traceback string with `msg`
pub fn traceback(&self, msg: Option<&str>) -> Result<String> {
unsafe {
let _g = StackGuard::new(self);
@ -576,4 +587,108 @@ impl State {
Ok(s)
}
}
/// Returns a boolean indiciating if the Lua garbage collector is running.
pub fn is_gc_running(&self) -> Result<bool> {
unsafe {
let s = self.state_ptr();
Ok(lua_gc(s, lua::LUA_GCISRUNNING) != 0)
}
}
/// Triggers a full garbage-collection cycle.
pub fn gc_collect(&self) {
unsafe {
let s = self.state_ptr();
lua_gc(s, lua::LUA_GCCOLLECT);
}
}
/// Disable the garbage collector
pub fn gc_stop(&self) {
unsafe {
let s = self.state_ptr();
lua_gc(s, lua::LUA_GCSTOP);
}
}
/// Restarts the garbage collector
pub fn gc_restart(&self) {
unsafe {
let s = self.state_ptr();
lua_gc(s, lua::LUA_GCRESTART);
}
}
/// Returns the current amount of memory used by Lua in kilabytes
pub fn memory_usage(&self) -> u32 {
unsafe {
let s = self.state_ptr();
lua_gc(s, lua::LUA_GCCOUNT) as u32
}
}
/// Returns the remainder of dividing the current amount of bytes of memory in use by Lua by 1024.
pub fn memory_usage_rem(&self) -> u32 {
unsafe {
let s = self.state_ptr();
lua_gc(s, lua::LUA_GCCOUNTB) as u32
}
}
/// Trigger a step of the garbage collector
///
/// Lua docs:
/// > Performs an incremental step of garbage collection, corresponding to the allocation
/// of stepsize Kbytes.
pub fn gc_step(&self, step_size: i32) {
unsafe {
let s = self.state_ptr();
lua_gc(s, lua::LUA_GCSTEP, step_size);
}
}
/// Changes the collector to incremental mode with the given parameters
/// (see [Incremental Garbage Collection](https://www.lua.org/manual/5.4/manual.html#2.5.1)).
/// Returns the previous mode.
pub fn gc_set_incremental(&self, pause: i32, step_mul: i32, step_size: i32) -> GcMode {
unsafe {
let s = self.state_ptr();
let mode = lua_gc(s, lua::LUA_GCINC, pause, step_mul, step_size);
if mode == lua::LUA_GCGEN {
GcMode::Generational
} else {
GcMode::Incremental
}
}
}
/// Changes the collector to generational mode with the given parameters
/// (see [Generational Garbage Collection](https://www.lua.org/manual/5.4/manual.html#2.5.2)).
/// Returns the previous mode.
pub fn gc_set_generational(&self, minor_mul: i32, major_mul: i32)-> GcMode {
unsafe {
let s = self.state_ptr();
let mode = lua_gc(s, lua::LUA_GCGEN, minor_mul, major_mul);
if mode == lua::LUA_GCGEN {
GcMode::Generational
} else {
GcMode::Incremental
}
}
}
}
pub enum GcMode {
Incremental,
Generational,
}

View File

@ -1,14 +1,13 @@
use mlua_sys as lua;
use crate::{ensure_type, FromLuaStack, LuaRef, PushToLuaStack, Result, StackGuard, State};
use crate::{ensure_type, AsLua, FromLua, FromLuaStack, LuaRef, PushToLuaStack, Result, StackGuard, State, Value};
#[derive(Clone)]
pub struct Table<'a> {
state: &'a State,
pub(crate) lref: LuaRef<'a>,
/// a boolean indicating if this Table is a Metatable
mt_marker: bool,
pub(crate) mt_marker: bool,
}
impl<'a> Table<'a> {
@ -83,8 +82,8 @@ impl<'a> Table<'a> {
/// This may trigger the `__newindex` metamethod, see [`Table::raw_set`]
pub fn set<K, V>(&self, key: K, val: V) -> Result<()>
where
K: PushToLuaStack<'a>,
V: PushToLuaStack<'a>
K: AsLua<'a>,
V: AsLua<'a>
{
let s = self.state.state_ptr();
unsafe {
@ -97,8 +96,10 @@ impl<'a> Table<'a> {
}
self.lref.push_to_lua_stack(self.state)?;
key.push_to_lua_stack(self.state)?;
val.push_to_lua_stack(self.state)?;
key.as_lua(self.state)?
.push_to_lua_stack(self.state)?;
val.as_lua(self.state)?
.push_to_lua_stack(self.state)?;
lua::lua_settable(s, -3);
}
@ -111,8 +112,8 @@ impl<'a> Table<'a> {
/// This may trigger the `__index` metamethod, see [`Table::raw_get`]
pub fn get<K, V>(&self, key: K) -> Result<V>
where
K: PushToLuaStack<'a>,
V: FromLuaStack<'a>,
K: AsLua<'a>,
V: FromLua<'a>,
{
let s = self.state.state_ptr();
unsafe {
@ -120,10 +121,13 @@ impl<'a> Table<'a> {
let _g = StackGuard::new(self.state);
self.lref.push_to_lua_stack(self.state)?;
key.push_to_lua_stack(self.state)?;
let val = key.as_lua(self.state)?;
val.push_to_lua_stack(self.state)?;
lua::lua_gettable(s, -2); // table[key] is at top of stack
let val = V::from_lua_stack(self.state)?;
let val = Value::from_lua_stack(self.state)?;
let val = V::from_lua(self.state, val)?;
Ok(val)
}
@ -150,7 +154,7 @@ impl<'a> Table<'a> {
/// Returns a boolean indicating if this table has a key
pub fn has_key<K>(&self, key: K) -> Result<bool>
where
K: PushToLuaStack<'a>,
K: AsLua<'a>,
{
let s = self.state.state_ptr();
unsafe {
@ -158,7 +162,8 @@ impl<'a> Table<'a> {
let _g = StackGuard::new(self.state);
self.lref.push_to_lua_stack(self.state)?;
key.push_to_lua_stack(self.state)?;
let key_val = key.as_lua(self.state)?;
key_val.push_to_lua_stack(self.state)?;
// table[key] is at top of stack
if lua::lua_gettable(s, -2) == lua::LUA_TNIL {
@ -172,8 +177,8 @@ impl<'a> Table<'a> {
/// Set a key to a value in the table without calling any meta methods.
pub fn raw_set<K, V>(&self, key: K, val: V) -> Result<()>
where
K: PushToLuaStack<'a>,
V: PushToLuaStack<'a>
K: AsLua<'a>,
V: AsLua<'a>
{
let s = self.state.state_ptr();
unsafe {
@ -181,8 +186,10 @@ impl<'a> Table<'a> {
let _g = StackGuard::new(self.state);
self.lref.push_to_lua_stack(self.state)?;
key.push_to_lua_stack(self.state)?;
val.push_to_lua_stack(self.state)?;
key.as_lua(self.state)?
.push_to_lua_stack(self.state)?;
val.as_lua(self.state)?
.push_to_lua_stack(self.state)?;
lua::lua_rawset(s, -3);
}
@ -191,10 +198,10 @@ impl<'a> Table<'a> {
}
/// Get a value from the table without calling any meta methods.
pub fn raw_get<K, V>(&'a self, key: K) -> Result<V>
pub fn raw_get<K, V>(&self, key: K) -> Result<V>
where
K: PushToLuaStack<'a>,
V: FromLuaStack<'a>,
K: AsLua<'a>,
V: FromLua<'a>,
{
let s = self.state.state_ptr();
unsafe {
@ -202,9 +209,13 @@ impl<'a> Table<'a> {
let _g = StackGuard::new(self.state);
self.lref.push_to_lua_stack(self.state)?;
key.push_to_lua_stack(self.state)?;
key.as_lua(self.state)?
.push_to_lua_stack(self.state)?;
lua::lua_rawget(s, -2); // table[key] is at top of stack
V::from_lua_stack(self.state)
let val = Value::from_lua_stack(self.state)?;
V::from_lua(self.state, val)
}
}
@ -226,7 +237,7 @@ impl<'a> Table<'a> {
/// Returns a boolean indicating if this table has a key without calling any meta methods.
pub fn raw_has_key<K>(&self, key: K) -> Result<bool>
where
K: PushToLuaStack<'a>,
K: AsLua<'a>,
{
let s = self.state.state_ptr();
unsafe {
@ -234,7 +245,9 @@ impl<'a> Table<'a> {
let _g = StackGuard::new(self.state);
self.lref.push_to_lua_stack(self.state)?;
key.push_to_lua_stack(self.state)?;
key.as_lua(self.state)?
.push_to_lua_stack(self.state)?;
// table[key] is at top of stack
Ok(lua::lua_rawget(s, -2) != lua::LUA_TNIL)
}
@ -245,8 +258,8 @@ impl<'a> Table<'a> {
/// Does nothing if this table is not a metatable
pub fn set_meta<K, V>(&self, key: K, val: V) -> Result<()>
where
K: PushToLuaStack<'a>,
V: PushToLuaStack<'a>
K: AsLua<'a>,
V: AsLua<'a>
{
if self.mt_marker {
unsafe {
@ -255,14 +268,24 @@ impl<'a> Table<'a> {
let _g = StackGuard::new(self.state);
self.lref.push_to_lua_stack(self.state)?;
key.push_to_lua_stack(self.state)?;
val.push_to_lua_stack(self.state)?;
key.as_lua(self.state)?
.push_to_lua_stack(self.state)?;
val.as_lua(self.state)?
.push_to_lua_stack(self.state)?;
lua::lua_settable(s, -3);
}
}
Ok(())
}
pub fn try_into<T: TableProxy>(self) -> Result<T> {
T::from_table(self.state, self)
}
pub fn try_from<T: TableProxy + Sized>(state: &'a State, proxy: T) -> Result<Table<'a>> {
proxy.as_table(state)
}
}
impl<'a> PushToLuaStack<'a> for Table<'a> {
@ -279,4 +302,282 @@ impl<'a> FromLuaStack<'a> for Table<'a> {
ensure_type(state, lua::LUA_TTABLE, -1)?;
Table::with_ref(state, LuaRef::from_stack(state)?, false)
}
}
impl<'a> FromLua<'a> for Table<'a> {
fn from_lua(_: &'a State, val: Value<'a>) -> crate::Result<Self> {
val.into_table()
}
}
impl<'a> AsLua<'a> for Table<'a> {
fn as_lua(&self, _: &'a State) -> crate::Result<Value<'a>> {
Ok(Value::Table(self.clone()))
}
}
/// This trait is used for proxying a Rust type to and from Lua as a Lua table. In Lua, you can
/// make a representation of your Rust type, then implement this trait on the Rust type. This
/// trait allows you to put your Rust type into Lua as the type you created in Lua, and you can
/// also retrieve an instance of your Rust type from an instance of the Lua type. This could help
/// with minimizing the amount of calls to and from Rust.
pub trait TableProxy: Sized {
/// Create an instance of `Self` from the table
fn from_table<'a>(lua: &'a State, table: Table<'a>) -> Result<Self>;
/// Creates a Lua instance from `Self`
fn as_table<'a>(&self, state: &'a State) -> Result<Table<'a>>;
}
/// A struct that wraps a Proxied value. It can be used to get
pub struct Proxy<T: TableProxy> {
data: Option<T>,
}
impl<T: TableProxy> Proxy<T> {
pub fn new() -> Self {
Self {
data: None,
}
}
/// Retrieves the data from the proxy, if there is no data, it will panic.
pub fn take(self) -> T {
self.data
.expect("the proxy was not provided any data")
}
/// Retrieves the data from the proxy, returns `None` if the Proxy has no data.
pub fn try_take(self) -> Option<T> {
self.data
}
}
impl<T: TableProxy> From<T> for Proxy<T> {
fn from(value: T) -> Self {
Self {
data: Some(value),
}
}
}
// should proxying from lua be implicit by implementing this trait for T, instead of Proxy?
// it would make it easier, but its more difficult to tell if code is proxying from lua, or
// getting a borrow.
impl<'a, T: TableProxy> FromLua<'a> for Proxy<T> {
fn from_lua(lua: &'a State, val: Value<'a>) -> crate::Result<Self> {
let table = val.into_table()?;
let t = T::from_table(lua, table)?;
Ok(Self::from(t))
}
}
impl<'a, T: TableProxy> AsLua<'a> for Proxy<T> {
fn as_lua(&self, lua: &'a State) -> crate::Result<Value<'a>> {
self.data.as_ref()
.ok_or(crate::Error::Nil)
.and_then(|d| d.as_table(lua))
.map(|t| Value::Table(t))
}
}
#[cfg(test)]
mod tests {
use crate::{tests::Vec2, Function, Proxy, State, StdLibrary, Table, TableProxy};
impl TableProxy for Vec2 {
fn from_table<'a>(_state: &'a crate::State, table: crate::Table<'a>) -> crate::Result<Self> {
let x: f32 = table.get("x")?;
let y: f32 = table.get("y")?;
Ok(Vec2 {
x,
y,
})
}
fn as_table<'a>(&self, state: &'a crate::State) -> crate::Result<crate::Table<'a>> {
let globals = state.globals()?;
let vec2: Table = globals.get("Vec2")?;
let new_fn: Function = vec2.get("new")?;
new_fn.exec((vec2, self.x, self.y))
}
}
#[test]
fn table_get() -> crate::Result<()> {
let lua = State::new();
lua.expose_libraries(&[StdLibrary::Debug, StdLibrary::Package]);
let res = lua.load(
"test.lua",
r#"
text = "Hello, World"
"#)?.execute::<_, ()>(());
// pretty print the error
if let Err(err) = res {
panic!("{}", err);
}
let globals = lua.globals()?;
let text: String = globals.get("text")?;
assert_eq!(text, "Hello, World".to_string());
Ok(())
}
#[test]
fn table_set() -> crate::Result<()> {
let lua = State::new();
lua.expose_libraries(&[StdLibrary::Debug, StdLibrary::Package]);
let globals = lua.globals()?;
globals.set("text", "Hello, World")?;
let res = lua.load(
"test.lua",
r#"
assert(text == "Hello, World", "The text was not set correctly")
"#)?.execute::<_, ()>(());
// pretty print the error
if let Err(err) = res {
panic!("{}", err);
}
Ok(())
}
#[test]
fn table_proxy() -> crate::Result<()> {
let lua = State::new();
lua.expose_libraries(&[StdLibrary::Debug, StdLibrary::Package]);
let res = lua.load(
"test.lua",
r#"
Vec2 = { x = 0.0, y = 0.0 }
Vec2.__index = Vec2
Vec2.__name = "Vec2"
function Vec2:new(x, y)
local v = {}
setmetatable(v, Vec2)
v.x = x
v.y = y
return v
end
function Vec2:__tostring()
return "Vec2(" .. self.x .. ", " .. self.y .. ")"
end
function do_math()
return Vec2:new(15, 20)
end
"#)?.execute::<_, ()>(());
// pretty print the error
if let Err(err) = res {
panic!("{}", err);
}
let globals = lua.globals()?;
let math_fn: Function = globals.get("do_math")?;
let lua_vec2: Table = math_fn.exec(())?;
let mut vec2 = Vec2::from_table(&lua, lua_vec2)?;
assert_eq!(vec2.x, 15.0);
assert_eq!(vec2.y, 20.0);
vec2.x *= 2.0;
vec2.y *= 2.0;
globals.set("pos", vec2.as_table(&lua)?)?;
let res = lua.load(
"test.lua",
r#"
-- Vec2 stuff is included from last chunk
assert(type(pos) == "table", "The global 'pos' is not a table like expected of 'as_table'!!")
print("pos is " .. tostring(pos))
"#)?.execute::<_, ()>(());
// pretty print the error
if let Err(err) = res {
panic!("{}", err);
}
Ok(())
}
#[test]
fn table_proxy_value() -> crate::Result<()> {
let lua = State::new();
lua.expose_libraries(&[StdLibrary::Debug, StdLibrary::Package]);
let globals = lua.globals()?;
let add_fn = lua.create_function(|_, (a, b): (Proxy<Vec2>, Proxy<Vec2>)| {
let a = a.take();
let b = b.take();
let x = a.x + b.x;
let y = a.y + b.y;
Ok(Proxy::from(
Vec2 {
x,
y
}
))
})?;
globals.set("add_vec2", add_fn)?;
let res = lua.load(
"test.lua",
r#"
require("util")
Vec2 = { x = 0.0, y = 0.0 }
Vec2.__index = Vec2
Vec2.__name = "Vec2"
function Vec2:new(x, y)
local v = {}
setmetatable(v, Vec2)
v.x = x
v.y = y
return v
end
function Vec2:__tostring()
return "Vec2(" .. self.x .. ", " .. self.y .. ")"
end
function do_math()
return Vec2:new(15, 20)
end
local v1 = Vec2:new(15, 20)
local v2 = Vec2:new(7, 10)
local v3 = add_vec2(v1, v2)
assert(v3.x == 22 and v3.y == 30, "The result from adding the values was incorrect!")
--print("v3 is " .. dump_table(v3))
--print("Added together, v3 is " .. tostring(v3))
"#)?.execute::<_, ()>(());
// pretty print the error
if let Err(err) = res {
panic!("{}", err);
}
Ok(())
}
}

62
src/tests.rs Executable file
View File

@ -0,0 +1,62 @@
use std::cell::Ref;
use crate::{MetaMethod, State, Userdata, UserdataBuilder};
pub struct Vec2 {
pub x: f32,
pub y: f32,
}
impl Userdata for Vec2 {
fn build<'a>(_state: &State, 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,): (Ref<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,
})
})
.method_mut("mult", |_, this: &mut Vec2, scalar: f32| {
this.x *= scalar;
this.y *= scalar;
Ok(())
})
.method("mult_ret", |lua, this: &Vec2, scalar: f32| {
lua.create_userdata(Vec2 {
x: this.x * scalar,
y: this.y * scalar,
})
})
.meta_method(MetaMethod::Add, |lua, lhs: &Vec2, (rhs,): (Ref<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()
}
}

168
src/userdata/borrow.rs Executable file
View File

@ -0,0 +1,168 @@
use std::{cell::Ref, mem, ops::Deref};
use crate::{AnyUserdata, Error, Result, State, Userdata, UserdataBuilder};
enum Borrow<'a, T> {
Wrapped(Ref<'a, T>),
Raw(&'a T),
}
impl<'a, T> Deref for Borrow<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match self {
Borrow::Wrapped(w) => w,
Borrow::Raw(w) => w,
}
}
}
pub struct UserdataRef<'a, T: Userdata> {
borrow: Borrow<'a, T>,
}
impl<'a, T: Userdata> From<&'a T> for UserdataRef<'static, T> {
fn from(value: &'a T) -> Self {
let ud = Borrow::Raw(value);
Self {
borrow: unsafe {
mem::transmute(ud)
}
}
}
}
impl<'a, T: Userdata> From<Ref<'a, T>> for UserdataRef<'static, T> {
fn from(value: Ref<'a, T>) -> Self {
let ud = Borrow::Wrapped(value);
Self {
borrow: unsafe {
mem::transmute(ud)
},
}
}
}
impl<'a, T: Userdata + 'static> Userdata for UserdataRef<'a, T> {
fn build<'b>(state: &State, builder: &mut UserdataBuilder<'b, Self>) -> crate::Result<()> {
let mut other = UserdataBuilder::<T>::new();
T::build(state, &mut other)?;
builder.expand_with(other);
let getter: fn(AnyUserdata<'_>) -> Result<*const ()> = move |ud: AnyUserdata| {
let ud_ptr = {
let ud = ud.as_ref::<UserdataRef<T>>()?;
let ud_ptr: *const T = &*ud.borrow;
ud_ptr
};
Ok(ud_ptr.cast::<()>())
};
let mut_getter: fn(AnyUserdata<'_>) -> Result<*mut ()> = move |_ud: AnyUserdata| {
Err(Error::Runtime(format!("cannot mutably access '{}' when its behind a non mutable reference!", T::name())))
};
if builder.wrapped_getter.set(Box::new(getter)).is_err() {
panic!("Somehow the wrapped getter has already been set");
}
if builder.wrapped_getter_mut.set(Box::new(mut_getter)).is_err() {
panic!("Somehow the wrapped mutable getter has already been set");
}
Ok(())
}
fn name() -> String {
let name = format!("{}Ref", T::name());
name
}
}
#[cfg(test)]
mod tests {
use std::cell::{Ref, RefCell};
use crate::{tests::Vec2, State, StdLibrary, Value};
use super::UserdataRef;
/// This test ensures that a Ref of userdata can be provided to Lua, and it that it can access fields on the userdata.
#[test]
fn ud_fields_borrow() -> crate::Result<()> {
let lua = State::new();
lua.expose_libraries(&[StdLibrary::Debug, StdLibrary::Package]);
let globals = lua.globals()?;
let v1 = RefCell::new(Vec2 { x: 50.0, y: 5.0 });
let ud = lua.create_userdata(UserdataRef::from(v1.borrow()))?;
globals.set("v1", ud)?;
let chunk = lua.load(
"text.lua",
r#"
print("v1: (" .. v1.x .. ", " .. v1.y .. ")")
"#)?;
for _ in 0..40 {
let res = lua.execute_chunk::<_, Value>(&chunk, ());
if let Err(e) = res {
panic!("{}", e);
}
//println!("i = {}", i);
globals.set("v1", Value::Nil)?;
lua.gc_collect(); // must collect here to drop the Ref
let mut t = v1.borrow_mut();
t.x += 50.0;
t.y += 5.0;
drop(t);
let ud = lua.create_userdata(UserdataRef::from(v1.borrow()))?;
globals.raw_set("v1", ud)?;
}
Ok(())
}
/// This test ensures that a raw borrow of userdata can be provided to Lua, and it that it can run non-mutating methods on the userdata.
#[test]
fn ud_methods_borrow() -> crate::Result<()> {
let lua = State::new();
lua.expose_libraries(&[StdLibrary::Debug, StdLibrary::Package]);
let globals = lua.globals()?;
let v1 = RefCell::new(Vec2 { x: 50.0, y: 5.0 });
let ud = lua.create_userdata(UserdataRef::from(v1.borrow()))?;
globals.set("v1", ud)?;
let chunk = lua.load(
"text.lua",
r#"
v2 = v1:mult_ret(2.0)
print("v2: (" .. v2.x .. ", " .. v2.y .. ")")
"#)?;
let res = lua.execute_chunk::<_, Value>(&chunk, ());
if let Err(e) = res {
panic!("{}", e);
}
let v2 = globals.get::<_, Ref<Vec2>>("v2")?;
assert_eq!(v2.x, 100.0);
assert_eq!(v2.y, 10.0);
Ok(())
}
}

195
src/userdata/borrow_mut.rs Executable file
View File

@ -0,0 +1,195 @@
use std::{cell::RefMut, mem, ops::{Deref, DerefMut}};
use crate::{AnyUserdata, Result, State, Userdata, UserdataBuilder};
enum BorrowMut<'a, T> {
Wrapped(RefMut<'a, T>),
Raw(&'a mut T),
}
impl<'a, T> Deref for BorrowMut<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match self {
BorrowMut::Wrapped(w) => w,
BorrowMut::Raw(w) => w,
}
}
}
impl<'a, T> DerefMut for BorrowMut<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
match self {
BorrowMut::Wrapped(w) => w,
BorrowMut::Raw(w) => w,
}
}
}
pub struct UserdataRefMut<'a, T: Userdata> {
borrow: BorrowMut<'a, T>,
}
impl<'a, T: Userdata> From<&'a mut T> for UserdataRefMut<'static, T> {
fn from(value: &'a mut T) -> Self {
let ud = BorrowMut::Raw(value);
Self {
borrow: unsafe {
mem::transmute(ud)
}
}
}
}
impl<'a, T: Userdata> From<RefMut<'a, T>> for UserdataRefMut<'static, T> {
fn from(value: RefMut<'a, T>) -> Self {
let ud = BorrowMut::Wrapped(value);
Self {
borrow: unsafe {
mem::transmute(ud)
},
}
}
}
impl<'a, T: Userdata + 'static> Userdata for UserdataRefMut<'a, T> {
fn build<'b>(state: &State, builder: &mut UserdataBuilder<'b, Self>) -> crate::Result<()> {
let mut other = UserdataBuilder::<T>::new();
T::build(state, &mut other)?;
builder.expand_with(other);
let getter: fn(AnyUserdata<'_>) -> Result<*const ()> = move |ud: AnyUserdata| {
let ud_ptr = {
let ud = ud.as_ref::<UserdataRefMut<T>>()?;
let ud_ptr: *const T = &*ud.borrow;
ud_ptr
};
Ok(ud_ptr.cast::<()>())
};
let getter_mut: fn(AnyUserdata<'_>) -> Result<*mut ()> = move |ud: AnyUserdata| {
let ud_ptr = {
let mut ud = ud.as_mut::<UserdataRefMut<T>>()?;
let ud_ptr: *mut T = &mut *ud.borrow;
ud_ptr
};
Ok(ud_ptr.cast::<()>())
};
if builder.wrapped_getter.set(Box::new(getter)).is_err() {
panic!("Somehow the wrapped getter has already been set");
}
if builder.wrapped_getter_mut.set(Box::new(getter_mut)).is_err() {
panic!("Somehow the wrapped mutable getter has already been set");
}
Ok(())
}
fn name() -> String {
let name = format!("{}RefMut", T::name());
name
}
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use crate::{tests::Vec2, State, StdLibrary, Value};
use super::UserdataRefMut;
/// This test ensures that you can provide Lua a RefMut and it that it can set the fields on the userdata.
#[test]
fn ud_fields_borrow_mut() -> crate::Result<()> {
let lua = State::new();
lua.expose_libraries(&[StdLibrary::Debug, StdLibrary::Package]);
let globals = lua.globals()?;
let v1 = RefCell::new(Vec2 { x: 50.0, y: 5.0 });
let ud = lua.create_userdata(UserdataRefMut::from(v1.borrow_mut()))?;
globals.set("v1", ud)?;
let mut x = 50.0;
let mut y = 5.0;
let chunk = lua.load(
"text.lua",
r#"
v1.x = v1.x + 50
v1.y = v1.y + 5
print("v1: (" .. v1.x .. ", " .. v1.y .. ")")
"#)?;
for _ in 0..40 {
let res = lua.execute_chunk::<_, Value>(&chunk, ());
if let Err(e) = res {
panic!("{}", e);
}
//println!("i = {}", i);
globals.set("v1", Value::Nil)?;
lua.gc_collect(); // must collect here to drop the RefMut
x += 50.0;
y += 5.0;
let t = v1.borrow();
assert_eq!(x, t.x);
assert_eq!(y, t.y);
drop(t);
let ud = lua.create_userdata(UserdataRefMut::from(v1.borrow_mut()))?;
globals.raw_set("v1", ud)?;
}
Ok(())
}
/// This test ensures that a RefMut of userdata can be provided to Lua, and it that it can run mutating methods on the userdata.
#[test]
fn ud_methods_borrow_mut() -> crate::Result<()> {
let lua = State::new();
lua.expose_libraries(&[StdLibrary::Debug, StdLibrary::Package]);
let globals = lua.globals()?;
let v1 = RefCell::new(Vec2 { x: 50.0, y: 5.0 });
let ud = lua.create_userdata(UserdataRefMut::from(v1.borrow_mut()))?;
globals.set("v1", ud)?;
let chunk = lua.load(
"text.lua",
r#"
v1:mult(2.0)
print("v1: (" .. v1.x .. ", " .. v1.y .. ")")
"#)?;
let res = lua.execute_chunk::<_, Value>(&chunk, ());
if let Err(e) = res {
panic!("{}", e);
}
globals.set("v1", Value::Nil)?;
lua.gc_collect(); // must collect here to drop the RefMut
let t = v1.borrow();
assert_eq!(100.0, t.x);
assert_eq!(10.0, t.y);
Ok(())
}
}

0
src/userdata/convert.rs Executable file
View File

View File

@ -1,11 +1,24 @@
use std::{borrow::Borrow, cell::{Ref, RefCell, RefMut}, collections::HashMap, ffi::CStr, marker::PhantomData, ops::{Deref, DerefMut}};
use std::{cell::{OnceCell, Ref, RefCell, RefMut}, collections::HashMap, ffi::CStr, marker::PhantomData, mem, ops::DerefMut, sync::Arc};
use crate::{ensure_type, AsLua, FromLua, FromLuaStack, FromLuaVec, LuaRef, PushToLuaStack, StackGuard, State, Value, ValueVec};
use crate::{ensure_type, AsLua, FromLua, FromLuaStack, FromLuaVec, Function, IntoLuaVec, LuaRef, PushToLuaStack, StackGuard, State, Table, Value, ValueVec};
use mlua_sys as lua;
//pub type FieldSetter<T> = fn(lua: &State, this: &T);
//pub type FieldGetter<T, U> = fn(lua: &State, this: &T, val: &U);
pub mod unsafe_ud;
pub use unsafe_ud::*;
pub mod proxy;
#[allow(unused_imports)]
use proxy::*;
pub mod borrow;
#[allow(unused_imports)]
use borrow::*;
pub mod borrow_mut;
#[allow(unused_imports)]
use borrow_mut::*;
/// An enum representing all Lua MetaMethods
/// https://gist.github.com/oatmealine/655c9e64599d0f0dd47687c1186de99f
@ -92,15 +105,9 @@ impl<'a> PushToLuaStack<'a> for MetaMethod {
}
}
pub trait FieldSetter {
fn set_field(&self, val: Value);
}
pub trait FieldGetter {
fn get_field(&self) -> Value;
}
type UserdataFn<'a> = Box<dyn Fn(&'a State, ValueVec<'a>) -> crate::Result<Value<'a>>>;
pub type UserdataFn<'a> = Box<dyn Fn(&'a State, ValueVec<'a>) -> crate::Result<Value<'a>>>;
pub type UserdataGetterFn<'a> = Arc<OnceCell<Box<dyn Fn(AnyUserdata<'a>) -> crate::Result<*const ()>>>>;
pub type UserdataMutGetterFn<'a> = Arc<OnceCell<Box<dyn Fn(AnyUserdata<'a>) -> crate::Result<*mut ()>>>>;
pub struct UserdataBuilder<'a, T> {
pub(crate) name: String,
@ -108,6 +115,10 @@ pub struct UserdataBuilder<'a, T> {
pub(crate) field_setters: HashMap<String, UserdataFn<'a>>,
pub(crate) functions: HashMap<String, UserdataFn<'a>>,
pub(crate) meta_methods: HashMap<String, UserdataFn<'a>>,
pub(crate) wrapped_getter: Arc<OnceCell<Box<dyn Fn(AnyUserdata<'a>) -> crate::Result<*const ()>>>>,
pub(crate) wrapped_getter_mut: Arc<OnceCell<Box<dyn Fn(AnyUserdata<'a>) -> crate::Result<*mut ()>>>>,
_marker: PhantomData<T>,
}
@ -119,6 +130,8 @@ impl<'a, T: Userdata> UserdataBuilder<'a, T> {
field_setters: HashMap::new(),
functions: HashMap::new(),
meta_methods: HashMap::new(),
wrapped_getter: Arc::new(OnceCell::new()),
wrapped_getter_mut: Arc::new(OnceCell::new()),
_marker: PhantomData,
}
}
@ -129,11 +142,32 @@ impl<'a, T: Userdata> UserdataBuilder<'a, T> {
R: AsLua<'a>,
T: Userdata + 'static
{
let wrapped = self.wrapped_getter.clone();
let wrapped: Arc<OnceCell<Box<dyn Fn(AnyUserdata<'_>) -> Result<*const (), crate::Error>>>> = unsafe { mem::transmute(wrapped) };
let ud_name = self.name.clone();
let fn_name = name.to_string();
let wrap = move |lua: &'a State, mut val: ValueVec<'a>| {
let val = val.pop_front().unwrap();
let this = val.as_userdata().unwrap(); // if this panics, its a bug
let this = this.as_ref::<T>()?;
f(lua, &*this).and_then(|r| r.as_lua(lua))
if let Some(getter) = wrapped.get() {
let this_ptr = Self::result_to_bad_arg(
getter(this.clone()),
&ud_name,
&fn_name,
1,
Some("self")
)?;
let this_ptr = this_ptr.cast::<T>();
let this = unsafe { &*this_ptr };
f(lua, this).and_then(|r| r.as_lua(lua))
} else {
let this = this.as_ref::<T>()?;
f(lua, &*this).and_then(|r| r.as_lua(lua))
}
};
self.field_getters.insert(name.to_string(), Box::new(wrap));
@ -146,22 +180,43 @@ impl<'a, T: Userdata> UserdataBuilder<'a, T> {
V: FromLua<'a>,
T: Userdata + 'static
{
let wrapped = self.wrapped_getter_mut.clone();
let wrapped: Arc<OnceCell<Box<dyn Fn(AnyUserdata<'_>) -> Result<*mut (), crate::Error>>>> = unsafe { mem::transmute(wrapped) };
let ud_name = self.name.clone();
let fn_name = name.to_string();
let wrap = move |lua: &'a State, mut val: ValueVec<'a>| {
let lua_val = val.pop_front().unwrap();
let this = lua_val.as_userdata().unwrap(); // if this panics, its a bug
let mut this = this.as_mut::<T>()?;
let lua_val = val.pop_front().unwrap();
let v_arg = V::from_lua(lua, lua_val)?;
f(lua, this.deref_mut(), v_arg).as_lua(lua)
if let Some(mut_getter) = wrapped.get() {
let this_ptr = Self::result_to_bad_arg(
mut_getter(this.clone()),
&ud_name,
&fn_name,
1,
Some("self")
)?;
let this_ptr = this_ptr.cast::<T>();
let this = unsafe { this_ptr.as_mut().unwrap() };
f(lua, this, v_arg).as_lua(lua)
} else {
let mut this = this.as_mut::<T>()?;
f(lua, this.deref_mut(), v_arg).as_lua(lua)
}
};
self.field_setters.insert(name.to_string(), Box::new(wrap));
self
}
fn result_to_bad_arg<R>(res: crate::Result<R>, ud_name: &str, fn_name: &str) -> crate::Result<R> {
fn arg_result_to_bad_arg<R>(res: crate::Result<R>, ud_name: &str, fn_name: &str) -> crate::Result<R> {
res.map_err(|e| match e {
crate::Error::ValueVecError { value_idx, error } => {
let full_name = format!("{}.{}", ud_name, fn_name);
@ -173,17 +228,27 @@ impl<'a, T: Userdata> UserdataBuilder<'a, T> {
})
}
fn result_to_bad_arg<R>(res: crate::Result<R>, ud_name: &str, fn_name: &str, arg_idx: i32, arg_name: Option<&str>) -> crate::Result<R> {
res.map_err(|e| {
let full_name = format!("{}.{}", ud_name, fn_name);
let arg_name = arg_name.map(|s| s.to_string());
crate::Error::BadArgument { func: Some(full_name), arg_index: arg_idx, arg_name, error: Arc::new(e), }
})
}
pub fn function<F, R, A>(&mut self, name: &str, f: F) -> &mut Self
where
F: Fn(&'a State, A) -> crate::Result<R> + 'static,
A: FromLuaVec<'a>,
R: AsLua<'a>,
{
let ud_name = self.name.clone();
let fn_name = name.to_string();
let wrap = move |lua: &'a State, val: ValueVec<'a>| {
let args = Self::result_to_bad_arg(
let args = Self::arg_result_to_bad_arg(
A::from_lua_value_vec(lua, val),
&T::name(), &fn_name
&ud_name, &fn_name
)?;
f(lua, args).and_then(|r| r.as_lua(lua))
};
@ -199,19 +264,83 @@ impl<'a, T: Userdata> UserdataBuilder<'a, T> {
R: AsLua<'a>,
T: Userdata + 'static
{
let wrapped = self.wrapped_getter.clone();
let wrapped: Arc<OnceCell<Box<dyn Fn(AnyUserdata<'_>) -> Result<*const (), crate::Error>>>> = unsafe { mem::transmute(wrapped) };
let ud_name = self.name.clone();
let fn_name = name.to_string();
let wrap = move |lua: &'a State, mut val: ValueVec<'a>| {
let this_val = val.pop_front().unwrap();
let this = this_val.as_userdata().unwrap(); // if this panics, its a bug
let this = this.as_ref::<T>()?;
let this_name = T::name();
let args = Self::result_to_bad_arg(
let args = Self::arg_result_to_bad_arg(
A::from_lua_value_vec(lua, val),
&this_name, &fn_name
&ud_name, &fn_name
)?;
f(lua, &*this, args).and_then(|r| r.as_lua(lua))
if let Some(mut_getter) = wrapped.get() {
let this_ptr = Self::result_to_bad_arg(
mut_getter(this.clone()),
&ud_name,
&fn_name,
1,
Some("self")
)?;
let this_ptr = this_ptr.cast::<T>();
let this = unsafe { this_ptr.as_ref().unwrap() };
f(lua, this, args).and_then(|r| r.as_lua(lua))
} else {
let this = this.as_ref::<T>()?;
f(lua, &*this, args).and_then(|r| r.as_lua(lua))
}
};
self.functions.insert(name.to_string(), Box::new(wrap));
self
}
pub fn method_mut<F, R, A>(&mut self, name: &str, f: F) -> &mut Self
where
F: Fn(&'a State, &mut T, A) -> crate::Result<R> + 'static,
A: FromLuaVec<'a>,
R: AsLua<'a>,
T: Userdata + 'static
{
let wrapped = self.wrapped_getter_mut.clone();
let wrapped: Arc<OnceCell<Box<dyn Fn(AnyUserdata<'_>) -> Result<*mut (), crate::Error>>>> = unsafe { mem::transmute(wrapped) };
let ud_name = self.name.clone();
let fn_name = name.to_string();
let wrap = move |lua: &'a State, mut val: ValueVec<'a>| {
let this_val = val.pop_front().unwrap();
let this = this_val.as_userdata().unwrap(); // if this panics, its a bug
let args = Self::arg_result_to_bad_arg(
A::from_lua_value_vec(lua, val),
&ud_name, &fn_name
)?;
if let Some(mut_getter) = wrapped.get() {
let this_ptr = Self::result_to_bad_arg(
mut_getter(this.clone()),
&ud_name,
&fn_name,
1,
Some("self")
)?;
let this_ptr = this_ptr.cast::<T>();
let this = unsafe { this_ptr.as_mut().unwrap() };
f(lua, this, args).and_then(|r| r.as_lua(lua))
} else {
let mut this = this.as_mut::<T>()?;
f(lua, this.deref_mut(), args).and_then(|r| r.as_lua(lua))
}
};
self.functions.insert(name.to_string(), Box::new(wrap));
@ -226,16 +355,16 @@ impl<'a, T: Userdata> UserdataBuilder<'a, T> {
R: AsLua<'a>,
T: Userdata + 'static
{
let ud_name = self.name.clone();
let fn_name = name.as_ref().to_string();
let wrap = move |lua: &'a State, mut val: ValueVec<'a>| {
let this_val = val.pop_front().unwrap();
let this = this_val.as_userdata().unwrap(); // if this panics, its a bug
let this = this.as_ref::<T>()?;
let this_name = T::name();
let args = Self::result_to_bad_arg(
let args = Self::arg_result_to_bad_arg(
A::from_lua_value_vec(lua, val),
&this_name, &fn_name
&ud_name, &fn_name
)?;
f(lua, &*this, args).and_then(|r| r.as_lua(lua))
@ -244,78 +373,61 @@ impl<'a, T: Userdata> UserdataBuilder<'a, T> {
self
}
//fn append_fields_from<S>(&mut self, other: UserDataRegistry<'lua, S>) {
pub fn expand_with<U>(&mut self, other: UserdataBuilder<'a, U>) {
self.field_getters = other.field_getters;
self.field_setters = other.field_setters;
self.functions = other.functions;
self.meta_methods = other.meta_methods;
self.wrapped_getter = other.wrapped_getter;
self.wrapped_getter_mut = other.wrapped_getter_mut;
}
}
pub trait Userdata: Sized {
fn name() -> String;
fn build<'a>(builder: &mut UserdataBuilder<'a, Self>) -> crate::Result<()>;
fn build<'a>(state: &State, builder: &mut UserdataBuilder<'a, Self>) -> crate::Result<()>;
}
/// A handle to some userdata on the stack
#[derive(Clone)]
//#[derive(Clone)]
pub struct AnyUserdata<'a> {
pub(crate) lref: LuaRef<'a>,
pub(crate) unsafe_ud: UnsafeUserdata<'a>,
state: &'a State,
}
impl<'a> Clone for AnyUserdata<'a> {
fn clone(&self) -> Self {
Self {
state: self.state,
unsafe_ud: self.unsafe_ud.clone()
}
}
}
impl<'a> AnyUserdata<'a> {
pub fn from_ref(state: &'a State, lref: LuaRef<'a>) -> Self {
Self {
lref,
state,
unsafe_ud: UnsafeUserdata::from_ref(state, lref)
}
}
/// Returns a borrow to the userdata.
pub fn as_ref<T: Userdata + 'static>(&self) -> crate::Result<Ref<'a, T>> {
unsafe {
self.state.ensure_stack(3)?;
let _g = StackGuard::new(self.state);
let s = self.state.state_ptr();
self.lref.push_to_lua_stack(self.state)?;
if lua::lua_getmetatable(s, -1) == 0 {
return Err(crate::Error::UserdataMismatch);
}
self.state.get_userdata_metatable::<T>()
.push_to_lua_stack(self.state)?;
if lua::lua_rawequal(s, -2, -1) == 1 {
let cptr = lua::lua_touserdata(s, -3);
let cell = &*cptr.cast::<RefCell<T>>();
Ok(cell.borrow())
} else {
return Err(crate::Error::UserdataMismatch);
}
let cell = &*self.unsafe_ud.as_ptr_unchecked::<RefCell<T>>()?;
Ok(cell.borrow())
}
}
/// Returns a mutable reference to the userdata.
pub fn as_mut<T: Userdata + 'static>(&self) -> crate::Result<RefMut<'a, T>> {
unsafe {
self.state.ensure_stack(3)?;
let _g = StackGuard::new(self.state);
let s = self.state.state_ptr();
self.lref.push_to_lua_stack(self.state)?;
if lua::lua_getmetatable(s, -1) == 0 {
return Err(crate::Error::MissingMetatable);
}
self.state.get_userdata_metatable::<T>()
.push_to_lua_stack(self.state)?;
if lua::lua_rawequal(s, -2, -1) == 1 {
let cptr = lua::lua_touserdata(s, -3);
let cell = &*cptr.cast::<RefCell<T>>();
Ok(cell.borrow_mut())
} else {
Err(crate::Error::UserdataMismatch)
}
let cell = &*self.unsafe_ud.as_ptr_unchecked::<RefCell<T>>()?;
Ok(cell.borrow_mut())
}
}
@ -328,14 +440,8 @@ impl<'a> AnyUserdata<'a> {
/// If there is a possibility that these types do not match, use [`AnyUserdata::as_ptr`]
/// which does verify the types.
pub unsafe fn as_ptr_unchecked<T: Userdata + 'static>(&self) -> crate::Result<*mut RefCell<T>> {
self.state.ensure_stack(1)?;
let _g = StackGuard::new(self.state);
let s = self.state.state_ptr();
self.lref.push_to_lua_stack(self.state)?;
let cptr = lua::lua_touserdata(s, -3);
Ok(cptr.cast())
let cell = self.unsafe_ud.as_ptr_unchecked::<RefCell<T>>()?;
Ok(cell)
}
/// Returns a mutable pointer of the [`RefCell`] storing the userdata.
@ -346,7 +452,7 @@ impl<'a> AnyUserdata<'a> {
let _g = StackGuard::new(self.state);
let s = self.state.state_ptr();
self.lref.push_to_lua_stack(self.state)?;
self.unsafe_ud.lref.push_to_lua_stack(self.state)?;
if lua::lua_getmetatable(s, -1) == 0 {
return Err(crate::Error::MissingMetatable);
@ -356,8 +462,9 @@ impl<'a> AnyUserdata<'a> {
.push_to_lua_stack(self.state)?;
if lua::lua_rawequal(s, -2, -1) == 1 {
let cptr = lua::lua_touserdata(s, -3);
Ok(cptr.cast())
drop(_g);
let cell = self.unsafe_ud.as_ptr_unchecked::<RefCell<T>>()?;
Ok(cell)
} else {
Err(crate::Error::UserdataMismatch)
}
@ -369,7 +476,7 @@ impl<'a> AnyUserdata<'a> {
self.state.ensure_stack(3)?;
let _g = StackGuard::new(self.state);
self.lref.push_to_lua_stack(self.state)?;
self.unsafe_ud.lref.push_to_lua_stack(self.state)?;
let s = self.state.state_ptr();
if lua::lua_getmetatable(s, -1) == 0 {
@ -389,6 +496,67 @@ impl<'a> AnyUserdata<'a> {
Ok(cstr.to_string())
}
}
/// Returns the metatable of this userdata
pub fn get_metatable(&'a self) -> crate::Result<Table<'a>> {
unsafe {
self.state.ensure_stack(2)?;
let _g = StackGuard::new(self.state);
//self.unsafe_ud.lref.push_to_lua_stack(self.state)?;
self.push_to_lua_stack(self.state)?;
let s = self.state.state_ptr();
if lua::lua_getmetatable(s, -1) == 0 {
Err(crate::Error::MissingMetatable)
} else {
let mut t = Table::from_lua_stack(self.state)?;
t.mt_marker = true;
Ok(t)
}
}
}
/// Gets something from the userdata.
///
/// Will trigger a call to the `__index` metamethod. Use [`AnyUserdata::raw_get`] if you
/// don't want to call it.
pub fn get<K, V>(&'a self, key: K) -> crate::Result<V>
where
K: AsLua<'a>,
V: FromLua<'a>,
{
let mt = self.get_metatable()?;
mt.get::<K, V>(key)
}
/// Gets something from the userdata. Will **not** trigger a call to the `__index` metamethod.
pub fn raw_get<K, V>(&'a self, key: K) -> crate::Result<V>
where
K: AsLua<'a>,
V: FromLua<'a>,
{
let mt = self.get_metatable()?;
mt.raw_get::<K, V>(key)
}
/// Execute a method on this userdata. This finds a function with `name` and executes it
/// with the userdata as the first argument.
pub fn execute_method<A, R>(&'a self, name: &str, args: A) -> crate::Result<R>
where
A: IntoLuaVec<'a>,
R: FromLua<'a>,
{
let name = name.to_string();
let mt = self.get_metatable()?;
let f = mt.get::<_, Function>(name)?;
let mut args = args.into_lua_value_vec(self.state)?;
args.push_front(self.clone().as_lua(self.state)?);
f.exec(args)
}
}
impl<'a> FromLuaStack<'a> for AnyUserdata<'a> {
@ -396,15 +564,15 @@ impl<'a> FromLuaStack<'a> for AnyUserdata<'a> {
ensure_type(state, lua::LUA_TUSERDATA, -1)?;
Ok(AnyUserdata {
lref: LuaRef::from_stack(state)?,
state,
unsafe_ud: UnsafeUserdata::from_lua_stack(state)?,
})
}
}
impl<'a> PushToLuaStack<'a> for AnyUserdata<'a> {
unsafe fn push_to_lua_stack(&self, state: &'a State) -> crate::Result<()> {
self.lref.push_to_lua_stack(state)
self.unsafe_ud.lref.push_to_lua_stack(state)
}
}

34
src/userdata/proxy.rs Executable file
View File

@ -0,0 +1,34 @@
use std::marker::PhantomData;
use crate::{AsLua, State, Userdata, UserdataBuilder, Value};
pub struct UserdataProxy<T: Userdata>(PhantomData<T>);
impl<T: Userdata> UserdataProxy<T> {
pub fn new() -> Self {
Self(PhantomData)
}
}
impl<'a, T: Userdata> AsLua<'a> for UserdataProxy<T> {
fn as_lua(&self, _lua: &'a State) -> crate::Result<Value<'a>> {
todo!()
}
}
impl<T: Userdata> Userdata for UserdataProxy<T> {
fn build<'a>(state: &State, builder: &mut UserdataBuilder<'a, Self>) -> crate::Result<()> {
let mut other = UserdataBuilder::<T>::new();
T::build(state, &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
}
}

180
src/userdata/unsafe_ud.rs Executable file
View File

@ -0,0 +1,180 @@
use std::ffi::CStr;
use crate::{ensure_type, FromLuaStack, LuaRef, PushToLuaStack, StackGuard, State, Userdata};
use mlua_sys as lua;
pub struct UnsafeUserdata<'a> {
pub(crate) lref: LuaRef<'a>,
state: &'a State,
}
impl<'a> Clone for UnsafeUserdata<'a> {
fn clone(&self) -> Self {
Self {
lref: self.lref.clone(),
state: self.state,
}
}
}
impl<'a> UnsafeUserdata<'a> {
pub fn from_ref(state: &'a State, lref: LuaRef<'a>) -> Self {
Self {
lref,
state,
}
}
/// Returns a borrow to the userdata.
pub fn as_ref<T: Userdata + 'static>(&self) -> crate::Result<&'a T> {
unsafe {
self.state.ensure_stack(3)?;
let _g = StackGuard::new(self.state);
let s = self.state.state_ptr();
self.lref.push_to_lua_stack(self.state)?;
if lua::lua_getmetatable(s, -1) == 0 {
return Err(crate::Error::UserdataMismatch);
}
self.state.get_userdata_metatable::<T>()
.push_to_lua_stack(self.state)?;
if lua::lua_rawequal(s, -2, -1) == 1 {
let cptr = lua::lua_touserdata(s, -3);
let t = &*cptr.cast::<T>();
Ok(t)
} else {
return Err(crate::Error::UserdataMismatch);
}
}
}
/// Returns a mutable reference to the userdata.
pub fn as_mut<T: Userdata + 'static>(&self) -> crate::Result<&'a mut T> {
unsafe {
self.state.ensure_stack(3)?;
let _g = StackGuard::new(self.state);
let s = self.state.state_ptr();
self.lref.push_to_lua_stack(self.state)?;
if lua::lua_getmetatable(s, -1) == 0 {
return Err(crate::Error::MissingMetatable);
}
self.state.get_userdata_metatable::<T>()
.push_to_lua_stack(self.state)?;
if lua::lua_rawequal(s, -2, -1) == 1 {
let cptr = lua::lua_touserdata(s, -3);
let t = &mut *cptr.cast::<T>();
Ok(t)
} else {
Err(crate::Error::UserdataMismatch)
}
}
}
/// Returns a mutable pointer of the [`RefCell`] of userdata **WITHOUT verifying the type of it**.
///
/// # Safety
/// * You must be certain that the type `T` is the same type that this userdata has a handle to.
/// There is a blind cast from `void*` to `T*`
///
/// If there is a possibility that these types do not match, use [`UnsafeUserdata::as_ptr`]
/// which does verify the types.
pub unsafe fn as_ptr_unchecked<T>(&self) -> crate::Result<*mut T> {
self.state.ensure_stack(1)?;
let _g = StackGuard::new(self.state);
let s = self.state.state_ptr();
self.lref.push_to_lua_stack(self.state)?;
let cptr = lua::lua_touserdata(s, -1);
Ok(cptr.cast())
}
/// Returns a mutable pointer of the data stored.
///
/// This function ensures that the type of the userdata this struct has a handle to is the
/// same as `T`. If it isn't, a `UserdataMismatch` error will be returned.
pub unsafe fn as_ptr<T: Userdata + 'static>(&self) -> crate::Result<*mut T> {
let _g = StackGuard::new(self.state);
let s = self.state.state_ptr();
self.lref.push_to_lua_stack(self.state)?;
if lua::lua_getmetatable(s, -1) == 0 {
return Err(crate::Error::MissingMetatable);
}
self.state.get_userdata_metatable::<T>()
.push_to_lua_stack(self.state)?;
if lua::lua_rawequal(s, -2, -1) == 1 {
let cptr = lua::lua_touserdata(s, -3);
Ok(cptr.cast())
} else {
Err(crate::Error::UserdataMismatch)
}
}
/// Returns the name of the userdata by accessing the metatable
pub fn name(&self) -> crate::Result<String> {
unsafe {
self.state.ensure_stack(3)?;
let _g = StackGuard::new(self.state);
self.lref.push_to_lua_stack(self.state)?;
let s = self.state.state_ptr();
if lua::lua_getmetatable(s, -1) == 0 {
return Err(crate::Error::MissingMetatable);
}
lua::lua_pushliteral(s, "__name");
lua::lua_rawget(s, -2);
ensure_type(self.state, lua::LUA_TSTRING, -1)?;
let cstr = CStr::from_ptr(lua::lua_tostring(s, -1));
let cstr = cstr.to_str()
// on panic, this should be considered a bug
.expect("Metatable name has invalid utf8 bytes!");
Ok(cstr.to_string())
}
}
}
impl<'a> FromLuaStack<'a> for UnsafeUserdata<'a> {
unsafe fn from_lua_stack(state: &'a State) -> crate::Result<Self> {
ensure_type(state, lua::LUA_TUSERDATA, -1)?;
Ok(UnsafeUserdata {
lref: LuaRef::from_stack(state)?,
state,
})
}
}
impl<'a> PushToLuaStack<'a> for UnsafeUserdata<'a> {
unsafe fn push_to_lua_stack(&self, state: &'a State) -> crate::Result<()> {
self.lref.push_to_lua_stack(state)
}
}
/* impl<'a> AsLua<'a> for UnsafeUserdata<'a> {
fn as_lua(&self, _lua: &'a State) -> crate::Result<Value<'a>> {
Ok(Value::Userdata(self.clone()))
}
}
impl<'a> FromLua<'a> for UnsafeUserdata<'a> {
fn from_lua(_lua: &State, val: Value<'a>) -> crate::Result<Self> {
val.into_userdata()
}
} */

View File

@ -1,10 +1,10 @@
use std::ffi::CStr;
use crate::State;
use crate::{FromLuaStack, PushToLuaStack, StackGuard, State, Value};
use mlua_sys as lua;
pub unsafe fn ensure_type(state: &State, typ: i32, idx: i32) -> crate::Result<()> {
pub(crate) unsafe fn ensure_type(state: &State, typ: i32, idx: i32) -> crate::Result<()> {
let s = state.state_ptr();
let lua_type = lua::lua_type(s, idx);
@ -24,4 +24,40 @@ pub unsafe fn ensure_type(state: &State, typ: i32, idx: i32) -> crate::Result<()
Err(crate::Error::UnexpectedType(exp_s.to_string(), s.to_string()))
}
}
/// Iterates through all entries in the registry and prints the types of each entry.
#[allow(dead_code)]
pub(crate) fn print_registry(lua: &State) -> crate::Result<()> {
unsafe {
let _g = StackGuard::new(&lua);
let s = lua.state_ptr();
Value::String(String::from("Hello World")).push_to_lua_stack(&lua)?;
let r = lua::luaL_ref(s, lua::LUA_REGISTRYINDEX);
lua::luaL_unref(s, lua::LUA_REGISTRYINDEX, r);
for i in 0..r {
let ty = lua::lua_rawgeti(s, lua::LUA_REGISTRYINDEX, i as i64);
match ty {
lua::LUA_TUSERDATA => {
lua::lua_getmetatable(s, -1);
lua::lua_pushliteral(s, "__name");
lua::lua_rawget(s, -2);
let v = Value::from_lua_stack(&lua)?;
println!("{}: {}", i, v.as_string().unwrap());
},
_ => {
let tyname = CStr::from_ptr(lua::lua_typename(s, ty));
let tyname = tyname.to_str().unwrap();
println!("{}: {}", i, tyname);
}
}
lua::lua_pop(s, 1);
}
}
Ok(())
}

View File

@ -36,27 +36,17 @@ impl<'a> Value<'a> {
}
}
pub fn as_userdata(&self) -> crate::Result<&AnyUserdata> {
pub fn as_number(&self) -> crate::Result<f64> {
match self {
Value::Userdata(ud) => Ok(ud),
Value::Number(v) => Ok(*v),
_ => {
Err(crate::Error::UnexpectedType("Userdata".to_string(), self.type_name().to_string()))
}
}
}
/// Consumes self, and attempts to get `AnyUserdata`.
///
/// Returns an error if this value is not userdata
pub fn into_userdata(self) -> crate::Result<AnyUserdata<'a>> {
match self {
Value::Userdata(ud) => Ok(ud),
_ => {
Err(crate::Error::UnexpectedType("Userdata".to_string(), self.type_name().to_string()))
Err(crate::Error::UnexpectedType("Number".to_string(), self.type_name().to_string()))
}
}
}
/// If `self` is an instance of Value::String, returns a borrow to it. If it is not then
/// an `UnexpectedType` error is returned.
pub fn as_string(&self) -> crate::Result<&String> {
match self {
Value::String(s) => Ok(s),
@ -66,6 +56,41 @@ impl<'a> Value<'a> {
}
}
/// If `self` is an instance of Value::Function, returns a borrow to it. If it is not then
/// an `UnexpectedType` error is returned.
pub fn as_function(&self) -> crate::Result<Function> {
match self {
Value::Function(v) => Ok(v.clone()),
_ => {
Err(crate::Error::UnexpectedType("Table".to_string(), self.type_name().to_string()))
}
}
}
/// If `self` is an instance of Value::Table, returns a borrow to it. If it is not then
/// an `UnexpectedType` error is returned.
pub fn as_table(&self) -> crate::Result<Table> {
match self {
Value::Table(v) => Ok(v.clone()),
_ => {
Err(crate::Error::UnexpectedType("Table".to_string(), self.type_name().to_string()))
}
}
}
/// If `self` is an instance of Value::Userdata, returns a borrow to it. If it is not then
/// an `UnexpectedType` error is returned.
pub fn as_userdata(&self) -> crate::Result<AnyUserdata<'a>> {
match self {
Value::Userdata(ud) => Ok(ud.clone()),
_ => {
Err(crate::Error::UnexpectedType("Userdata".to_string(), self.type_name().to_string()))
}
}
}
/// If `self` is an instance of Value::String, the string is returned. If it is not then
/// an `UnexpectedType` error is returned.
pub fn into_string(self) -> crate::Result<String> {
match self {
Value::String(s) => Ok(s),
@ -74,10 +99,43 @@ impl<'a> Value<'a> {
}
}
}
/// If `self` is an instance of Value::Function, the function is returned. If it is not then
/// an `UnexpectedType` error is returned.
pub fn into_function(self) -> crate::Result<Function<'a>> {
match self {
Value::Function(v) => Ok(v),
_ => {
Err(crate::Error::UnexpectedType("Function".to_string(), self.type_name().to_string()))
}
}
}
/// If `self` is an instance of Value::Table, the table is returned. If it is not then
/// an `UnexpectedType` error is returned.
pub fn into_table(self) -> crate::Result<Table<'a>> {
match self {
Value::Table(v) => Ok(v),
_ => {
Err(crate::Error::UnexpectedType("Table".to_string(), self.type_name().to_string()))
}
}
}
/// If `self` is an instance of Value::Userdata, the userdata is returned. If it is not then
/// an `UnexpectedType` error is returned.
pub fn into_userdata(self) -> crate::Result<AnyUserdata<'a>> {
match self {
Value::Userdata(ud) => Ok(ud),
_ => {
Err(crate::Error::UnexpectedType("Userdata".to_string(), self.type_name().to_string()))
}
}
}
}
impl<'a> PushToLuaStack<'a> for Value<'a> {
unsafe fn push_to_lua_stack(&self, state: &crate::State) -> crate::Result<()> {
unsafe fn push_to_lua_stack(&self, state: &'a crate::State) -> crate::Result<()> {
let s = state.state_ptr();
match self {
@ -304,6 +362,16 @@ impl<'a> FromLuaVec<'a> for ValueVec<'a> {
}
}
impl<'a> PushToLuaStack<'a> for ValueVec<'a> {
unsafe fn push_to_lua_stack(&self, state: &'a State) -> crate::Result<()> {
for v in self.iter() {
v.push_to_lua_stack(state)?;
}
Ok(())
}
}
impl<'a, T: FromLua<'a>> FromLuaVec<'a> for T {
fn from_lua_value_vec(state: &'a State, mut values: ValueVec<'a>) -> crate::Result<Self> {
if let Some(val) = values.pop_front() {
@ -317,6 +385,12 @@ impl<'a, T: FromLua<'a>> FromLuaVec<'a> for T {
}
}
impl<'a> IntoLuaVec<'a> for () {
fn into_lua_value_vec(self, _state: &'a State) -> crate::Result<ValueVec<'a>> {
Ok(ValueVec::new())
}
}
macro_rules! impl_from_lua_vec_tuple {
( $count: expr, $first: tt, $( $name: tt ),+ ) => (
#[allow(non_snake_case)]