Groundwork for userdata

Still need a way to add methods and functions
This commit is contained in:
SeanOMik 2024-01-26 15:43:26 -05:00
parent 71199bc905
commit 2b03b55014
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
8 changed files with 608 additions and 172 deletions

View File

@ -1,9 +1,10 @@
use std::ffi::CStr;
use crate::{FromLuaStack, LuaRef, PushToLuaStack, StackGuard, State};
use crate::{AsLua, FromLuaStack, LuaRef, PushToLuaStack, StackGuard, State};
use mlua_sys as lua;
#[derive(Clone)]
pub struct Function<'a> {
state: &'a State,
lref: LuaRef
@ -18,7 +19,7 @@ impl<'a> FromLuaStack<'a> for Function<'a> {
}
}
impl<'a> PushToLuaStack for Function<'a> {
impl<'a> PushToLuaStack<'a> for Function<'a> {
unsafe fn push_to_lua_stack(&self, state: &State) -> crate::Result<()> {
self.lref.push_to_lua_stack(state)
}
@ -59,20 +60,26 @@ impl<'a> Function<'a> {
}
}
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()))
}
}
pub trait PushToLuaStackMulti<'a> {
fn len(&self) -> usize;
fn push_args_to_lua_stack(&self, state: &State) -> crate::Result<()>;
fn push_args_to_lua_stack(&self, state: &'a State) -> crate::Result<()>;
}
impl<'a, T> PushToLuaStackMulti<'a> for T
where
T: PushToLuaStack,
T: PushToLuaStack<'a>,
{
fn len(&self) -> usize {
1
}
fn push_args_to_lua_stack(&self, state: &State) -> crate::Result<()> {
fn push_args_to_lua_stack(&self, state: &'a State) -> crate::Result<()> {
unsafe {
self.push_to_lua_stack(state)?;
}
@ -109,14 +116,14 @@ impl<'a, T: FromLuaStack<'a>> FromLuaStackMulti<'a> for T {
macro_rules! impl_function_arg_tuple {
( $count: expr, $first: tt, $( $name: tt ),+ ) => (
#[allow(non_snake_case)]
impl<'a, $first: PushToLuaStack, $($name: PushToLuaStack,)+> PushToLuaStackMulti<'a> for ($first, $($name,)+) {
impl<'a, $first: PushToLuaStack<'a>, $($name: PushToLuaStack<'a>,)+> PushToLuaStackMulti<'a> for ($first, $($name,)+) {
fn len(&self) -> usize {
// this will end up generating $count - 1 - 1 - 1... hopefully the compiler will
// optimize that out
$count
}
fn push_args_to_lua_stack(&self, state: &State) -> crate::Result<()> {
fn push_args_to_lua_stack(&self, state: &'a State) -> crate::Result<()> {
let ($first, $($name,)+) = self;
unsafe {
@ -134,7 +141,13 @@ macro_rules! impl_function_arg_tuple {
}
fn results_from_lua_stack(state: &'a State) -> crate::Result<Self> {
Ok(unsafe { ( $first::from_lua_stack(state)?, $( $name::from_lua_stack(state)?, )+ ) })
unsafe {
let ($( $name, )+ $first) = ( $( $name::from_lua_stack(state)?, )+ $first::from_lua_stack(state)? );
Ok( ($first, $( $name, )+) )
}
//Ok(unsafe { ( $( $name::from_lua_stack(state)?, )+ $first::from_lua_stack(state)? ) })
//Ok(unsafe { ( $first::from_lua_stack(state)?, $( $name::from_lua_stack(state)?, )+ ) })
}
}
@ -144,12 +157,12 @@ macro_rules! impl_function_arg_tuple {
// implements PushToLuaStackMulti and FromLuaStackMulti for a tuple with a single element
( $count: expr, $only: tt ) => {
#[allow(non_snake_case)]
impl<'a, $only: PushToLuaStack> PushToLuaStackMulti<'a> for ($only,) {
impl<'a, $only: PushToLuaStack<'a>> PushToLuaStackMulti<'a> for ($only,) {
fn len(&self) -> usize {
1
}
fn push_args_to_lua_stack(&self, state: &State) -> crate::Result<()> {
fn push_args_to_lua_stack(&self, state: &'a State) -> crate::Result<()> {
let (a,) = self;
unsafe {

View File

@ -1,4 +1,4 @@
use std::{ffi::CStr, str::Utf8Error, sync::Arc};
use std::{any::type_name, ffi::CStr, marker::PhantomData, mem, ptr, str::Utf8Error, sync::Arc};
use lua::{lua_typename, lua_type};
use mlua_sys as lua;
@ -18,29 +18,19 @@ use value::*;
pub mod guard;
use guard::*;
/* struct RustFn {
} */
/* struct RustFnUpvalue {
} */
///
pub fn ptr_to_string(ptr: *const i8) -> std::result::Result<String, Utf8Error> {
let c = unsafe { CStr::from_ptr(ptr) };
let s= c.to_str()?;
Ok(s.to_string())
}
pub mod userdata;
use userdata::*;
pub mod util;
use util::*;
fn main() -> Result<()> {
let lua = State::new();
lua.expose_libraries(&[StdLibrary::Debug]);
lua.expose_libraries(&[StdLibrary::Debug, StdLibrary::Package]);
let globals = lua.globals()?;
let a = |lua: &State, (num,): (i32,)| -> Result<i32> {
let a = |_lua: &State, (num,): (i32,)| -> Result<i32> {
println!("Rust got number from lua: {}", num);
Ok(999)
};
@ -48,35 +38,8 @@ fn main() -> Result<()> {
let f = lua.create_function(a)?;
globals.set("native_test", f)?;
//let tbl = lua.create_table()?;
let vec2_add = lua.create_function(|lua: &State, (a, b): (Table, Table)| -> Result<Table> {
let ax: i32 = a.get("x")?;
let ay: i32 = a.get("y")?;
let bx: i32 = b.get("x")?;
let by: i32 = b.get("y")?;
let rx = ax + bx;
let ry = ay + by;
let mt = lua.create_meta_table("Vec2")?;
mt.set("x", rx)?;
mt.set("y", ry)?;
Ok(mt)
})?;
let mt = lua.create_meta_table("Vec2")?;
mt.set("x", 50)?;
mt.set("y", 50)?;
mt.set_meta("__add", vec2_add)?;
globals.set("pos1", mt)?;
let mt = lua.create_meta_table("Vec2")?;
mt.set("x", 25)?;
mt.set("y", 25)?;
globals.set("pos2", mt)?;
let ud = lua.create_userdata("Vec2", Vec2 { x: 0.0, y: 0.0})?;
globals.set("Vec2", ud)?;
let tbl = lua.create_table()?;
tbl.set("x", 10)?;
@ -85,6 +48,8 @@ fn main() -> Result<()> {
globals.set("X", tbl)?;
lua.execute(r#"
require "util"
--[[function dump_table(tbl)
for k, v in pairs(tbl) do
if type(v) == "table" then
@ -123,28 +88,12 @@ fn main() -> Result<()> {
local res = native_test(50)
print("Lua got " .. res .. " back from rust!")
print("Pos1 is (" .. pos1.x .. ", " .. pos1.y .. ")")
print("Pos2 is (" .. pos2.x .. ", " .. pos2.y .. ")")
local add_pos = pos1 + pos2
print("Pos1 + pos2 is (" .. add_pos.x .. ", " .. add_pos.y .. ")")
print("Vec2: " .. dump_table(Vec2))
print("Meta Vec2: " .. dump_table(getmetatable(Vec2)))
--local vec2 = Vec2.new(50, 50)
print("Vec2 is (" .. Vec2.x .. ", " .. Vec2.y .. ")")
"#).unwrap();
let num = globals.get::<_, i32>("cool_num")?;
assert_eq!(num, 50);
println!("Got number as 50!");
let num = globals.get::<_, Function>("say_number")?;
num.exec::<_, ()>(50)?;
let num = globals.get::<_, Function>("multiply_print")?;
num.exec::<_, ()>((10, 5))?;
let num = globals.get::<_, Function>("multiply_ret")?;
let num: i32 = num.exec::<_, i32>((10, 5))?;
assert_eq!(num, 50);
println!("Did math in lua and got 50!");
unsafe {
assert_eq!(lua::lua_gettop(lua.state_ptr()), 0); // ensure that nothing is left on the stack
}
@ -165,10 +114,8 @@ impl LuaRef {
/// Creates a reference to what is at the top of the stack.
pub unsafe fn from_stack(state: &State) -> Result<Self> {
let s = state.state_ptr();
let t = lua::lua_gettop(s);
let r = lua::luaL_ref(s, lua::LUA_REGISTRYINDEX);
let t = lua::lua_gettop(s);
if r == lua::LUA_REFNIL {
Err(Error::Nil)
} else {
@ -177,7 +124,7 @@ impl LuaRef {
}
}
impl PushToLuaStack for LuaRef {
impl<'a> PushToLuaStack<'a> for LuaRef {
unsafe fn push_to_lua_stack(&self, state: &State) -> Result<()> {
let s = state.state_ptr();
@ -236,91 +183,143 @@ impl Error {
let msg = format!("{}", self);
let msg_c = msg.as_ptr() as *const i8;
lua::luaL_error(lua, msg_c);
panic!("never gets here");
unreachable!();
}
}
/// A result for use with lua functions
type Result<T> = core::result::Result<T, Error>;
pub trait PushToLuaStack {
unsafe fn push_to_lua_stack(&self, state: &State) -> 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<Self>;
}
/* impl<'a> FromLuaStack<'a> for () {
unsafe fn from_lua_stack(state: &'a State) -> Result<Self> {
Ok(())
}
} */
/// Implements PushToLuaStack for a number
macro_rules! impl_push_to_lua_stack_number {
macro_rules! impl_as_lua_number {
($ty: ident) => {
impl PushToLuaStack for $ty {
unsafe fn push_to_lua_stack(&self, state: &State) -> Result<()> {
state.ensure_stack(1)?;
lua::lua_pushnumber(state.state_ptr(), *self as f64);
Ok(())
impl<'a> AsLua<'a> for $ty {
fn as_lua(&self, _lua: &'a State) -> crate::Result<Value<'a>> {
Ok(Value::Number(*self as f64))
}
}
impl FromLua 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<'a> FromLuaStack<'a> for i32 {
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> FromLuaStack<'a> for String {
unsafe fn from_lua_stack(state: &'a State) -> Result<Self> {
let s = state.state_ptr();
if lua::lua_isnumber(s, -1) == 1 {
let v = lua::lua_tonumber(s, -1) as i32;
lua::lua_pop(s, 1);
Ok(v)
} else {
let lua_ty = lua_type(s, -1);
let typec = CStr::from_ptr(lua_typename(s, lua_ty));
let type_str = typec.to_str()
.expect("Type has invalid bytes!");
let cstr = lua::lua_tostring(s, -1);
lua::lua_pop(s, 1);
Err(Error::unexpected_type("Number", type_str))
let cstr = CStr::from_ptr(cstr);
Ok(cstr.to_str().unwrap().to_string())
}
}
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(())
}
}
pub struct Vec2 {
x: f32,
y: f32,
}
impl Userdata for Vec2 {
fn build<'a>(builder: &mut UserdataBuilder<'a, Vec2>) -> crate::Result<()> {
builder.name("Vec2")
.field_getter("x", |_, this| Ok(this.x))
.field_getter("y", |_, this| Ok(this.y));
Ok(())
}
}
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)?;
builder.name(&other.name.unwrap());
for (lbl, getters) in other.field_getters.into_iter() {
let wrap = |state: &State, data: &Self| {
Ok(())
};
builder.field_getter(&lbl, wrap);
}
}
}
impl_push_to_lua_stack_number!(i8);
impl_push_to_lua_stack_number!(i16);
impl_push_to_lua_stack_number!(i32);
impl_push_to_lua_stack_number!(i64);
impl_push_to_lua_stack_number!(u8);
impl_push_to_lua_stack_number!(u16);
impl_push_to_lua_stack_number!(u32);
impl_push_to_lua_stack_number!(u64);
impl_push_to_lua_stack_number!(f32);
impl_push_to_lua_stack_number!(f64);
impl PushToLuaStack 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 PushToLuaStack 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(())
}

View File

@ -1,9 +1,15 @@
use core::ffi;
use std::{ffi::{CString, CStr}, mem, ptr::{self, NonNull}};
use std::{ffi::{CString, CStr}, mem, ptr::{self, NonNull}, str::Utf8Error};
use mlua_sys as lua;
use crate::{lua_error_guard, Error, FromLuaStack, FromLuaStackMulti, Function, LuaRef, PushToLuaStack, PushToLuaStackMulti, Result, Table};
use crate::{ensure_type, lua_error_guard, AnyUserdata, Error, FromLuaStack, FromLuaStackMulti, Function, LuaRef, PushToLuaStack, PushToLuaStackMulti, Result, StackGuard, Table, Userdata, UserdataBuilder, Value};
pub fn ptr_to_string(ptr: *const i8) -> std::result::Result<String, Utf8Error> {
let c = unsafe { CStr::from_ptr(ptr) };
let s= c.to_str()?;
Ok(s.to_string())
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StdLibrary {
@ -167,7 +173,7 @@ impl State {
unsafe {
let s = self.state_ptr();
lua::lua_rawgeti(s, lua::LUA_REGISTRYINDEX, lua::LUA_RIDX_GLOBALS);
Table::with_ref(self, LuaRef::from_stack(self)?, None)
Table::with_ref(self, LuaRef::from_stack(self)?)
}
}
@ -180,11 +186,11 @@ impl State {
let tyname = lua::lua_typename(s, ty);
let tyname = CStr::from_ptr(tyname);
let tyname = tyname.to_str().unwrap();
println!("{}: {}", i, tyname);
println!("{}: {}", -(t + 1 - i), tyname);
}
}
pub fn create_function<'a, A, R, F>(&'a self, f: F) -> Result<Function<'a>>
pub fn create_function<'a, A, R, F>(&self, f: F) -> Result<Function>
where
A: FromLuaStackMulti<'a>,
R: PushToLuaStackMulti<'a>,
@ -268,4 +274,63 @@ impl State {
}
}
/// Create userdata
pub fn create_userdata<T: Userdata + 'static>(&self, name: &str, data: T) -> Result<AnyUserdata> {
unsafe {
self.ensure_stack(2)?;
let _g = StackGuard::new(self);
let s = self.state_ptr();
let ptr = lua::lua_newuserdata(s, mem::size_of::<T>()).cast::<T>();
ptr::write(ptr, data);
let name_cstr = format!("{}\0", name);
let name_cstr = name_cstr.as_str();
let name_cstr = name_cstr.as_ptr() as *const i8;
// attempt to get the metatable
lua::lua_pushstring(s, name_cstr);
let ty = lua::lua_rawget(s, lua::LUA_REGISTRYINDEX);
if ty == lua::LUA_TNIL {
lua::lua_pop(s, 1); // remove nil
// if the metatable is not made yet, make it
let mt = self.create_userdata_metatable::<T>()?;
let mt = mt.get_metatable().unwrap(); // the table 100% has a metatable
mt.push_to_lua_stack(self)?;
} else if ty != lua::LUA_TTABLE {
return Err(Error::RegistryConflict(name.to_string()));
}
lua::lua_setmetatable(s, -2);
AnyUserdata::from_lua_stack(self)
}
}
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)?;
let getters = builder.field_getters;
let name = builder.name.unwrap();
let mt = self.create_meta_table(&name)?;
if !getters.is_empty() {
let index_fn = self.create_function(move |lua: &State, (ud, key): (AnyUserdata, String)| {
if let Some(getter) = getters.get(&key) {
let r = getter(lua, Value::Userdata(ud))?;
Ok(r)
} else {
Ok(Value::Nil)
}
})?;
mt.set_meta("__index", index_fn)?;
}
Ok(mt)
}
}

View File

@ -3,7 +3,7 @@ use std::{ffi::CStr, ops::Deref, sync::Arc};
use mlua_sys as lua;
use crate::{FromLuaStack, LuaRef, PushToLuaStack, Result, StackGuard, State};
use crate::{ensure_type, FromLuaStack, LuaRef, PushToLuaStack, Result, StackGuard, State};
pub(crate) struct MetaTableInfo {
name: Option<String>,
@ -13,8 +13,8 @@ pub(crate) struct MetaTableInfo {
#[derive(Clone)]
pub struct Table<'a> {
state: &'a State,
lref: LuaRef,
meta: Option<LuaRef>, // Some if this table is a metatable
pub(crate) lref: LuaRef,
pub(crate) meta: Option<LuaRef>, // Some if this table is a metatable
}
impl<'a> Table<'a> {
@ -38,12 +38,17 @@ impl<'a> Table<'a> {
pub fn new_meta_table(state: &'a State, name: &str) -> Result<Self> {
let name_term = format!("{}\0", name);
let name_term_c = name_term.as_str().as_ptr() as *const i8;
let name_term_arc = Arc::new(name_term_c);
let (lref, meta_ref) = unsafe {
let _g = StackGuard::new(state);
state.ensure_stack(2)?;
// create the table, this will be retrieved from
// stack after the metatable is created
let s = state.state_ptr();
lua::lua_newtable(s);
//let table_idx = lua::lua_gettop(s);
let s = state.state_ptr();
if lua::luaL_newmetatable(s, name_term_c) == 0 {
// lua::luaL_getmetatable does not return the type that was
@ -55,11 +60,11 @@ impl<'a> Table<'a> {
return Err(crate::Error::RegistryConflict(name.to_string()));
}
}
lua::lua_pushvalue(s, lua::lua_gettop(s));
lua::lua_setmetatable(s, -3); // -3 here since the metatable was added twice
let meta = LuaRef::from_stack(state)?;
let s = state.state_ptr();
lua::lua_newtable(s);
// retrieve the table created before the metatable
(LuaRef::from_stack(state)?, meta)
};
@ -71,7 +76,7 @@ impl<'a> Table<'a> {
}
/// Construct a table with a lua reference to one.
pub fn with_ref(state: &'a State, lua_ref: LuaRef, name: Option<String>) -> Result<Self> {
pub fn with_ref(state: &'a State, lua_ref: LuaRef) -> Result<Self> {
let s = state.state_ptr();
unsafe {
@ -79,7 +84,7 @@ impl<'a> Table<'a> {
lua_ref.push_to_lua_stack(state)?;
if lua::lua_istable(s, -1) == 0 {
panic!("Index is not a table")
panic!("Provided reference is not a table")
}
}
@ -95,8 +100,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,
V: PushToLuaStack
K: PushToLuaStack<'a>,
V: PushToLuaStack<'a>
{
let s = self.state.state_ptr();
unsafe {
@ -126,9 +131,9 @@ impl<'a> Table<'a> {
/// Get a value from the table.
///
/// This may trigger the `__index` metamethod, see [`Table::raw_get`]
pub fn get<K, V>(&'a self, key: K) -> Result<V>
pub fn get<K, V>(&self, key: K) -> Result<V>
where
K: PushToLuaStack,
K: PushToLuaStack<'a>,
V: FromLuaStack<'a>,
{
let s = self.state.state_ptr();
@ -167,8 +172,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,
V: PushToLuaStack
K: PushToLuaStack<'a>,
V: PushToLuaStack<'a>
{
let s = self.state.state_ptr();
unsafe {
@ -189,7 +194,7 @@ 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>
where
K: PushToLuaStack,
K: PushToLuaStack<'a>,
V: FromLuaStack<'a>,
{
let s = self.state.state_ptr();
@ -219,10 +224,14 @@ impl<'a> Table<'a> {
}
}
/// Set something in the metatable
///
/// # Panics
/// Panics if this table is not a metatable
pub fn set_meta<K, V>(&self, key: K, val: V) -> Result<()>
where
K: PushToLuaStack,
V: PushToLuaStack
K: PushToLuaStack<'a>,
V: PushToLuaStack<'a>
{
let mt = self.meta.as_ref()
.expect("this table is not a meta table!");
@ -232,7 +241,6 @@ impl<'a> Table<'a> {
self.state.ensure_stack(3)?;
let _g = StackGuard::new(self.state);
//lua::luaL_getmetatable(s, **cname);
mt.push_to_lua_stack(self.state)?;
key.push_to_lua_stack(self.state)?;
val.push_to_lua_stack(self.state)?;
@ -241,9 +249,17 @@ impl<'a> Table<'a> {
Ok(())
}
/// Returns a handle to the metatable of this table
pub fn get_metatable(&self) -> Option<Table> {
self.meta.clone()
.map(|r|
Table::with_ref(self.state, r).unwrap()
)
}
}
impl<'a> PushToLuaStack for Table<'a> {
impl<'a> PushToLuaStack<'a> for Table<'a> {
unsafe fn push_to_lua_stack(&self, state: &State) -> Result<()> {
// no need to ensure stack, the LuaRef does it for us
self.lref.push_to_lua_stack(state)?;
@ -254,19 +270,7 @@ impl<'a> PushToLuaStack for Table<'a> {
impl<'a> FromLuaStack<'a> for Table<'a> {
unsafe fn from_lua_stack(state: &'a State) -> Result<Self> {
let s = state.state_ptr();
let ty = lua::lua_type(s, -1);
if ty == lua::LUA_TTABLE {
let t = Table::with_ref(state, LuaRef::from_stack(state)?, None);
t
} else {
let tyname = lua::lua_typename(s, ty);
let cstr = CStr::from_ptr(tyname);
let s = cstr.to_str()
.expect("Lua type has invalid bytes!");
Err(crate::Error::UnexpectedType("Table".to_string(), s.to_string()))
}
ensure_type(state, lua::LUA_TTABLE, -1)?;
Table::with_ref(state, LuaRef::from_stack(state)?)
}
}

164
src/userdata.rs Normal file
View File

@ -0,0 +1,164 @@
use std::{collections::HashMap, marker::PhantomData, sync::Arc};
use crate::{ensure_type, AsLua, FromLua, FromLuaStack, LuaRef, PushToLuaStack, State, Table, Value};
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 trait FieldSetter {
fn set_field(&self, val: Value);
}
pub trait FieldGetter {
fn get_field(&self) -> Value;
}
type UserdataFn<'a> = Box<dyn Fn(&'a State, Value<'a>) -> crate::Result<Value<'a>>>;
pub struct UserdataBuilder<'a, T> {
pub(crate) name: Option<String>,
pub(crate) field_getters: HashMap<String, UserdataFn<'a>>,
_marker: PhantomData<T>,
}
impl<'a, T> UserdataBuilder<'a, T> {
pub fn new() -> Self {
Self {
name: None,
field_getters: HashMap::new(),
_marker: PhantomData,
}
}
pub fn name(&mut self, name: &str) -> &mut Self {
self.name = Some(name.to_string());
self
}
pub fn field_getter<F, R>(&mut self, name: &str, f: F) -> &mut Self
where
F: Fn(&'a State, &T) -> crate::Result<R> + 'static,
R: AsLua<'a>,
{
let wrap = move |lua: &'a State, val: Value<'a>| {
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))
};
self.field_getters.insert(name.to_string(), Box::new(wrap));
self
}
pub fn into_meta_table(self, state: &'a State, table: &Table<'a>) -> crate::Result<()> {
//let this: Self = unsafe { std::mem::transmute(self) };
//let getters = *self.field_getters;
/* let index_fn = state.create_function(move |lua: &'a State, (ud, key): (AnyUserdata<'a>, String)| {
let ud_ref = ud.as_ref::<T>()?;
if let Some(getter) = self.field_getters.get(&key) {
let r = getter(lua, ome(ud_ref), Value::Nil)?;
let r = unsafe { std::mem::transmute(r) };
Ok(r)
} else {
//ud.get::<_, Value<'a>>(key)
Ok(Value::Nil)
}
})?;
table.set("__index", index_fn)?; */
Ok(())
}
/* pub fn wrap_builder<U, F>(this: &mut UserdataBuilder<'a, T>, other: UserdataBuilder<'a, U>, to_proxied: F)
where
F: Fn(&'a T) -> &'a U + 'a,
{
let to_proxied = Arc::new(to_proxied);
for (lbl, getter) in other.field_getters.into_iter() {
let to_proxied_cl = to_proxied.clone();
let new_getter = move |lua: &'a State, this: Option<&T>, val: Value<'a>| {
let proxy = this.map(|t| to_proxied_cl(t));
getter(lua, proxy, val)
};
this.field_getters.insert(lbl, Box::new(new_getter));
}
} */
/* pub fn into_userdata(self, state: &'a State, data: T) -> crate::Result<Table<'a>> {
let name = self.name
.expect("No name was set for userdata!");
let getters = self.field_getters.clone();
let index_fn = state.create_function(move |lua: &'a State, (ud, key): (AnyUserdata<'a>, String)| {
let ud_ref = ud.as_ref::<T>()?;
if let Some(getter) = getters.get(&key) {
getter(lua, Some(ud_ref), Value::Nil)
} else {
//ud.get::<_, Value<'a>>(key)
Ok(Value::Nil)
}
//todo!()
})?;
let mt = state.create_meta_table(&name)?;
mt.set("__index", index_fn)?;
Ok(mt)
} */
}
pub trait Userdata: Sized {
fn build<'a>(builder: &mut UserdataBuilder<'a, Self>) -> crate::Result<()>;
}
/// A handle to some userdata on the stack
#[derive(Clone)]
pub struct AnyUserdata<'a> {
lref: LuaRef,
state: &'a State,
}
impl<'a> AnyUserdata<'a> {
pub fn from_ref(state: &'a State, lref: LuaRef) -> Self {
Self {
lref,
state,
}
}
pub fn as_ref<T>(&self) -> crate::Result<&'a T> {
unsafe {
let s = self.state.state_ptr();
self.lref.push_to_lua_stack(self.state)?;
let cptr = lua::lua_touserdata(s, -1);
Ok(cptr.cast::<T>().as_ref().unwrap())
}
}
}
impl<'a> FromLuaStack<'a> for AnyUserdata<'a> {
unsafe fn from_lua_stack(state: &'a State) -> crate::Result<Self> {
ensure_type(state, lua::LUA_TUSERDATA, -1)?;
Ok(AnyUserdata {
lref: LuaRef::from_stack(state)?,
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)
}
}

29
src/util.rs Normal file
View File

@ -0,0 +1,29 @@
use std::ffi::CStr;
use crate::State;
use mlua_sys as lua;
pub 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);
if lua_type == typ {
Ok(())
} else {
let exp_tyname = lua::lua_typename(s, typ);
let exp_cstr = CStr::from_ptr(exp_tyname);
let exp_s = exp_cstr.to_str()
.expect("Lua type has invalid bytes!");
let tyname = lua::lua_typename(s, lua_type);
let cstr = CStr::from_ptr(tyname);
let s = cstr.to_str()
.expect("Lua type has invalid bytes!");
println!("ty = {}, typ = {}", lua_type, typ);
Err(crate::Error::UnexpectedType(exp_s.to_string(), s.to_string()))
}
}

View File

@ -1,19 +1,54 @@
use crate::{Function, Table, PushToLuaStack};
use std::ffi::CStr;
use crate::{AnyUserdata, FromLuaStack, Function, PushToLuaStack, State, Table};
use mlua_sys as lua;
#[derive(Clone)]
pub enum Value<'a> {
Nil,
Number(f64),
String(String),
Function(Function<'a>),
Table(Table<'a>),
Userdata(AnyUserdata<'a>),
}
impl<'a> PushToLuaStack for Value<'a> {
impl<'a> Value<'a> {
pub fn is_nil(&self) -> bool {
matches!(self, Value::Nil)
}
pub fn type_name(&self) -> &'static str {
match self {
Value::Nil => "Nil",
Value::Number(_) => "Number",
Value::String(_) => "String",
Value::Function(_) => "Function",
Value::Table(_) => "Table",
Value::Userdata(_) => todo!(),
}
}
pub fn as_userdata(&self) -> crate::Result<&AnyUserdata> {
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<()> {
let s = state.state_ptr();
match self {
Value::Nil => {
state.ensure_stack(1)?;
lua::lua_pushnil(s);
}
Value::Number(n) => {
state.ensure_stack(1)?;
lua::lua_pushnumber(s, *n);
@ -25,14 +60,91 @@ impl<'a> PushToLuaStack for Value<'a> {
let cstr = s.as_ptr() as *const i8;
lua::lua_pushstring(state.state_ptr(), cstr);
}
Value::Function(f) => {
f.push_to_lua_stack(state)?;
},
Value::Table(t) => {
t.push_to_lua_stack(state)?;
}
Value::Function(f) => f.push_to_lua_stack(state)?,
Value::Table(t) => t.push_to_lua_stack(state)?,
Value::Userdata(ud) => ud.push_to_lua_stack(state)?,
}
Ok(())
}
}
impl<'a> FromLuaStack<'a> for Value<'a> {
unsafe fn from_lua_stack(state: &'a State) -> crate::Result<Self> {
let s = state.state_ptr();
let ty = lua::lua_type(s, -1);
let val = match ty {
lua::LUA_TNIL => {
lua::lua_pop(s, 1);
Ok(Value::Nil)
},
lua::LUA_TNUMBER => {
let n = lua::lua_tonumber(s, -1);
lua::lua_pop(s, 1);
Ok(Value::Number(n))
}
lua::LUA_TSTRING => {
let cstr = lua::lua_tostring(s, -1);
lua::lua_pop(s, 1);
let cstr = CStr::from_ptr(cstr);
let lua_str = cstr.to_str().unwrap().to_string();
Ok(Value::String(lua_str))
},
lua::LUA_TFUNCTION => {
Function::from_lua_stack(state)
.map(|f| Value::Function(f))
},
lua::LUA_TTABLE => {
Table::from_lua_stack(state)
.map(|t| Value::Table(t))
},
_ => {
let s = lua::lua_typename(s, ty);
let s = CStr::from_ptr(s);
let s = s.to_str().unwrap();
unimplemented!("Not yet able to get '{}' as a value from the stack", s);
}
};
val
}
}
pub trait AsLua<'a> {
fn as_lua(&self, lua: &'a State) -> crate::Result<Value<'a>>;
}
pub trait FromLua: Sized {
fn from_lua(lua: &State, val: Value) -> crate::Result<Self>;
}
/* impl<'a> AsLua<'a> for Value<'a> {
fn as_lua(&self, lua: &'a State) -> crate::Result<Value<'a>> {
Ok(self.clone())
}
} */
/* impl<'a, T: AsLua<'a>> PushToLuaStack<'a> for T {
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<'a, T: FromLua> FromLuaStack<'a> for T {
unsafe fn from_lua_stack(state: &'a State) -> crate::Result<Self> {
let v = Value::from_lua_stack(state)?;
T::from_lua(state, v)
}
}
impl<'a> AsLua<'a> for () {
fn as_lua(&self, lua: &'a State) -> crate::Result<Value<'a>> {
Ok(Value::Nil)
}
}

50
util.lua Executable file
View File

@ -0,0 +1,50 @@
---
---Print a formatted string. Put `{}` where you want something to be formatted in its place.
---
---```lua
---local yummy_food = "chips"
---printf("Lets eat {} together!", yummy_food)
---```
---
---The items provided in the variable arguments must implement `tostring`!
---
---@param str string
---@param ... any
function printf(str, ...)
local fleft, fright = str:find("{}")
local formatted = ""
-- current vararg index
local arg = 1
while (fleft ~= nil and fright ~= nil) do
formatted = formatted .. str:sub(0, fleft - 1)
formatted = formatted .. tostring(select(arg, ...))
str = str:sub(fright + 1, str:len())
fleft, fright = str:find("{}")
arg = arg + 1
end
formatted = formatted .. str
print(formatted)
end
---
---Recursively dumps a table as a string.
---
---@param obj table
---@return string
---@nodiscard
function dump_table(obj)
if type(obj) == 'table' then
local s = '{ '
for k,v in pairs(obj) do
if type(k) ~= 'number' then k = '"'..k..'"' end
s = s .. '['..k..'] = ' .. dump_table(v) .. ','
end
return s .. '} '
else
return tostring(obj)
end
end