diff --git a/src/lib.rs b/src/lib.rs index 1a5ea36..b4597b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,6 +121,7 @@ pub use crate::types::{Integer, LightUserData, Number, RegistryKey}; pub use crate::userdata::{ AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, }; +pub use crate::userdata_impl::UserDataRegistrar; pub use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value}; #[cfg(not(feature = "luau"))] diff --git a/src/lua.rs b/src/lua.rs index 26bec0e..b26dfe0 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1763,23 +1763,12 @@ impl Lua { /// Otherwise, the userdata object will have an empty metatable. /// /// All userdata instances of the same type `T` shares the same metatable. + #[inline] pub fn create_any_userdata(&self, data: T) -> Result where T: MaybeSend + 'static, { - unsafe { - self.make_userdata_with_metatable(UserDataCell::new(data), || { - // Check if userdata/metatable is already registered - let type_id = TypeId::of::(); - if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) { - return Ok(table_id as Integer); - } - - // Create empty metatable - let registry = UserDataRegistrar::new(); - self.register_userdata_metatable::(registry) - }) - } + unsafe { self.make_any_userdata(UserDataCell::new(data)) } } /// Registers a custom Rust type in Lua to use in userdata objects. @@ -1891,12 +1880,10 @@ impl Lua { /// dropped. `Function` types will error when called, and `AnyUserData` will be typeless. It /// would be impossible to prevent handles to scoped values from escaping anyway, since you /// would always be able to smuggle them through Lua state. - pub fn scope<'lua, 'scope, R, F>(&'lua self, f: F) -> Result - where - 'lua: 'scope, - R: 'static, - F: FnOnce(&Scope<'lua, 'scope>) -> Result, - { + pub fn scope<'lua, 'scope, R>( + &'lua self, + f: impl FnOnce(&Scope<'lua, 'scope>) -> Result, + ) -> Result { f(&Scope::new(self)) } @@ -2961,6 +2948,23 @@ impl Lua { }) } + pub(crate) unsafe fn make_any_userdata(&self, data: UserDataCell) -> Result + where + T: 'static, + { + self.make_userdata_with_metatable(data, || { + // Check if userdata/metatable is already registered + let type_id = TypeId::of::(); + if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) { + return Ok(table_id as Integer); + } + + // Create empty metatable + let registry = UserDataRegistrar::new(); + self.register_userdata_metatable::(registry) + }) + } + unsafe fn make_userdata_with_metatable( &self, data: UserDataCell, diff --git a/src/prelude.rs b/src/prelude.rs index 9111282..bf88bc8 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -12,7 +12,7 @@ pub use crate::{ TableSequence as LuaTableSequence, Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, - Value as LuaValue, + UserDataRegistrar as LuaUserDataRegistrar, Value as LuaValue, }; #[cfg(not(feature = "luau"))] diff --git a/src/scope.rs b/src/scope.rs index 22bebc7..4dce3ef 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -2,8 +2,7 @@ use std::any::Any; use std::cell::{Cell, RefCell}; use std::marker::PhantomData; use std::mem; -use std::os::raw::{c_int, c_void}; -use std::rc::Rc; +use std::os::raw::c_int; #[cfg(feature = "serialize")] use serde::Serialize; @@ -66,7 +65,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { where A: FromLuaMulti<'callback>, R: IntoLuaMulti<'callback>, - F: 'scope + Fn(&'callback Lua, A) -> Result, + F: Fn(&'callback Lua, A) -> Result + 'scope, { // Safe, because 'scope must outlive 'callback (due to Self containing 'scope), however the // callback itself must be 'scope lifetime, so the function should not be able to capture @@ -99,7 +98,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { where A: FromLuaMulti<'callback>, R: IntoLuaMulti<'callback>, - F: 'scope + FnMut(&'callback Lua, A) -> Result, + F: FnMut(&'callback Lua, A) -> Result + 'scope, { let func = RefCell::new(func); self.create_function(move |lua, args| { @@ -144,7 +143,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { } } - /// Create a Lua userdata object from a custom userdata type. + /// Creates a Lua userdata object from a custom userdata type. /// /// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on /// scope drop, and does not require that the userdata type be Send (but still requires that the @@ -155,12 +154,18 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { /// [`Lua::scope`]: crate::Lua::scope pub fn create_userdata(&self, data: T) -> Result> where - T: 'static + UserData, + T: UserData + 'static, { - self.create_userdata_inner(UserDataCell::new(data)) + // Safe even though T may not be Send, because the parent Lua cannot be sent to another + // thread while the Scope is alive (or the returned AnyUserData handle even). + unsafe { + let ud = self.lua.make_userdata(UserDataCell::new(data))?; + self.seal_userdata::(&ud)?; + Ok(ud) + } } - /// Create a Lua userdata object from a custom serializable userdata type. + /// Creates a Lua userdata object from a custom serializable userdata type. /// /// This is a version of [`Lua::create_ser_userdata`] that creates a userdata which expires on /// scope drop, and does not require that the userdata type be Send (but still requires that the @@ -175,60 +180,125 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] pub fn create_ser_userdata(&self, data: T) -> Result> where - T: 'static + UserData + Serialize, + T: UserData + Serialize + 'static, { - self.create_userdata_inner(UserDataCell::new_ser(data)) - } - - fn create_userdata_inner(&self, data: UserDataCell) -> Result> - where - T: 'static + UserData, - { - // Safe even though T may not be Send, because the parent Lua cannot be sent to another - // thread while the Scope is alive (or the returned AnyUserData handle even). unsafe { - let ud = self.lua.make_userdata(data)?; - - #[cfg(any(feature = "lua51", feature = "luajit"))] - let newtable = self.lua.create_table()?; - let destructor: DestructorCallback = Box::new(move |ud| { - let state = ud.lua.state(); - let _sg = StackGuard::new(state); - assert_stack(state, 2); - - // Check that userdata is not destructed (via `take()` call) - if ud.lua.push_userdata_ref(&ud).is_err() { - return vec![]; - } - - // Clear associated user values - #[cfg(feature = "lua54")] - for i in 1..=USER_VALUE_MAXSLOT { - ffi::lua_pushnil(state); - ffi::lua_setiuservalue(state, -2, i as c_int); - } - #[cfg(any(feature = "lua53", feature = "lua52", feature = "luau"))] - { - ffi::lua_pushnil(state); - ffi::lua_setuservalue(state, -2); - } - #[cfg(any(feature = "lua51", feature = "luajit"))] - { - ud.lua.push_ref(&newtable.0); - ffi::lua_setuservalue(state, -2); - } - - vec![Box::new(take_userdata::>(state))] - }); - self.destructors - .borrow_mut() - .push((ud.0.clone(), destructor)); - + let ud = self.lua.make_userdata(UserDataCell::new_ser(data))?; + self.seal_userdata::(&ud)?; Ok(ud) } } - /// Create a Lua userdata object from a custom userdata type. + /// Creates a Lua userdata object from a reference to custom userdata type. + /// + /// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on + /// scope drop, and does not require that the userdata type be Send. This method takes non-'static + /// reference to the data. See [`Lua::scope`] for more details. + /// + /// Userdata created with this method will not be able to be mutated from Lua. + pub fn create_userdata_ref(&self, data: &'scope T) -> Result> + where + T: UserData + 'static, + { + unsafe { + let ud = self.lua.make_userdata(UserDataCell::new_ref(data))?; + self.seal_userdata::(&ud)?; + Ok(ud) + } + } + + /// Creates a Lua userdata object from a mutable reference to custom userdata type. + /// + /// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on + /// scope drop, and does not require that the userdata type be Send. This method takes non-'static + /// mutable reference to the data. See [`Lua::scope`] for more details. + pub fn create_userdata_ref_mut(&self, data: &'scope mut T) -> Result> + where + T: UserData + 'static, + { + unsafe { + let ud = self.lua.make_userdata(UserDataCell::new_ref_mut(data))?; + self.seal_userdata::(&ud)?; + Ok(ud) + } + } + + /// Creates a Lua userdata object from a reference to custom Rust type. + /// + /// This is a version of [`Lua::create_any_userdata`] that creates a userdata which expires on + /// scope drop, and does not require that the Rust type be Send. This method takes non-'static + /// reference to the data. See [`Lua::scope`] for more details. + /// + /// Userdata created with this method will not be able to be mutated from Lua. + pub fn create_any_userdata_ref(&self, data: &'scope T) -> Result> + where + T: 'static, + { + unsafe { + let ud = self.lua.make_any_userdata(UserDataCell::new_ref(data))?; + self.seal_userdata::(&ud)?; + Ok(ud) + } + } + + /// Creates a Lua userdata object from a mutable reference to custom Rust type. + /// + /// This is a version of [`Lua::create_any_userdata`] that creates a userdata which expires on + /// scope drop, and does not require that the Rust type be Send. This method takes non-'static + /// mutable reference to the data. See [`Lua::scope`] for more details. + pub fn create_any_userdata_ref_mut(&self, data: &'scope mut T) -> Result> + where + T: 'static, + { + let lua = self.lua; + unsafe { + let ud = lua.make_any_userdata(UserDataCell::new_ref_mut(data))?; + self.seal_userdata::(&ud)?; + Ok(ud) + } + } + + /// Shortens the lifetime of a userdata to the lifetime of the scope. + unsafe fn seal_userdata(&self, ud: &AnyUserData<'lua>) -> Result<()> { + #[cfg(any(feature = "lua51", feature = "luajit"))] + let newtable = self.lua.create_table()?; + let destructor: DestructorCallback = Box::new(move |ud| { + let state = ud.lua.state(); + let _sg = StackGuard::new(state); + assert_stack(state, 2); + + // Check that userdata is not destructed (via `take()` call) + if ud.lua.push_userdata_ref(&ud).is_err() { + return vec![]; + } + + // Clear associated user values + #[cfg(feature = "lua54")] + for i in 1..=USER_VALUE_MAXSLOT { + ffi::lua_pushnil(state); + ffi::lua_setiuservalue(state, -2, i as c_int); + } + #[cfg(any(feature = "lua53", feature = "lua52", feature = "luau"))] + { + ffi::lua_pushnil(state); + ffi::lua_setuservalue(state, -2); + } + #[cfg(any(feature = "lua51", feature = "luajit"))] + { + ud.lua.push_ref(&newtable.0); + ffi::lua_setuservalue(state, -2); + } + + vec![Box::new(take_userdata::>(state))] + }); + self.destructors + .borrow_mut() + .push((ud.0.clone(), destructor)); + + Ok(()) + } + + /// Creates a Lua userdata object from a custom userdata type. /// /// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on /// scope drop, and does not require that the userdata type be Send or 'static. See @@ -253,10 +323,8 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { /// [`UserDataMethods`]: crate::UserDataMethods pub fn create_nonstatic_userdata(&self, data: T) -> Result> where - T: 'scope + UserData, + T: UserData + 'scope, { - let data = Rc::new(RefCell::new(data)); - // 'callback outliving 'scope is a lie to make the types work out, required due to the // inability to work with the more correct callback type that is universally quantified over // 'lua. This is safe though, because `UserData::add_methods` does not get to pick the 'lua @@ -264,8 +332,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { // parameters. fn wrap_method<'scope, 'lua, 'callback: 'scope, T: 'scope>( scope: &Scope<'lua, 'scope>, - data: Rc>, - ud_ptr: *const c_void, + ud_ptr: *const UserDataCell, method: NonStaticMethod<'callback, T>, ) -> Result> { // On methods that actually receive the userdata, we fake a type check on the passed in @@ -275,7 +342,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { // with a type mismatch, but here without this check would proceed as though you had // called the method on the original value (since we otherwise completely ignore the // first argument). - let check_ud_type = move |lua: &'callback Lua, value| { + let check_ud_type = move |lua: &Lua, value| -> Result<&UserDataCell> { if let Some(Value::UserData(ud)) = value { let state = lua.state(); unsafe { @@ -283,7 +350,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { check_stack(state, 2)?; lua.push_userdata_ref(&ud.0)?; if get_userdata(state, -1) as *const _ == ud_ptr { - return Ok(()); + return Ok(&*ud_ptr); } } }; @@ -293,8 +360,8 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { match method { NonStaticMethod::Method(method) => { let f = Box::new(move |lua, mut args: MultiValue<'callback>| { - check_ud_type(lua, args.pop_front())?; - let data = data.try_borrow().map_err(|_| Error::UserDataBorrowError)?; + let data = check_ud_type(lua, args.pop_front())?; + let data = data.try_borrow()?; method(lua, &*data, args) }); unsafe { scope.create_callback(f) } @@ -302,13 +369,11 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { NonStaticMethod::MethodMut(method) => { let method = RefCell::new(method); let f = Box::new(move |lua, mut args: MultiValue<'callback>| { - check_ud_type(lua, args.pop_front())?; + let data = check_ud_type(lua, args.pop_front())?; let mut method = method .try_borrow_mut() .map_err(|_| Error::RecursiveMutCallback)?; - let mut data = data - .try_borrow_mut() - .map_err(|_| Error::UserDataBorrowMutError)?; + let mut data = data.try_borrow_mut()?; (*method)(lua, &mut *data, args) }); unsafe { scope.create_callback(f) } @@ -342,8 +407,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { #[cfg(not(feature = "luau"))] #[allow(clippy::let_and_return)] let ud_ptr = protect_lua!(state, 0, 1, |state| { - let ud = - ffi::lua_newuserdata(state, mem::size_of::>>>()); + let ud = ffi::lua_newuserdata(state, mem::size_of::>()); // Set empty environment for Lua 5.1 #[cfg(any(feature = "lua51", feature = "luajit"))] @@ -352,16 +416,12 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { ffi::lua_setuservalue(state, -2); } - ud + ud as *const UserDataCell })?; #[cfg(feature = "luau")] let ud_ptr = { - crate::util::push_userdata::>>>( - state, - UserDataCell::new(data.clone()), - true, - )?; - ffi::lua_touserdata(state, -1) + crate::util::push_userdata(state, UserDataCell::new(data), true)?; + ffi::lua_touserdata(state, -1) as *const UserDataCell }; // Prepare metatable, add meta methods first and then meta fields @@ -369,8 +429,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { push_table(state, 0, meta_methods_nrec as c_int, true)?; for (k, m) in ud_methods.meta_methods { - let data = data.clone(); - lua.push_value(Value::Function(wrap_method(self, data, ud_ptr, m)?))?; + lua.push_value(Value::Function(wrap_method(self, ud_ptr, m)?))?; rawset_field(state, -2, MetaMethod::validate(&k)?)?; } for (k, f) in ud_fields.meta_fields { @@ -384,8 +443,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { if field_getters_nrec > 0 { push_table(state, 0, field_getters_nrec as c_int, true)?; for (k, m) in ud_fields.field_getters { - let data = data.clone(); - lua.push_value(Value::Function(wrap_method(self, data, ud_ptr, m)?))?; + lua.push_value(Value::Function(wrap_method(self, ud_ptr, m)?))?; rawset_field(state, -2, &k)?; } field_getters_index = Some(ffi::lua_absindex(state, -1)); @@ -396,8 +454,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { if field_setters_nrec > 0 { push_table(state, 0, field_setters_nrec as c_int, true)?; for (k, m) in ud_fields.field_setters { - let data = data.clone(); - lua.push_value(Value::Function(wrap_method(self, data, ud_ptr, m)?))?; + lua.push_value(Value::Function(wrap_method(self, ud_ptr, m)?))?; rawset_field(state, -2, &k)?; } field_setters_index = Some(ffi::lua_absindex(state, -1)); @@ -409,14 +466,13 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { // Create table used for methods lookup push_table(state, 0, methods_nrec as c_int, true)?; for (k, m) in ud_methods.methods { - let data = data.clone(); - lua.push_value(Value::Function(wrap_method(self, data, ud_ptr, m)?))?; + lua.push_value(Value::Function(wrap_method(self, ud_ptr, m)?))?; rawset_field(state, -2, &k)?; } methods_index = Some(ffi::lua_absindex(state, -1)); } - init_userdata_metatable::>>>( + init_userdata_metatable::>( state, metatable_index, field_getters_index, @@ -478,8 +534,8 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { mem::transmute(f) } - let ud = Box::new(seal(take_userdata::>>>(state))); - vec![ud] + let ud = take_userdata::>(state); + vec![Box::new(seal(ud))] }); self.destructors .borrow_mut() diff --git a/src/userdata.rs b/src/userdata.rs index ca28b53..bcc735a 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -578,12 +578,22 @@ pub trait UserData: Sized { } // Wraps UserData in a way to always implement `serde::Serialize` trait. -pub(crate) struct UserDataCell(RefCell>); +pub(crate) struct UserDataCell(RefCell>); impl UserDataCell { #[inline] pub(crate) fn new(data: T) -> Self { - UserDataCell(RefCell::new(UserDataWrapped::new(data))) + UserDataCell(RefCell::new(UserDataVariant::new(data))) + } + + #[inline] + pub(crate) fn new_ref(data: &T) -> Self { + UserDataCell(RefCell::new(UserDataVariant::new_ref(data))) + } + + #[inline] + pub(crate) fn new_ref_mut(data: &mut T) -> Self { + UserDataCell(RefCell::new(UserDataVariant::new_ref_mut(data))) } #[cfg(feature = "serialize")] @@ -592,7 +602,7 @@ impl UserDataCell { where T: Serialize + 'static, { - UserDataCell(RefCell::new(UserDataWrapped::new_ser(data))) + UserDataCell(RefCell::new(UserDataVariant::new_ser(data))) } // Immutably borrows the wrapped value. @@ -609,27 +619,42 @@ impl UserDataCell { pub(crate) fn try_borrow_mut(&self) -> Result> { self.0 .try_borrow_mut() - .map(|r| RefMut::map(r, |r| r.deref_mut())) .map_err(|_| Error::UserDataBorrowMutError) + .and_then(|r| { + RefMut::filter_map(r, |r| r.try_deref_mut().ok()) + .map_err(|_| Error::UserDataBorrowMutError) + }) } // Consumes this `UserDataCell`, returning the wrapped value. #[inline] - unsafe fn into_inner(self) -> T { + fn into_inner(self) -> Result { self.0.into_inner().into_inner() } } -pub(crate) enum UserDataWrapped { +pub(crate) enum UserDataVariant { Default(Box), + Ref(*const T), + RefMut(*mut T), #[cfg(feature = "serialize")] Serializable(Box), } -impl UserDataWrapped { +impl UserDataVariant { #[inline] fn new(data: T) -> Self { - UserDataWrapped::Default(Box::new(data)) + UserDataVariant::Default(Box::new(data)) + } + + #[inline] + fn new_ref(data: &T) -> Self { + UserDataVariant::Ref(data) + } + + #[inline] + fn new_ref_mut(data: &mut T) -> Self { + UserDataVariant::RefMut(data) } #[cfg(feature = "serialize")] @@ -638,26 +663,42 @@ impl UserDataWrapped { where T: Serialize + 'static, { - UserDataWrapped::Serializable(Box::new(data)) + UserDataVariant::Serializable(Box::new(data)) } #[inline] - unsafe fn into_inner(self) -> T { + fn try_deref_mut(&mut self) -> Result<&mut T> { match self { - Self::Default(data) => *data, + Self::Default(data) => Ok(data.deref_mut()), + Self::Ref(_) => Err(Error::UserDataBorrowMutError), + Self::RefMut(data) => unsafe { Ok(&mut **data) }, #[cfg(feature = "serialize")] - Self::Serializable(data) => *Box::from_raw(Box::into_raw(data) as *mut T), + Self::Serializable(data) => unsafe { Ok(&mut *(data.as_mut() as *mut _ as *mut T)) }, + } + } + + #[inline] + fn into_inner(self) -> Result { + match self { + Self::Default(data) => Ok(*data), + Self::Ref(_) | Self::RefMut(_) => Err(Error::UserDataTypeMismatch), + #[cfg(feature = "serialize")] + Self::Serializable(data) => unsafe { + Ok(*Box::from_raw(Box::into_raw(data) as *mut T)) + }, } } } -impl Deref for UserDataWrapped { +impl Deref for UserDataVariant { type Target = T; #[inline] fn deref(&self) -> &Self::Target { match self { Self::Default(data) => data, + Self::Ref(data) => unsafe { &**data }, + Self::RefMut(data) => unsafe { &**data }, #[cfg(feature = "serialize")] Self::Serializable(data) => unsafe { &*(data.as_ref() as *const _ as *const Self::Target) @@ -666,19 +707,6 @@ impl Deref for UserDataWrapped { } } -impl DerefMut for UserDataWrapped { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - Self::Default(data) => data, - #[cfg(feature = "serialize")] - Self::Serializable(data) => unsafe { - &mut *(data.as_mut() as *mut _ as *mut Self::Target) - }, - } - } -} - #[cfg(feature = "serialize")] struct UserDataSerializeError; @@ -771,7 +799,7 @@ impl<'lua> AnyUserData<'lua> { Some(type_id) if type_id == TypeId::of::() => { // Try to borrow userdata exclusively let _ = (*get_userdata::>(state, -1)).try_borrow_mut()?; - Ok(take_userdata::>(state).into_inner()) + take_userdata::>(state).into_inner() } _ => Err(Error::UserDataTypeMismatch), } @@ -1043,8 +1071,8 @@ impl<'lua> AnyUserData<'lua> { let ud = &*get_userdata::>(state, -1); match &*ud.0.try_borrow().map_err(|_| Error::UserDataBorrowError)? { - UserDataWrapped::Default(_) => Result::Ok(false), - UserDataWrapped::Serializable(_) => Result::Ok(true), + UserDataVariant::Serializable(_) => Result::Ok(true), + _ => Result::Ok(false), } }; is_serializable().unwrap_or(false) @@ -1184,8 +1212,8 @@ impl<'lua> Serialize for AnyUserData<'lua> { .map_err(|_| ser::Error::custom(Error::UserDataBorrowError))? }; match &*data { - UserDataWrapped::Default(_) => UserDataSerializeError.serialize(serializer), - UserDataWrapped::Serializable(ser) => ser.serialize(serializer), + UserDataVariant::Serializable(ser) => ser.serialize(serializer), + _ => UserDataSerializeError.serialize(serializer), } } } diff --git a/tests/scope.rs b/tests/scope.rs index 9640383..4fde34a 100644 --- a/tests/scope.rs +++ b/tests/scope.rs @@ -356,3 +356,107 @@ fn test_scope_nonstatic_userdata_drop() -> Result<()> { Ok(()) } + +#[test] +fn test_scope_userdata_ref() -> Result<()> { + let lua = Lua::new(); + + struct MyUserData(Cell); + + impl UserData for MyUserData { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_method("inc", |_, data, ()| { + data.0.set(data.0.get() + 1); + Ok(()) + }); + + methods.add_method("dec", |_, data, ()| { + data.0.set(data.0.get() - 1); + Ok(()) + }); + } + } + + let data = MyUserData(Cell::new(1)); + lua.scope(|scope| { + let ud = scope.create_userdata_ref(&data)?; + modify_userdata(&lua, ud) + })?; + assert_eq!(data.0.get(), 2); + + Ok(()) +} + +#[test] +fn test_scope_userdata_ref_mut() -> Result<()> { + let lua = Lua::new(); + + struct MyUserData(i64); + + impl UserData for MyUserData { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_method_mut("inc", |_, data, ()| { + data.0 += 1; + Ok(()) + }); + + methods.add_method_mut("dec", |_, data, ()| { + data.0 -= 1; + Ok(()) + }); + } + } + + let mut data = MyUserData(1); + lua.scope(|scope| { + let ud = scope.create_userdata_ref_mut(&mut data)?; + modify_userdata(&lua, ud) + })?; + assert_eq!(data.0, 2); + + Ok(()) +} + +#[test] +fn test_scope_any_userdata_ref() -> Result<()> { + let lua = Lua::new(); + + lua.register_userdata_type::>(|reg| { + reg.add_method("inc", |_, data, ()| { + data.set(data.get() + 1); + Ok(()) + }); + + reg.add_method("dec", |_, data, ()| { + data.set(data.get() - 1); + Ok(()) + }); + })?; + + let data = Cell::new(1i64); + lua.scope(|scope| { + let ud = scope.create_any_userdata_ref(&data)?; + modify_userdata(&lua, ud) + })?; + assert_eq!(data.get(), 2); + + Ok(()) +} + +fn modify_userdata(lua: &Lua, ud: AnyUserData) -> Result<()> { + let f: Function = lua + .load( + r#" + function(u) + u:inc() + u:dec() + u:inc() + end +"#, + ) + .eval()?; + + f.call(ud)?; + + Ok(()) +}