Create UserdataRefMut, create tests for UserdataRef and UserdataRefMut

This commit is contained in:
SeanOMik 2024-02-10 14:48:03 -05:00
parent 80b9a4ef35
commit 25f4116278
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
5 changed files with 452 additions and 32 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}"
}
]
}

View File

@ -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<Vec2>,)| {
let lx = lhs.x;
let ly = lhs.y;

View File

@ -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<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(())
}
}

View File

@ -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<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,
@ -110,6 +117,7 @@ pub struct UserdataBuilder<'a, T> {
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>,
}
@ -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<OnceCell<Box<dyn Fn(AnyUserdata<'_>) -> 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::<T>();
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<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);
@ -200,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))
};
@ -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<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
//this.unsafe_ud.as_ptr_unchecked()
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));
@ -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::<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))
@ -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;
}
}