From 153502ec736e48e0d9748b91784307d309dc7b98 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Mon, 8 Nov 2021 21:16:31 +0000 Subject: [PATCH] Add `set_nth_user_value` and `get_nth_user_value` to `AnyUserData` with `n` up to 65535 for all Lua versions. --- src/lua.rs | 17 ++++- src/scope.rs | 56 ++++++++++++--- src/userdata.rs | 174 +++++++++++++++++++++++++++++++++++++--------- src/util.rs | 11 +++ tests/userdata.rs | 21 ++++-- 5 files changed, 228 insertions(+), 51 deletions(-) diff --git a/src/lua.rs b/src/lua.rs index 1203ebf..b1567a9 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -31,10 +31,15 @@ use crate::util::{ self, assert_stack, callback_error, check_stack, get_destructed_userdata_metatable, get_gc_metatable, get_gc_userdata, get_main_state, get_userdata, init_error_registry, init_gc_metatable, init_userdata_metatable, pop_error, push_gc_userdata, push_string, - push_table, push_userdata, rawset_field, safe_pcall, safe_xpcall, StackGuard, WrappedFailure, + push_table, rawset_field, safe_pcall, safe_xpcall, StackGuard, WrappedFailure, }; use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value}; +#[cfg(not(feature = "lua54"))] +use crate::util::push_userdata; +#[cfg(feature = "lua54")] +use crate::{userdata::USER_VALUE_MAXSLOT, util::push_userdata_uv}; + #[cfg(not(feature = "send"))] use std::rc::Rc; @@ -2215,10 +2220,20 @@ impl Lua { // We push metatable first to ensure having correct metatable with `__gc` method ffi::lua_pushnil(self.state); self.push_userdata_metatable::()?; + #[cfg(not(feature = "lua54"))] push_userdata(self.state, data)?; + #[cfg(feature = "lua54")] + push_userdata_uv(self.state, data, USER_VALUE_MAXSLOT as c_int)?; ffi::lua_replace(self.state, -3); ffi::lua_setmetatable(self.state, -2); + // Set empty environment for Lua 5.1 + #[cfg(any(feature = "lua51", feature = "luajit"))] + protect_lua!(self.state, 1, 1, fn(state) { + ffi::lua_newtable(state); + ffi::lua_setuservalue(state, -2); + })?; + Ok(AnyUserData(self.pop_ref())) } diff --git a/src/scope.rs b/src/scope.rs index 79ce558..ea31f0b 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -23,6 +23,9 @@ use crate::util::{ }; use crate::value::{FromLua, FromLuaMulti, MultiValue, ToLua, ToLuaMulti, Value}; +#[cfg(feature = "lua54")] +use crate::userdata::USER_VALUE_MAXSLOT; + #[cfg(feature = "async")] use { crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue}, @@ -197,12 +200,22 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { return vec![]; } - // Clear uservalue - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - ffi::lua_pushnil(state); + // 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"))] + { + 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); + { + ud.lua.push_ref(&newtable.0); + ffi::lua_setuservalue(state, -2); + } vec![Box::new(take_userdata::>(state))] }); @@ -323,8 +336,19 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { let _sg = StackGuard::new(lua.state); check_stack(lua.state, 13)?; + #[allow(clippy::let_and_return)] let data_ptr = protect_lua!(lua.state, 0, 1, |state| { - 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"))] + { + ffi::lua_newtable(state); + ffi::lua_setuservalue(state, -2); + } + + ud })?; // Prepare metatable, add meta methods first and then meta fields let meta_methods_nrec = ud_methods.meta_methods.len() + ud_fields.meta_fields.len() + 1; @@ -416,12 +440,22 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { ffi::lua_pop(state, 1); ud.lua.deregister_userdata_metatable(mt_ptr); - // Clear uservalue - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - ffi::lua_pushnil(state); + // 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"))] + { + 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); + { + ud.lua.push_ref(&newtable.0); + ffi::lua_setuservalue(state, -2); + } // A hack to drop non-static `T` unsafe fn seal(t: T) -> Box { diff --git a/src/userdata.rs b/src/userdata.rs index 877f2da..3d2f637 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -23,12 +23,15 @@ use crate::types::{Callback, LuaRef, MaybeSend}; use crate::util::{check_stack, get_userdata, take_userdata, StackGuard}; use crate::value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti}; -#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))] -use crate::value::Value; +#[cfg(feature = "lua54")] +use std::os::raw::c_int; #[cfg(feature = "async")] use crate::types::AsyncCallback; +#[cfg(feature = "lua54")] +pub(crate) const USER_VALUE_MAXSLOT: usize = 8; + /// Kinds of metamethods that can be overridden. /// /// Currently, this mechanism does not allow overriding the `__gc` metamethod, since there is @@ -819,11 +822,13 @@ impl<'lua> AnyUserData<'lua> { /// Takes out the value of `UserData` and sets the special "destructed" metatable that prevents /// any further operations with this userdata. + /// + /// All associated user values will be also cleared. pub fn take(&self) -> Result { let lua = self.0.lua; unsafe { let _sg = StackGuard::new(lua.state); - check_stack(lua.state, 2)?; + check_stack(lua.state, 3)?; let type_id = lua.push_userdata_ref(&self.0)?; match type_id { @@ -831,12 +836,22 @@ impl<'lua> AnyUserData<'lua> { // Try to borrow userdata exclusively let _ = (*get_userdata::>(lua.state, -1)).try_borrow_mut()?; - // Clear uservalue - #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] - ffi::lua_pushnil(lua.state); + // Clear associated user values + #[cfg(feature = "lua54")] + for i in 1..=USER_VALUE_MAXSLOT { + ffi::lua_pushnil(lua.state); + ffi::lua_setiuservalue(lua.state, -2, i as c_int); + } + #[cfg(any(feature = "lua53", feature = "lua52"))] + { + ffi::lua_pushnil(lua.state); + ffi::lua_setuservalue(lua.state, -2); + } #[cfg(any(feature = "lua51", feature = "luajit"))] - protect_lua!(lua.state, 0, 1, fn(state) ffi::lua_newtable(state))?; - ffi::lua_setuservalue(lua.state, -2); + protect_lua!(lua.state, 1, 1, fn(state) { + ffi::lua_newtable(state); + ffi::lua_setuservalue(state, -2); + })?; Ok(take_userdata::>(lua.state).into_inner()) } @@ -848,27 +863,76 @@ impl<'lua> AnyUserData<'lua> { /// Sets an associated value to this `AnyUserData`. /// /// The value may be any Lua value whatsoever, and can be retrieved with [`get_user_value`]. - /// As Lua < 5.3 allows to store only tables, the value will be stored in a table at index 1. + /// + /// This is the same as calling [`set_nth_user_value`] with `n` set to 1. /// /// [`get_user_value`]: #method.get_user_value + /// [`set_nth_user_value`]: #method.set_nth_user_value pub fn set_user_value>(&self, v: V) -> Result<()> { + self.set_nth_user_value(1, v) + } + + /// Sets an associated `n`th value to this `AnyUserData`. + /// + /// The value may be any Lua value whatsoever, and can be retrieved with [`get_nth_user_value`]. + /// `n` starts from 1 and can be up to 65535. + /// + /// This is supported for all Lua versions. + /// In Lua 5.4 first 7 elements are stored in a most efficient way. + /// For other Lua versions this functionality is provided using a wrapping table. + /// + /// [`get_nth_user_value`]: #method.get_nth_user_value + pub fn set_nth_user_value>(&self, n: usize, v: V) -> Result<()> { + if n < 1 || n > u16::MAX as usize { + return Err(Error::RuntimeError( + "user value index out of bounds".to_string(), + )); + } + let lua = self.0.lua; - #[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))] - let v = { - // Lua <= 5.2 allows to store only a table. Then we will wrap the value. - let t = lua.create_table_with_capacity(1, 0)?; - t.raw_set(1, v)?; - Value::Table(t) - }; - #[cfg(any(feature = "lua54", feature = "lua53"))] - let v = v.to_lua(lua)?; unsafe { let _sg = StackGuard::new(lua.state); - check_stack(lua.state, 3)?; + check_stack(lua.state, 5)?; lua.push_userdata_ref(&self.0)?; - lua.push_value(v)?; - ffi::lua_setuservalue(lua.state, -2); + lua.push_value(v.to_lua(lua)?)?; + + #[cfg(feature = "lua54")] + if n < USER_VALUE_MAXSLOT { + ffi::lua_setiuservalue(lua.state, -2, n as c_int); + return Ok(()); + } + + // Multiple (extra) user values are emulated by storing them in a table + + let getuservalue_t = |state, idx| { + #[cfg(feature = "lua54")] + return ffi::lua_getiuservalue(state, idx, USER_VALUE_MAXSLOT as c_int); + #[cfg(not(feature = "lua54"))] + return ffi::lua_getuservalue(state, idx); + }; + let getn = |n: usize| { + #[cfg(feature = "lua54")] + return n - USER_VALUE_MAXSLOT + 1; + #[cfg(not(feature = "lua54"))] + return n; + }; + + protect_lua!(lua.state, 2, 0, |state| { + if getuservalue_t(lua.state, -2) != ffi::LUA_TTABLE { + // Create a new table to use as uservalue + ffi::lua_pop(lua.state, 1); + ffi::lua_newtable(state); + ffi::lua_pushvalue(state, -1); + + #[cfg(feature = "lua54")] + ffi::lua_setiuservalue(lua.state, -4, USER_VALUE_MAXSLOT as c_int); + #[cfg(not(feature = "lua54"))] + ffi::lua_setuservalue(lua.state, -4); + } + ffi::lua_pushvalue(state, -2); + ffi::lua_rawseti(state, -2, getn(n) as ffi::lua_Integer); + })?; Ok(()) } @@ -876,26 +940,68 @@ impl<'lua> AnyUserData<'lua> { /// Returns an associated value set by [`set_user_value`]. /// - /// For Lua < 5.3 the value will be automatically extracted from the table wrapper from index 1. + /// This is the same as calling [`get_nth_user_value`] with `n` set to 1. /// /// [`set_user_value`]: #method.set_user_value + /// [`get_nth_user_value`]: #method.get_nth_user_value pub fn get_user_value>(&self) -> Result { + self.get_nth_user_value(1) + } + + /// Returns an associated `n`th value set by [`set_nth_user_value`]. + /// + /// `n` starts from 1 and can be up to 65535. + /// + /// This is supported for all Lua versions. + /// In Lua 5.4 first 7 elements are stored in a most efficient way. + /// For other Lua versions this functionality is provided using a wrapping table. + /// + /// [`set_nth_user_value`]: #method.set_nth_user_value + pub fn get_nth_user_value>(&self, n: usize) -> Result { + if n < 1 || n > u16::MAX as usize { + return Err(Error::RuntimeError( + "user value index out of bounds".to_string(), + )); + } + let lua = self.0.lua; - let res = unsafe { + unsafe { let _sg = StackGuard::new(lua.state); - check_stack(lua.state, 3)?; + check_stack(lua.state, 4)?; lua.push_userdata_ref(&self.0)?; - ffi::lua_getuservalue(lua.state, -1); - lua.pop_value() - }; - #[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))] - return match >::from_lua(res, lua)? { - Some(t) => t.get(1), - None => V::from_lua(Value::Nil, lua), - }; - #[cfg(any(feature = "lua54", feature = "lua53"))] - V::from_lua(res, lua) + + #[cfg(feature = "lua54")] + if n < USER_VALUE_MAXSLOT { + ffi::lua_getiuservalue(lua.state, -1, n as c_int); + return V::from_lua(lua.pop_value(), lua); + } + + // Multiple (extra) user values are emulated by storing them in a table + + let getuservalue_t = |state, idx| { + #[cfg(feature = "lua54")] + return ffi::lua_getiuservalue(state, idx, USER_VALUE_MAXSLOT as c_int); + #[cfg(not(feature = "lua54"))] + return ffi::lua_getuservalue(state, idx); + }; + let getn = |n: usize| { + #[cfg(feature = "lua54")] + return n - USER_VALUE_MAXSLOT + 1; + #[cfg(not(feature = "lua54"))] + return n; + }; + + protect_lua!(lua.state, 1, 1, |state| { + if getuservalue_t(lua.state, -1) != ffi::LUA_TTABLE { + ffi::lua_pushnil(lua.state); + return; + } + ffi::lua_rawgeti(state, -1, getn(n) as ffi::lua_Integer); + })?; + + V::from_lua(lua.pop_value(), lua) + } } /// Returns a metatable of this `UserData`. diff --git a/src/util.rs b/src/util.rs index e995cfb..bb7026e 100644 --- a/src/util.rs +++ b/src/util.rs @@ -287,6 +287,17 @@ pub unsafe fn push_userdata(state: *mut ffi::lua_State, t: T) -> Result<()> { Ok(()) } +// Internally uses 3 stack spaces, does not call checkstack. +#[cfg(feature = "lua54")] +#[inline] +pub unsafe fn push_userdata_uv(state: *mut ffi::lua_State, t: T, nuvalue: c_int) -> Result<()> { + let ud = protect_lua!(state, 0, 1, |state| { + ffi::lua_newuserdatauv(state, mem::size_of::(), nuvalue) as *mut T + })?; + ptr::write(ud, t); + Ok(()) +} + #[inline] pub unsafe fn get_userdata(state: *mut ffi::lua_State, index: c_int) -> *mut T { let ud = ffi::lua_touserdata(state, index) as *mut T; diff --git a/tests/userdata.rs b/tests/userdata.rs index 958da92..a96dbc1 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -299,7 +299,7 @@ fn test_userdata_take() -> Result<()> { fn check_userdata_take(lua: &Lua, userdata: AnyUserData, rc: Arc) -> Result<()> { lua.globals().set("userdata", userdata.clone())?; - assert_eq!(Arc::strong_count(&rc), 2); + assert_eq!(Arc::strong_count(&rc), 3); let userdata_copy = userdata.clone(); { let _value = userdata.borrow::()?; @@ -313,6 +313,7 @@ fn test_userdata_take() -> Result<()> { let value = userdata_copy.take::()?; assert_eq!(*value.0, 18); drop(value); + lua.gc_collect()?; assert_eq!(Arc::strong_count(&rc), 1); match userdata.borrow::() { @@ -333,6 +334,7 @@ fn test_userdata_take() -> Result<()> { let rc = Arc::new(18); let userdata = lua.create_userdata(MyUserdata(rc.clone()))?; + userdata.set_nth_user_value(2, MyUserdata(rc.clone()))?; check_userdata_take(&lua, userdata, rc)?; // Additionally check serializable userdata @@ -340,6 +342,7 @@ fn test_userdata_take() -> Result<()> { { let rc = Arc::new(18); let userdata = lua.create_ser_userdata(MyUserdata(rc.clone()))?; + userdata.set_nth_user_value(2, MyUserdata(rc.clone()))?; check_userdata_take(&lua, userdata, rc)?; } @@ -369,16 +372,24 @@ fn test_destroy_userdata() -> Result<()> { } #[test] -fn test_user_value() -> Result<()> { +fn test_user_values() -> Result<()> { struct MyUserData; impl UserData for MyUserData {} let lua = Lua::new(); let ud = lua.create_userdata(MyUserData)?; - ud.set_user_value("hello")?; - assert_eq!(ud.get_user_value::()?, "hello"); - assert!(ud.get_user_value::().is_err()); + + ud.set_nth_user_value(1, "hello")?; + ud.set_nth_user_value(2, "world")?; + ud.set_nth_user_value(65535, 321)?; + assert_eq!(ud.get_nth_user_value::(1)?, "hello"); + assert_eq!(ud.get_nth_user_value::(2)?, "world"); + assert_eq!(ud.get_nth_user_value::(3)?, Value::Nil); + assert_eq!(ud.get_nth_user_value::(65535)?, 321); + + assert!(ud.get_nth_user_value::(0).is_err()); + assert!(ud.get_nth_user_value::(65536).is_err()); Ok(()) }