From 25f4116278f47c7745c0b29eefede2567c9c3cd6 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 10 Feb 2024 14:48:03 -0500 Subject: [PATCH] Create UserdataRefMut, create tests for UserdataRef and UserdataRefMut --- .vscode/launch.json | 21 ++++ src/tests.rs | 16 ++- src/userdata/borrow.rs | 92 ++++++++++++++++- src/userdata/borrow_mut.rs | 195 +++++++++++++++++++++++++++++++++++++ src/userdata/mod.rs | 160 ++++++++++++++++++++++++------ 5 files changed, 452 insertions(+), 32 deletions(-) create mode 100755 src/userdata/borrow_mut.rs diff --git a/.vscode/launch.json b/.vscode/launch.json index f0215e5..45f546b 100755 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -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}" } ] } \ No newline at end of file diff --git a/src/tests.rs b/src/tests.rs index c63feb5..669e709 100755 --- a/src/tests.rs +++ b/src/tests.rs @@ -3,8 +3,8 @@ use std::cell::Ref; use crate::{MetaMethod, State, Userdata, UserdataBuilder}; pub struct Vec2 { - x: f32, - y: f32, + pub x: f32, + pub y: f32, } impl Userdata for Vec2 { @@ -28,6 +28,18 @@ impl Userdata for Vec2 { 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,)| { let lx = lhs.x; let ly = lhs.y; diff --git a/src/userdata/borrow.rs b/src/userdata/borrow.rs index b49b784..f2cc063 100755 --- a/src/userdata/borrow.rs +++ b/src/userdata/borrow.rs @@ -1,6 +1,6 @@ use std::{cell::Ref, mem, ops::Deref}; -use crate::{AnyUserdata, Result, State, Userdata, UserdataBuilder}; +use crate::{AnyUserdata, Error, Result, State, Userdata, UserdataBuilder}; enum Borrow<'a, T> { Wrapped(Ref<'a, T>), @@ -64,10 +64,18 @@ impl<'a, T: Userdata + 'static> Userdata for UserdataRef<'a, T> { 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(()) } @@ -76,3 +84,85 @@ impl<'a, T: Userdata + 'static> Userdata for UserdataRef<'a, T> { name } } + +#[cfg(test)] +mod tests { + use std::{borrow::Borrow, cell::{Ref, RefCell}}; + + use crate::{tests::Vec2, AnyUserdata, 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>("v2")?; + assert_eq!(v2.x, 100.0); + assert_eq!(v2.y, 10.0); + + Ok(()) + } +} \ No newline at end of file diff --git a/src/userdata/borrow_mut.rs b/src/userdata/borrow_mut.rs new file mode 100755 index 0000000..d76490c --- /dev/null +++ b/src/userdata/borrow_mut.rs @@ -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> 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::::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::>()?; + + 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::>()?; + + 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(()) + } +} \ No newline at end of file diff --git a/src/userdata/mod.rs b/src/userdata/mod.rs index 9dec6f7..5d1c23f 100755 --- a/src/userdata/mod.rs +++ b/src/userdata/mod.rs @@ -1,4 +1,4 @@ -use std::{cell::{OnceCell, Ref, RefCell, RefMut}, collections::HashMap, ffi::CStr, marker::PhantomData, mem, ops::{Deref, DerefMut}, sync::Arc}; +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, Function, IntoLuaVec, LuaRef, PushToLuaStack, StackGuard, State, Table, Value, ValueVec}; @@ -15,6 +15,11 @@ 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 pub enum MetaMethod { @@ -101,6 +106,8 @@ impl<'a> PushToLuaStack<'a> for MetaMethod { } pub type UserdataFn<'a> = Box) -> crate::Result>>; +pub type UserdataGetterFn<'a> = Arc) -> crate::Result<*const ()>>>>; +pub type UserdataMutGetterFn<'a> = Arc) -> crate::Result<*mut ()>>>>; pub struct UserdataBuilder<'a, T> { pub(crate) name: String, @@ -110,6 +117,7 @@ pub struct UserdataBuilder<'a, T> { pub(crate) meta_methods: HashMap>, pub(crate) wrapped_getter: Arc) -> crate::Result<*const ()>>>>, + pub(crate) wrapped_getter_mut: Arc) -> crate::Result<*mut ()>>>>, _marker: PhantomData, } @@ -123,6 +131,7 @@ impl<'a, T: Userdata> UserdataBuilder<'a, T> { functions: HashMap::new(), meta_methods: HashMap::new(), wrapped_getter: Arc::new(OnceCell::new()), + wrapped_getter_mut: Arc::new(OnceCell::new()), _marker: PhantomData, } } @@ -135,24 +144,22 @@ impl<'a, T: Userdata> UserdataBuilder<'a, T> { { let wrapped = self.wrapped_getter.clone(); let wrapped: Arc) -> Result<*const (), crate::Error>>>> = unsafe { mem::transmute(wrapped) }; - let fn_name = Arc::new(name.to_string()); + + 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 if let Some(getter) = wrapped.get() { - let this_ptr = match getter(this.clone()) { - Ok(v) => v, - Err(e) => { - return Err(crate::Error::BadArgument { - func: Some(fn_name.deref().clone()), - arg_index: 1, - arg_name: Some("self".to_string()), - error: Arc::new(e), - }); - } - }; + let this_ptr = Self::result_to_bad_arg( + getter(this.clone()), + &ud_name, + &fn_name, + 1, + Some("self") + )?; let this_ptr = this_ptr.cast::(); let this = unsafe { &*this_ptr }; @@ -173,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) -> 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::()?; 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::(); + let this = unsafe { this_ptr.as_mut().unwrap() }; + + f(lua, this, v_arg).as_lua(lua) + } else { + let mut this = this.as_mut::()?; + + 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(res: crate::Result, ud_name: &str, fn_name: &str) -> crate::Result { + fn arg_result_to_bad_arg(res: crate::Result, ud_name: &str, fn_name: &str) -> crate::Result { res.map_err(|e| match e { crate::Error::ValueVecError { value_idx, error } => { let full_name = format!("{}.{}", ud_name, fn_name); @@ -200,17 +228,27 @@ impl<'a, T: Userdata> UserdataBuilder<'a, T> { }) } + fn result_to_bad_arg(res: crate::Result, ud_name: &str, fn_name: &str, arg_idx: i32, arg_name: Option<&str>) -> crate::Result { + 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(&mut self, name: &str, f: F) -> &mut Self where F: Fn(&'a State, A) -> crate::Result + '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)) }; @@ -226,20 +264,83 @@ impl<'a, T: Userdata> UserdataBuilder<'a, T> { R: AsLua<'a>, T: Userdata + 'static { + let wrapped = self.wrapped_getter.clone(); + let wrapped: Arc) -> 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 - //this.unsafe_ud.as_ptr_unchecked() - let this = this.as_ref::()?; - - 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::(); + 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::()?; + + 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(&mut self, name: &str, f: F) -> &mut Self + where + F: Fn(&'a State, &mut T, A) -> crate::Result + 'static, + A: FromLuaVec<'a>, + R: AsLua<'a>, + T: Userdata + 'static + { + let wrapped = self.wrapped_getter_mut.clone(); + let wrapped: Arc) -> 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::(); + 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::()?; + + f(lua, this.deref_mut(), args).and_then(|r| r.as_lua(lua)) + } }; self.functions.insert(name.to_string(), Box::new(wrap)); @@ -254,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::()?; - 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)) @@ -280,6 +381,7 @@ impl<'a, T: Userdata> UserdataBuilder<'a, T> { self.functions = other.functions; self.meta_methods = other.meta_methods; self.wrapped_getter = other.wrapped_getter; + self.wrapped_getter_mut = other.wrapped_getter_mut; } }