Add set_nth_user_value and get_nth_user_value to AnyUserData

with `n` up to 65535 for all Lua versions.
This commit is contained in:
Alex Orlenko 2021-11-08 21:16:31 +00:00
parent 2ea2b1f4fb
commit 153502ec73
No known key found for this signature in database
GPG key ID: 4C150C250863B96D
5 changed files with 228 additions and 51 deletions

View file

@ -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::<T>()?;
#[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()))
}

View file

@ -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::<UserDataCell<T>>(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::<UserDataCell<Rc<RefCell<T>>>>())
let ud =
ffi::lua_newuserdata(state, mem::size_of::<UserDataCell<Rc<RefCell<T>>>>());
// 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: T) -> Box<dyn FnOnce() + 'static> {

View file

@ -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<T: 'static + UserData>(&self) -> Result<T> {
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::<UserDataCell<T>>(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::<UserDataCell<T>>(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<V: ToLua<'lua>>(&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<V: ToLua<'lua>>(&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<V: FromLua<'lua>>(&self) -> Result<V> {
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<V: FromLua<'lua>>(&self, n: usize) -> Result<V> {
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 <Option<Table>>::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`.

View file

@ -287,6 +287,17 @@ pub unsafe fn push_userdata<T>(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<T>(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::<T>(), nuvalue) as *mut T
})?;
ptr::write(ud, t);
Ok(())
}
#[inline]
pub unsafe fn get_userdata<T>(state: *mut ffi::lua_State, index: c_int) -> *mut T {
let ud = ffi::lua_touserdata(state, index) as *mut T;

View file

@ -299,7 +299,7 @@ fn test_userdata_take() -> Result<()> {
fn check_userdata_take(lua: &Lua, userdata: AnyUserData, rc: Arc<i64>) -> 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::<MyUserdata>()?;
@ -313,6 +313,7 @@ fn test_userdata_take() -> Result<()> {
let value = userdata_copy.take::<MyUserdata>()?;
assert_eq!(*value.0, 18);
drop(value);
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&rc), 1);
match userdata.borrow::<MyUserdata>() {
@ -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::<String>()?, "hello");
assert!(ud.get_user_value::<u32>().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::<String>(1)?, "hello");
assert_eq!(ud.get_nth_user_value::<String>(2)?, "world");
assert_eq!(ud.get_nth_user_value::<Value>(3)?, Value::Nil);
assert_eq!(ud.get_nth_user_value::<i32>(65535)?, 321);
assert!(ud.get_nth_user_value::<Value>(0).is_err());
assert!(ud.get_nth_user_value::<Value>(65536).is_err());
Ok(())
}