From 62b53e218cdbb63c51a296adde3dea4c104ceea1 Mon Sep 17 00:00:00 2001 From: Alex Orlenko Date: Fri, 21 Mar 2025 15:11:09 +0000 Subject: [PATCH] Move some userdata helpers from `crate::util` to `crate::userdata::util` --- src/state/raw.rs | 9 +- src/userdata.rs | 8 +- src/userdata/registry.rs | 7 +- src/userdata/util.rs | 394 ++++++++++++++++++++++++++++++++++++- src/util/mod.rs | 6 +- src/util/userdata.rs | 410 +-------------------------------------- 6 files changed, 416 insertions(+), 418 deletions(-) diff --git a/src/state/raw.rs b/src/state/raw.rs index acdee12..1f94db8 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -23,13 +23,14 @@ use crate::types::{ MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc, }; use crate::userdata::{ - AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, UserDataStorage, + init_userdata_metatable, AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, + UserDataStorage, }; use crate::util::{ assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state, - get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, init_userdata_metatable, - pop_error, push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, - short_type_name, StackGuard, WrappedFailure, + get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, pop_error, + push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name, + StackGuard, WrappedFailure, }; use crate::value::{Nil, Value}; diff --git a/src/userdata.rs b/src/userdata.rs index 86cdbc6..b3be682 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -12,10 +12,7 @@ use crate::string::String; use crate::table::{Table, TablePairs}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::types::{MaybeSend, ValueRef}; -use crate::util::{ - borrow_userdata_scoped, borrow_userdata_scoped_mut, check_stack, get_userdata, push_string, - take_userdata, StackGuard, TypeIdHints, -}; +use crate::util::{check_stack, get_userdata, push_string, take_userdata, StackGuard}; use crate::value::Value; #[cfg(feature = "async")] @@ -32,6 +29,9 @@ pub(crate) use cell::UserDataStorage; pub use r#ref::{UserDataRef, UserDataRefMut}; pub use registry::UserDataRegistry; pub(crate) use registry::{RawUserDataRegistry, UserDataProxy}; +pub(crate) use util::{ + borrow_userdata_scoped, borrow_userdata_scoped_mut, init_userdata_metatable, TypeIdHints, +}; /// Kinds of metamethods that can be overridden. /// diff --git a/src/userdata/registry.rs b/src/userdata/registry.rs index f9e88c9..cfb6fe6 100644 --- a/src/userdata/registry.rs +++ b/src/userdata/registry.rs @@ -10,10 +10,11 @@ use crate::error::{Error, Result}; use crate::state::{Lua, LuaGuard}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::types::{Callback, MaybeSend}; -use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataStorage}; -use crate::util::{ - borrow_userdata_scoped, borrow_userdata_scoped_mut, get_userdata, short_type_name, TypeIdHints, +use crate::userdata::{ + borrow_userdata_scoped, borrow_userdata_scoped_mut, AnyUserData, MetaMethod, TypeIdHints, UserData, + UserDataFields, UserDataMethods, UserDataStorage, }; +use crate::util::{get_userdata, short_type_name}; use crate::value::Value; #[cfg(feature = "async")] diff --git a/src/userdata/util.rs b/src/userdata/util.rs index 8cff934..bc62a49 100644 --- a/src/userdata/util.rs +++ b/src/userdata/util.rs @@ -1,9 +1,11 @@ +use std::any::TypeId; use std::cell::Cell; use std::marker::PhantomData; use std::os::raw::c_int; use super::UserDataStorage; -use crate::util::{get_userdata, take_userdata}; +use crate::error::{Error, Result}; +use crate::util::{get_userdata, rawget_field, rawset_field, take_userdata}; // This is a trick to check if a type is `Sync` or not. // It uses leaked specialization feature from stdlib. @@ -34,6 +36,393 @@ pub(crate) fn is_sync() -> bool { is_sync.get() } +// Userdata type hints, used to match types of wrapped userdata +#[derive(Clone, Copy)] +pub(crate) struct TypeIdHints { + t: TypeId, + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + rc: TypeId, + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + rc_refcell: TypeId, + + #[cfg(feature = "userdata-wrappers")] + arc: TypeId, + #[cfg(feature = "userdata-wrappers")] + arc_mutex: TypeId, + #[cfg(feature = "userdata-wrappers")] + arc_rwlock: TypeId, + #[cfg(feature = "userdata-wrappers")] + arc_pl_mutex: TypeId, + #[cfg(feature = "userdata-wrappers")] + arc_pl_rwlock: TypeId, +} + +impl TypeIdHints { + pub(crate) fn new() -> Self { + Self { + t: TypeId::of::(), + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + rc: TypeId::of::>(), + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + rc_refcell: TypeId::of::>>(), + + #[cfg(feature = "userdata-wrappers")] + arc: TypeId::of::>(), + #[cfg(feature = "userdata-wrappers")] + arc_mutex: TypeId::of::>>(), + #[cfg(feature = "userdata-wrappers")] + arc_rwlock: TypeId::of::>>(), + #[cfg(feature = "userdata-wrappers")] + arc_pl_mutex: TypeId::of::>>(), + #[cfg(feature = "userdata-wrappers")] + arc_pl_rwlock: TypeId::of::>>(), + } + } + + #[inline(always)] + pub(crate) fn type_id(&self) -> TypeId { + self.t + } +} + +pub(crate) unsafe fn borrow_userdata_scoped( + state: *mut ffi::lua_State, + idx: c_int, + type_id: Option, + type_hints: TypeIdHints, + f: impl FnOnce(&T) -> R, +) -> Result { + match type_id { + Some(type_id) if type_id == type_hints.t => { + let ud = get_userdata::>(state, idx); + (*ud).try_borrow_scoped(|ud| f(ud)) + } + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Some(type_id) if type_id == type_hints.rc => { + let ud = get_userdata::>>(state, idx); + (*ud).try_borrow_scoped(|ud| f(ud)) + } + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Some(type_id) if type_id == type_hints.rc_refcell => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped(|ud| { + let ud = ud.try_borrow().map_err(|_| Error::UserDataBorrowError)?; + Ok(f(&ud)) + })? + } + + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc => { + let ud = get_userdata::>>(state, idx); + (*ud).try_borrow_scoped(|ud| f(ud)) + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_mutex => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped(|ud| { + let ud = ud.try_lock().map_err(|_| Error::UserDataBorrowError)?; + Ok(f(&ud)) + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_rwlock => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped(|ud| { + let ud = ud.try_read().map_err(|_| Error::UserDataBorrowError)?; + Ok(f(&ud)) + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_pl_mutex => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped(|ud| { + let ud = ud.try_lock().ok_or(Error::UserDataBorrowError)?; + Ok(f(&ud)) + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_pl_rwlock => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped(|ud| { + let ud = ud.try_read().ok_or(Error::UserDataBorrowError)?; + Ok(f(&ud)) + })? + } + _ => Err(Error::UserDataTypeMismatch), + } +} + +pub(crate) unsafe fn borrow_userdata_scoped_mut( + state: *mut ffi::lua_State, + idx: c_int, + type_id: Option, + type_hints: TypeIdHints, + f: impl FnOnce(&mut T) -> R, +) -> Result { + match type_id { + Some(type_id) if type_id == type_hints.t => { + let ud = get_userdata::>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| f(ud)) + } + + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Some(type_id) if type_id == type_hints.rc => { + let ud = get_userdata::>>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| match std::rc::Rc::get_mut(ud) { + Some(ud) => Ok(f(ud)), + None => Err(Error::UserDataBorrowMutError), + })? + } + #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] + Some(type_id) if type_id == type_hints.rc_refcell => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped(|ud| { + let mut ud = ud.try_borrow_mut().map_err(|_| Error::UserDataBorrowMutError)?; + Ok(f(&mut ud)) + })? + } + + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc => { + let ud = get_userdata::>>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| match std::sync::Arc::get_mut(ud) { + Some(ud) => Ok(f(ud)), + None => Err(Error::UserDataBorrowMutError), + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_mutex => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| { + let mut ud = ud.try_lock().map_err(|_| Error::UserDataBorrowMutError)?; + Ok(f(&mut ud)) + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_rwlock => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| { + let mut ud = ud.try_write().map_err(|_| Error::UserDataBorrowMutError)?; + Ok(f(&mut ud)) + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_pl_mutex => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| { + let mut ud = ud.try_lock().ok_or(Error::UserDataBorrowMutError)?; + Ok(f(&mut ud)) + })? + } + #[cfg(feature = "userdata-wrappers")] + Some(type_id) if type_id == type_hints.arc_pl_rwlock => { + let ud = get_userdata::>>>(state, idx); + (*ud).try_borrow_scoped_mut(|ud| { + let mut ud = ud.try_write().ok_or(Error::UserDataBorrowMutError)?; + Ok(f(&mut ud)) + })? + } + _ => Err(Error::UserDataTypeMismatch), + } +} + +// Populates the given table with the appropriate members to be a userdata metatable for the given +// type. This function takes the given table at the `metatable` index, and adds an appropriate +// `__gc` member to it for the given type and a `__metatable` entry to protect the table from script +// access. The function also, if given a `field_getters` or `methods` tables, will create an +// `__index` metamethod (capturing previous one) to lookup in `field_getters` first, then `methods` +// and falling back to the captured `__index` if no matches found. +// The same is also applicable for `__newindex` metamethod and `field_setters` table. +// Internally uses 9 stack spaces and does not call checkstack. +pub(crate) unsafe fn init_userdata_metatable( + state: *mut ffi::lua_State, + metatable: c_int, + field_getters: Option, + field_setters: Option, + methods: Option, +) -> Result<()> { + if field_getters.is_some() || methods.is_some() { + // Push `__index` generator function + init_userdata_metatable_index(state)?; + + let index_type = rawget_field(state, metatable, "__index")?; + match index_type { + ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => { + for &idx in &[field_getters, methods] { + if let Some(idx) = idx { + ffi::lua_pushvalue(state, idx); + } else { + ffi::lua_pushnil(state); + } + } + + // Generate `__index` + protect_lua!(state, 4, 1, fn(state) ffi::lua_call(state, 3, 1))?; + } + _ => mlua_panic!("improper `__index` type: {}", index_type), + } + + rawset_field(state, metatable, "__index")?; + } + + if let Some(field_setters) = field_setters { + // Push `__newindex` generator function + init_userdata_metatable_newindex(state)?; + + let newindex_type = rawget_field(state, metatable, "__newindex")?; + match newindex_type { + ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => { + ffi::lua_pushvalue(state, field_setters); + // Generate `__newindex` + protect_lua!(state, 3, 1, fn(state) ffi::lua_call(state, 2, 1))?; + } + _ => mlua_panic!("improper `__newindex` type: {}", newindex_type), + } + + rawset_field(state, metatable, "__newindex")?; + } + + ffi::lua_pushboolean(state, 0); + rawset_field(state, metatable, "__metatable")?; + + Ok(()) +} + +unsafe extern "C-unwind" fn lua_error_impl(state: *mut ffi::lua_State) -> c_int { + ffi::lua_error(state); +} + +unsafe extern "C-unwind" fn lua_isfunction_impl(state: *mut ffi::lua_State) -> c_int { + ffi::lua_pushboolean(state, ffi::lua_isfunction(state, -1)); + 1 +} + +unsafe extern "C-unwind" fn lua_istable_impl(state: *mut ffi::lua_State) -> c_int { + ffi::lua_pushboolean(state, ffi::lua_istable(state, -1)); + 1 +} + +unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()> { + let index_key = &USERDATA_METATABLE_INDEX as *const u8 as *const _; + if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, index_key) == ffi::LUA_TFUNCTION { + return Ok(()); + } + ffi::lua_pop(state, 1); + + // Create and cache `__index` generator + let code = cr#" + local error, isfunction, istable = ... + return function (__index, field_getters, methods) + -- Common case: has field getters and index is a table + if field_getters ~= nil and methods == nil and istable(__index) then + return function (self, key) + local field_getter = field_getters[key] + if field_getter ~= nil then + return field_getter(self) + end + return __index[key] + end + end + + return function (self, key) + if field_getters ~= nil then + local field_getter = field_getters[key] + if field_getter ~= nil then + return field_getter(self) + end + end + + if methods ~= nil then + local method = methods[key] + if method ~= nil then + return method + end + end + + if isfunction(__index) then + return __index(self, key) + elseif __index == nil then + error("attempt to get an unknown field '"..key.."'") + else + return __index[key] + end + end + end + "#; + protect_lua!(state, 0, 1, |state| { + let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_index")); + if ret != ffi::LUA_OK { + ffi::lua_error(state); + } + ffi::lua_pushcfunction(state, lua_error_impl); + ffi::lua_pushcfunction(state, lua_isfunction_impl); + ffi::lua_pushcfunction(state, lua_istable_impl); + ffi::lua_call(state, 3, 1); + + #[cfg(feature = "luau-jit")] + if ffi::luau_codegen_supported() != 0 { + ffi::luau_codegen_compile(state, -1); + } + + // Store in the registry + ffi::lua_pushvalue(state, -1); + ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, index_key); + }) +} + +unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result<()> { + let newindex_key = &USERDATA_METATABLE_NEWINDEX as *const u8 as *const _; + if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, newindex_key) == ffi::LUA_TFUNCTION { + return Ok(()); + } + ffi::lua_pop(state, 1); + + // Create and cache `__newindex` generator + let code = cr#" + local error, isfunction = ... + return function (__newindex, field_setters) + return function (self, key, value) + if field_setters ~= nil then + local field_setter = field_setters[key] + if field_setter ~= nil then + field_setter(self, value) + return + end + end + + if isfunction(__newindex) then + __newindex(self, key, value) + elseif __newindex == nil then + error("attempt to set an unknown field '"..key.."'") + else + __newindex[key] = value + end + end + end + "#; + protect_lua!(state, 0, 1, |state| { + let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_newindex")); + if ret != ffi::LUA_OK { + ffi::lua_error(state); + } + ffi::lua_pushcfunction(state, lua_error_impl); + ffi::lua_pushcfunction(state, lua_isfunction_impl); + ffi::lua_call(state, 2, 1); + + #[cfg(feature = "luau-jit")] + if ffi::luau_codegen_supported() != 0 { + ffi::luau_codegen_compile(state, -1); + } + + // Store in the registry + ffi::lua_pushvalue(state, -1); + ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, newindex_key); + }) +} + pub(super) unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::lua_State) -> c_int { let ud = get_userdata::>(state, -1); if (*ud).is_safe_to_destroy() { @@ -44,3 +433,6 @@ pub(super) unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::l } 1 } + +static USERDATA_METATABLE_INDEX: u8 = 0; +static USERDATA_METATABLE_NEWINDEX: u8 = 0; diff --git a/src/util/mod.rs b/src/util/mod.rs index d53f1b7..4366ecd 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -12,10 +12,8 @@ pub(crate) use error::{ pub(crate) use short_names::short_type_name; pub(crate) use types::TypeKey; pub(crate) use userdata::{ - borrow_userdata_scoped, borrow_userdata_scoped_mut, get_destructed_userdata_metatable, - get_internal_metatable, get_internal_userdata, get_userdata, init_internal_metatable, - init_userdata_metatable, push_internal_userdata, take_userdata, TypeIdHints, - DESTRUCTED_USERDATA_METATABLE, + get_destructed_userdata_metatable, get_internal_metatable, get_internal_userdata, get_userdata, + init_internal_metatable, push_internal_userdata, take_userdata, DESTRUCTED_USERDATA_METATABLE, }; #[cfg(not(feature = "luau"))] diff --git a/src/util/userdata.rs b/src/util/userdata.rs index 359dcf8..6108ddf 100644 --- a/src/util/userdata.rs +++ b/src/util/userdata.rs @@ -1,10 +1,8 @@ -use std::any::TypeId; use std::os::raw::{c_int, c_void}; -use std::{ptr, str}; +use std::ptr; -use crate::error::{Error, Result}; -use crate::userdata::UserDataStorage; -use crate::util::{check_stack, get_metatable_ptr, push_table, rawget_field, rawset_field, TypeKey}; +use crate::error::Result; +use crate::util::{check_stack, get_metatable_ptr, push_table, rawset_field, TypeKey}; // Pushes the userdata and attaches a metatable with __gc method. // Internally uses 3 stack spaces, does not call checkstack. @@ -37,6 +35,11 @@ pub(crate) unsafe fn init_internal_metatable( #[cfg(not(feature = "luau"))] { + unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::lua_State) -> c_int { + take_userdata::(state); + 0 + } + ffi::lua_pushcfunction(state, userdata_destructor::); rawset_field(state, -2, "__gc")?; } @@ -139,401 +142,4 @@ pub(crate) unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_Stat ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, key); } -// Populates the given table with the appropriate members to be a userdata metatable for the given -// type. This function takes the given table at the `metatable` index, and adds an appropriate -// `__gc` member to it for the given type and a `__metatable` entry to protect the table from script -// access. The function also, if given a `field_getters` or `methods` tables, will create an -// `__index` metamethod (capturing previous one) to lookup in `field_getters` first, then `methods` -// and falling back to the captured `__index` if no matches found. -// The same is also applicable for `__newindex` metamethod and `field_setters` table. -// Internally uses 9 stack spaces and does not call checkstack. -pub(crate) unsafe fn init_userdata_metatable( - state: *mut ffi::lua_State, - metatable: c_int, - field_getters: Option, - field_setters: Option, - methods: Option, -) -> Result<()> { - if field_getters.is_some() || methods.is_some() { - // Push `__index` generator function - init_userdata_metatable_index(state)?; - - let index_type = rawget_field(state, metatable, "__index")?; - match index_type { - ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => { - for &idx in &[field_getters, methods] { - if let Some(idx) = idx { - ffi::lua_pushvalue(state, idx); - } else { - ffi::lua_pushnil(state); - } - } - - // Generate `__index` - protect_lua!(state, 4, 1, fn(state) ffi::lua_call(state, 3, 1))?; - } - _ => mlua_panic!("improper `__index` type: {}", index_type), - } - - rawset_field(state, metatable, "__index")?; - } - - if let Some(field_setters) = field_setters { - // Push `__newindex` generator function - init_userdata_metatable_newindex(state)?; - - let newindex_type = rawget_field(state, metatable, "__newindex")?; - match newindex_type { - ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => { - ffi::lua_pushvalue(state, field_setters); - // Generate `__newindex` - protect_lua!(state, 3, 1, fn(state) ffi::lua_call(state, 2, 1))?; - } - _ => mlua_panic!("improper `__newindex` type: {}", newindex_type), - } - - rawset_field(state, metatable, "__newindex")?; - } - - ffi::lua_pushboolean(state, 0); - rawset_field(state, metatable, "__metatable")?; - - Ok(()) -} - -unsafe extern "C-unwind" fn lua_error_impl(state: *mut ffi::lua_State) -> c_int { - ffi::lua_error(state); -} - -unsafe extern "C-unwind" fn lua_isfunction_impl(state: *mut ffi::lua_State) -> c_int { - ffi::lua_pushboolean(state, ffi::lua_isfunction(state, -1)); - 1 -} - -unsafe extern "C-unwind" fn lua_istable_impl(state: *mut ffi::lua_State) -> c_int { - ffi::lua_pushboolean(state, ffi::lua_istable(state, -1)); - 1 -} - -unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()> { - let index_key = &USERDATA_METATABLE_INDEX as *const u8 as *const _; - if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, index_key) == ffi::LUA_TFUNCTION { - return Ok(()); - } - ffi::lua_pop(state, 1); - - // Create and cache `__index` generator - let code = cr#" - local error, isfunction, istable = ... - return function (__index, field_getters, methods) - -- Common case: has field getters and index is a table - if field_getters ~= nil and methods == nil and istable(__index) then - return function (self, key) - local field_getter = field_getters[key] - if field_getter ~= nil then - return field_getter(self) - end - return __index[key] - end - end - - return function (self, key) - if field_getters ~= nil then - local field_getter = field_getters[key] - if field_getter ~= nil then - return field_getter(self) - end - end - - if methods ~= nil then - local method = methods[key] - if method ~= nil then - return method - end - end - - if isfunction(__index) then - return __index(self, key) - elseif __index == nil then - error("attempt to get an unknown field '"..key.."'") - else - return __index[key] - end - end - end - "#; - protect_lua!(state, 0, 1, |state| { - let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_index")); - if ret != ffi::LUA_OK { - ffi::lua_error(state); - } - ffi::lua_pushcfunction(state, lua_error_impl); - ffi::lua_pushcfunction(state, lua_isfunction_impl); - ffi::lua_pushcfunction(state, lua_istable_impl); - ffi::lua_call(state, 3, 1); - - #[cfg(feature = "luau-jit")] - if ffi::luau_codegen_supported() != 0 { - ffi::luau_codegen_compile(state, -1); - } - - // Store in the registry - ffi::lua_pushvalue(state, -1); - ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, index_key); - }) -} - -unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result<()> { - let newindex_key = &USERDATA_METATABLE_NEWINDEX as *const u8 as *const _; - if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, newindex_key) == ffi::LUA_TFUNCTION { - return Ok(()); - } - ffi::lua_pop(state, 1); - - // Create and cache `__newindex` generator - let code = cr#" - local error, isfunction = ... - return function (__newindex, field_setters) - return function (self, key, value) - if field_setters ~= nil then - local field_setter = field_setters[key] - if field_setter ~= nil then - field_setter(self, value) - return - end - end - - if isfunction(__newindex) then - __newindex(self, key, value) - elseif __newindex == nil then - error("attempt to set an unknown field '"..key.."'") - else - __newindex[key] = value - end - end - end - "#; - protect_lua!(state, 0, 1, |state| { - let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_newindex")); - if ret != ffi::LUA_OK { - ffi::lua_error(state); - } - ffi::lua_pushcfunction(state, lua_error_impl); - ffi::lua_pushcfunction(state, lua_isfunction_impl); - ffi::lua_call(state, 2, 1); - - #[cfg(feature = "luau-jit")] - if ffi::luau_codegen_supported() != 0 { - ffi::luau_codegen_compile(state, -1); - } - - // Store in the registry - ffi::lua_pushvalue(state, -1); - ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, newindex_key); - }) -} - -#[cfg(not(feature = "luau"))] -unsafe extern "C-unwind" fn userdata_destructor(state: *mut ffi::lua_State) -> c_int { - // It's probably NOT a good idea to catch Rust panics in finalizer - // Lua 5.4 ignores it, other versions generates `LUA_ERRGCMM` without calling message handler - take_userdata::(state); - 0 -} - -// Userdata type hints, used to match types of wrapped userdata -#[derive(Clone, Copy)] -pub(crate) struct TypeIdHints { - t: TypeId, - - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - rc: TypeId, - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - rc_refcell: TypeId, - - #[cfg(feature = "userdata-wrappers")] - arc: TypeId, - #[cfg(feature = "userdata-wrappers")] - arc_mutex: TypeId, - #[cfg(feature = "userdata-wrappers")] - arc_rwlock: TypeId, - #[cfg(feature = "userdata-wrappers")] - arc_pl_mutex: TypeId, - #[cfg(feature = "userdata-wrappers")] - arc_pl_rwlock: TypeId, -} - -impl TypeIdHints { - pub(crate) fn new() -> Self { - Self { - t: TypeId::of::(), - - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - rc: TypeId::of::>(), - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - rc_refcell: TypeId::of::>>(), - - #[cfg(feature = "userdata-wrappers")] - arc: TypeId::of::>(), - #[cfg(feature = "userdata-wrappers")] - arc_mutex: TypeId::of::>>(), - #[cfg(feature = "userdata-wrappers")] - arc_rwlock: TypeId::of::>>(), - #[cfg(feature = "userdata-wrappers")] - arc_pl_mutex: TypeId::of::>>(), - #[cfg(feature = "userdata-wrappers")] - arc_pl_rwlock: TypeId::of::>>(), - } - } - - #[inline(always)] - pub(crate) fn type_id(&self) -> TypeId { - self.t - } -} - -pub(crate) unsafe fn borrow_userdata_scoped( - state: *mut ffi::lua_State, - idx: c_int, - type_id: Option, - type_hints: TypeIdHints, - f: impl FnOnce(&T) -> R, -) -> Result { - match type_id { - Some(type_id) if type_id == type_hints.t => { - let ud = get_userdata::>(state, idx); - (*ud).try_borrow_scoped(|ud| f(ud)) - } - - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - Some(type_id) if type_id == type_hints.rc => { - let ud = get_userdata::>>(state, idx); - (*ud).try_borrow_scoped(|ud| f(ud)) - } - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - Some(type_id) if type_id == type_hints.rc_refcell => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped(|ud| { - let ud = ud.try_borrow().map_err(|_| Error::UserDataBorrowError)?; - Ok(f(&ud)) - })? - } - - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc => { - let ud = get_userdata::>>(state, idx); - (*ud).try_borrow_scoped(|ud| f(ud)) - } - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc_mutex => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped(|ud| { - let ud = ud.try_lock().map_err(|_| Error::UserDataBorrowError)?; - Ok(f(&ud)) - })? - } - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc_rwlock => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped(|ud| { - let ud = ud.try_read().map_err(|_| Error::UserDataBorrowError)?; - Ok(f(&ud)) - })? - } - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc_pl_mutex => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped(|ud| { - let ud = ud.try_lock().ok_or(Error::UserDataBorrowError)?; - Ok(f(&ud)) - })? - } - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc_pl_rwlock => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped(|ud| { - let ud = ud.try_read().ok_or(Error::UserDataBorrowError)?; - Ok(f(&ud)) - })? - } - _ => Err(Error::UserDataTypeMismatch), - } -} - -pub(crate) unsafe fn borrow_userdata_scoped_mut( - state: *mut ffi::lua_State, - idx: c_int, - type_id: Option, - type_hints: TypeIdHints, - f: impl FnOnce(&mut T) -> R, -) -> Result { - match type_id { - Some(type_id) if type_id == type_hints.t => { - let ud = get_userdata::>(state, idx); - (*ud).try_borrow_scoped_mut(|ud| f(ud)) - } - - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - Some(type_id) if type_id == type_hints.rc => { - let ud = get_userdata::>>(state, idx); - (*ud).try_borrow_scoped_mut(|ud| match std::rc::Rc::get_mut(ud) { - Some(ud) => Ok(f(ud)), - None => Err(Error::UserDataBorrowMutError), - })? - } - #[cfg(all(feature = "userdata-wrappers", not(feature = "send")))] - Some(type_id) if type_id == type_hints.rc_refcell => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped(|ud| { - let mut ud = ud.try_borrow_mut().map_err(|_| Error::UserDataBorrowMutError)?; - Ok(f(&mut ud)) - })? - } - - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc => { - let ud = get_userdata::>>(state, idx); - (*ud).try_borrow_scoped_mut(|ud| match std::sync::Arc::get_mut(ud) { - Some(ud) => Ok(f(ud)), - None => Err(Error::UserDataBorrowMutError), - })? - } - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc_mutex => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped_mut(|ud| { - let mut ud = ud.try_lock().map_err(|_| Error::UserDataBorrowMutError)?; - Ok(f(&mut ud)) - })? - } - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc_rwlock => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped_mut(|ud| { - let mut ud = ud.try_write().map_err(|_| Error::UserDataBorrowMutError)?; - Ok(f(&mut ud)) - })? - } - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc_pl_mutex => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped_mut(|ud| { - let mut ud = ud.try_lock().ok_or(Error::UserDataBorrowMutError)?; - Ok(f(&mut ud)) - })? - } - #[cfg(feature = "userdata-wrappers")] - Some(type_id) if type_id == type_hints.arc_pl_rwlock => { - let ud = get_userdata::>>>(state, idx); - (*ud).try_borrow_scoped_mut(|ud| { - let mut ud = ud.try_write().ok_or(Error::UserDataBorrowMutError)?; - Ok(f(&mut ud)) - })? - } - _ => Err(Error::UserDataTypeMismatch), - } -} - pub(crate) static DESTRUCTED_USERDATA_METATABLE: u8 = 0; -static USERDATA_METATABLE_INDEX: u8 = 0; -static USERDATA_METATABLE_NEWINDEX: u8 = 0;