Allow registering and creating custom userdata types that don't necessary implement the `UserData` trait.

This is useful to register 3rd party types that cannot implement `UserData` due to Rust orphan rules.
See #206
This commit is contained in:
Alex Orlenko 2023-02-03 23:46:04 +00:00
parent 8339621f9c
commit 47c8300ccf
No known key found for this signature in database
GPG Key ID: 4C150C250863B96D
6 changed files with 322 additions and 258 deletions

View File

@ -30,7 +30,7 @@ use crate::types::{
Number, RegistryKey,
};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataCell};
use crate::userdata_impl::{StaticUserDataFields, StaticUserDataMethods, UserDataProxy};
use crate::userdata_impl::{UserDataProxy, UserDataRegistrar};
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,
@ -1732,18 +1732,18 @@ impl Lua {
false
}
/// Create a Lua userdata object from a custom userdata type.
/// Creates a Lua userdata object from a custom userdata type.
///
/// All userdata instances of type `T` shares the same metatable.
/// All userdata instances of the same type `T` shares the same metatable.
#[inline]
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: 'static + MaybeSend + UserData,
T: UserData + MaybeSend + 'static,
{
unsafe { self.make_userdata(UserDataCell::new(data)) }
}
/// Create a Lua userdata object from a custom serializable userdata type.
/// Creates a Lua userdata object from a custom serializable userdata type.
///
/// Requires `feature = "serialize"`
#[cfg(feature = "serialize")]
@ -1751,11 +1751,60 @@ impl Lua {
#[inline]
pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: 'static + MaybeSend + UserData + Serialize,
T: UserData + Serialize + MaybeSend + 'static,
{
unsafe { self.make_userdata(UserDataCell::new_ser(data)) }
}
/// Creates a Lua userdata object from a custom Rust type.
///
/// You can register the type using [`Lua::register_userdata_type()`] to add fields or methods
/// _before_ calling this method.
/// Otherwise, the userdata object will have an empty metatable.
///
/// All userdata instances of the same type `T` shares the same metatable.
pub fn create_any_userdata<T>(&self, data: T) -> Result<AnyUserData>
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::<T>();
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::<T>(registry)
})
}
}
/// Registers a custom Rust type in Lua to use in userdata objects.
///
/// This methods provides a way to add fields or methods to userdata objects of a type `T`.
pub fn register_userdata_type<T: 'static>(
&self,
f: impl FnOnce(&mut UserDataRegistrar<T>),
) -> Result<()> {
let mut registry = UserDataRegistrar::new();
f(&mut registry);
unsafe {
// Deregister the type if it already registered
let type_id = TypeId::of::<T>();
if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) {
ffi::luaL_unref(self.state(), ffi::LUA_REGISTRYINDEX, table_id);
}
// Register the type
self.register_userdata_metatable(registry)?;
Ok(())
}
}
/// Create a Lua userdata "proxy" object from a custom userdata type.
///
/// Proxy object is an empty userdata object that has `T` metatable attached.
@ -2490,38 +2539,29 @@ impl Lua {
LuaRef::new(self, index)
}
unsafe fn push_userdata_metatable<T: UserData + 'static>(&self) -> Result<()> {
unsafe fn register_userdata_metatable<'lua, T: 'static>(
&'lua self,
registry: UserDataRegistrar<'lua, T>,
) -> Result<Integer> {
let state = self.state();
let type_id = TypeId::of::<T>();
if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) {
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, table_id as Integer);
return Ok(());
}
let _sg = StackGuard::new_extra(state, 1);
let _sg = StackGuard::new(state);
check_stack(state, 13)?;
let mut fields = StaticUserDataFields::default();
let mut methods = StaticUserDataMethods::default();
T::add_fields(&mut fields);
T::add_methods(&mut methods);
// Prepare metatable, add meta methods first and then meta fields
let metatable_nrec = methods.meta_methods.len() + fields.meta_fields.len();
let metatable_nrec = registry.meta_methods.len() + registry.meta_fields.len();
#[cfg(feature = "async")]
let metatable_nrec = metatable_nrec + methods.async_meta_methods.len();
let metatable_nrec = metatable_nrec + registry.async_meta_methods.len();
push_table(state, 0, metatable_nrec as c_int, true)?;
for (k, m) in methods.meta_methods {
for (k, m) in registry.meta_methods {
self.push_value(Value::Function(self.create_callback(m)?))?;
rawset_field(state, -2, MetaMethod::validate(&k)?)?;
}
#[cfg(feature = "async")]
for (k, m) in methods.async_meta_methods {
for (k, m) in registry.async_meta_methods {
self.push_value(Value::Function(self.create_async_callback(m)?))?;
rawset_field(state, -2, MetaMethod::validate(&k)?)?;
}
for (k, f) in fields.meta_fields {
for (k, f) in registry.meta_fields {
self.push_value(f(self)?)?;
rawset_field(state, -2, MetaMethod::validate(&k)?)?;
}
@ -2530,10 +2570,10 @@ impl Lua {
let mut extra_tables_count = 0;
let mut field_getters_index = None;
let field_getters_nrec = fields.field_getters.len();
let field_getters_nrec = registry.field_getters.len();
if field_getters_nrec > 0 {
push_table(state, 0, field_getters_nrec as c_int, true)?;
for (k, m) in fields.field_getters {
for (k, m) in registry.field_getters {
self.push_value(Value::Function(self.create_callback(m)?))?;
rawset_field(state, -2, &k)?;
}
@ -2542,10 +2582,10 @@ impl Lua {
}
let mut field_setters_index = None;
let field_setters_nrec = fields.field_setters.len();
let field_setters_nrec = registry.field_setters.len();
if field_setters_nrec > 0 {
push_table(state, 0, field_setters_nrec as c_int, true)?;
for (k, m) in fields.field_setters {
for (k, m) in registry.field_setters {
self.push_value(Value::Function(self.create_callback(m)?))?;
rawset_field(state, -2, &k)?;
}
@ -2554,17 +2594,17 @@ impl Lua {
}
let mut methods_index = None;
let methods_nrec = methods.methods.len();
let methods_nrec = registry.methods.len();
#[cfg(feature = "async")]
let methods_nrec = methods_nrec + methods.async_methods.len();
let methods_nrec = methods_nrec + registry.async_methods.len();
if methods_nrec > 0 {
push_table(state, 0, methods_nrec as c_int, true)?;
for (k, m) in methods.methods {
for (k, m) in registry.methods {
self.push_value(Value::Function(self.create_callback(m)?))?;
rawset_field(state, -2, &k)?;
}
#[cfg(feature = "async")]
for (k, m) in methods.async_methods {
for (k, m) in registry.async_methods {
self.push_value(Value::Function(self.create_async_callback(m)?))?;
rawset_field(state, -2, &k)?;
}
@ -2584,21 +2624,21 @@ impl Lua {
ffi::lua_pop(state, extra_tables_count);
let mt_ptr = ffi::lua_topointer(state, -1);
ffi::lua_pushvalue(state, -1);
let id = protect_lua!(state, 1, 0, |state| {
ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX)
})?;
let type_id = TypeId::of::<T>();
(*self.extra.get()).registered_userdata.insert(type_id, id);
(*self.extra.get())
.registered_userdata_mt
.insert(mt_ptr, Some(type_id));
Ok(())
Ok(id as Integer)
}
#[inline]
pub(crate) unsafe fn register_userdata_metatable(
pub(crate) unsafe fn register_raw_userdata_metatable(
&self,
ptr: *const c_void,
type_id: Option<TypeId>,
@ -2609,7 +2649,7 @@ impl Lua {
}
#[inline]
pub(crate) unsafe fn deregister_userdata_metatable(&self, ptr: *const c_void) {
pub(crate) unsafe fn deregister_raw_userdata_metatable(&self, ptr: *const c_void) {
(*self.extra.get()).registered_userdata_mt.remove(&ptr);
}
@ -2905,13 +2945,34 @@ impl Lua {
where
T: UserData + 'static,
{
self.make_userdata_with_metatable(data, || {
// Check if userdata/metatable is already registered
let type_id = TypeId::of::<T>();
if let Some(&table_id) = (*self.extra.get()).registered_userdata.get(&type_id) {
return Ok(table_id as Integer);
}
// Create new metatable from UserData definition
let mut registry = UserDataRegistrar::new();
T::add_fields(&mut registry);
T::add_methods(&mut registry);
self.register_userdata_metatable(registry)
})
}
unsafe fn make_userdata_with_metatable<T>(
&self,
data: UserDataCell<T>,
get_metatable_id: impl FnOnce() -> Result<Integer>,
) -> Result<AnyUserData> {
let state = self.state();
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
// We push metatable first to ensure having correct metatable with `__gc` method
ffi::lua_pushnil(state);
self.push_userdata_metatable::<T>()?;
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, get_metatable_id()?);
let protect = !self.unlikely_memory_error();
#[cfg(not(feature = "lua54"))]
push_userdata(state, data, protect)?;

View File

@ -435,7 +435,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
std::ptr::write(ud_ptr as _, UserDataCell::new(data));
ffi::lua_setmetatable(state, -2);
let ud = AnyUserData(lua.pop_ref());
lua.register_userdata_metatable(mt_ptr, None);
lua.register_raw_userdata_metatable(mt_ptr, None);
#[cfg(any(feature = "lua51", feature = "luajit"))]
let newtable = lua.create_table()?;
@ -453,7 +453,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
ffi::lua_getmetatable(state, -1);
let mt_ptr = ffi::lua_topointer(state, -1);
ffi::lua_pop(state, 1);
ud.lua.deregister_userdata_metatable(mt_ptr);
ud.lua.deregister_raw_userdata_metatable(mt_ptr);
// Clear associated user values
#[cfg(feature = "lua54")]

View File

@ -234,7 +234,7 @@ impl AsRef<str> for MetaMethod {
/// Method registry for [`UserData`] implementors.
///
/// [`UserData`]: crate::UserData
pub trait UserDataMethods<'lua, T: UserData> {
pub trait UserDataMethods<'lua, T> {
/// Add a regular method which accepts a `&T` as the first parameter.
///
/// Regular methods are implemented by overriding the `__index` metamethod and returning the
@ -427,7 +427,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
/// Field registry for [`UserData`] implementors.
///
/// [`UserData`]: crate::UserData
pub trait UserDataFields<'lua, T: UserData> {
pub trait UserDataFields<'lua, T> {
/// Add a regular field getter as a method which accepts a `&T` as the parameter.
///
/// Regular field getters are implemented by overriding the `__index` metamethod and returning the
@ -503,8 +503,8 @@ pub trait UserDataFields<'lua, T: UserData> {
/// Trait for custom userdata types.
///
/// By implementing this trait, a struct becomes eligible for use inside Lua code.
/// Implementation of [`IntoLua`] is automatically provided, [`FromLua`] is implemented
/// only for `T: UserData + Clone`.
/// Implementation of [`IntoLua`] is automatically provided, [`FromLua`] needs to be implemented
/// manually.
///
///
/// # Examples
@ -725,7 +725,7 @@ impl OwnedAnyUserData {
impl<'lua> AnyUserData<'lua> {
/// Checks whether the type of this userdata is `T`.
pub fn is<T: UserData + 'static>(&self) -> bool {
pub fn is<T: 'static>(&self) -> bool {
match self.inspect(|_: &UserDataCell<T>| Ok(())) {
Ok(()) => true,
Err(Error::UserDataTypeMismatch) => false,
@ -740,7 +740,7 @@ impl<'lua> AnyUserData<'lua> {
/// Returns a `UserDataBorrowError` if the userdata is already mutably borrowed. Returns a
/// `UserDataTypeMismatch` if the userdata is not of type `T`.
#[inline]
pub fn borrow<T: UserData + 'static>(&self) -> Result<Ref<T>> {
pub fn borrow<T: 'static>(&self) -> Result<Ref<T>> {
self.inspect(|cell| cell.try_borrow())
}
@ -751,7 +751,7 @@ impl<'lua> AnyUserData<'lua> {
/// Returns a `UserDataBorrowMutError` if the userdata cannot be mutably borrowed.
/// Returns a `UserDataTypeMismatch` if the userdata is not of type `T`.
#[inline]
pub fn borrow_mut<T: UserData + 'static>(&self) -> Result<RefMut<T>> {
pub fn borrow_mut<T: 'static>(&self) -> Result<RefMut<T>> {
self.inspect(|cell| cell.try_borrow_mut())
}
@ -759,7 +759,7 @@ impl<'lua> AnyUserData<'lua> {
/// Sets the special "destructed" metatable that prevents any further operations with this userdata.
///
/// Keeps associated user values unchanged (they will be collected by Lua's GC).
pub fn take<T: UserData + 'static>(&self) -> Result<T> {
pub fn take<T: 'static>(&self) -> Result<T> {
let lua = self.0.lua;
let state = lua.state();
unsafe {
@ -980,7 +980,7 @@ impl<'lua> AnyUserData<'lua> {
/// Returned [`UserDataMetatable`] object wraps the original metatable and
/// provides safe access to its methods.
///
/// For `T: UserData + 'static` returned metatable is shared among all instances of type `T`.
/// For `T: 'static` returned metatable is shared among all instances of type `T`.
///
/// [`UserDataMetatable`]: crate::UserDataMetatable
#[inline]
@ -1052,7 +1052,7 @@ impl<'lua> AnyUserData<'lua> {
fn inspect<'a, T, F, R>(&'a self, func: F) -> Result<R>
where
T: UserData + 'static,
T: 'static,
F: FnOnce(&'a UserDataCell<T>) -> Result<R>,
{
let lua = self.0.lua;

View File

@ -23,19 +23,33 @@ use {
std::future::Future,
};
pub(crate) struct StaticUserDataMethods<'lua, T: UserData + 'static> {
pub struct UserDataRegistrar<'lua, T: 'static> {
// Fields
pub(crate) field_getters: Vec<(String, Callback<'lua, 'static>)>,
pub(crate) field_setters: Vec<(String, Callback<'lua, 'static>)>,
#[allow(clippy::type_complexity)]
pub(crate) meta_fields: Vec<(
String,
Box<dyn Fn(&'lua Lua) -> Result<Value<'lua>> + 'static>,
)>,
// Methods
pub(crate) methods: Vec<(String, Callback<'lua, 'static>)>,
#[cfg(feature = "async")]
pub(crate) async_methods: Vec<(String, AsyncCallback<'lua, 'static>)>,
pub(crate) meta_methods: Vec<(String, Callback<'lua, 'static>)>,
#[cfg(feature = "async")]
pub(crate) async_meta_methods: Vec<(String, AsyncCallback<'lua, 'static>)>,
_type: PhantomData<T>,
}
impl<'lua, T: UserData + 'static> Default for StaticUserDataMethods<'lua, T> {
fn default() -> StaticUserDataMethods<'lua, T> {
StaticUserDataMethods {
impl<'lua, T: 'static> UserDataRegistrar<'lua, T> {
pub(crate) const fn new() -> Self {
UserDataRegistrar {
field_getters: Vec::new(),
field_setters: Vec::new(),
meta_fields: Vec::new(),
methods: Vec::new(),
#[cfg(feature = "async")]
async_methods: Vec::new(),
@ -45,161 +59,7 @@ impl<'lua, T: UserData + 'static> Default for StaticUserDataMethods<'lua, T> {
_type: PhantomData,
}
}
}
impl<'lua, T: UserData + 'static> UserDataMethods<'lua, T> for StaticUserDataMethods<'lua, T> {
fn add_method<M, A, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: Fn(&'lua Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
self.methods
.push((name.as_ref().into(), Self::box_method(method)));
}
fn add_method_mut<M, A, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: FnMut(&'lua Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
self.methods
.push((name.as_ref().into(), Self::box_method_mut(method)));
}
#[cfg(feature = "async")]
fn add_async_method<M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
where
T: Clone,
M: Fn(&'lua Lua, T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 'lua,
R: IntoLuaMulti<'lua>,
{
self.async_methods
.push((name.as_ref().into(), Self::box_async_method(method)));
}
fn add_function<F, A, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: Fn(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
self.methods
.push((name.as_ref().into(), Self::box_function(function)));
}
fn add_function_mut<F, A, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: FnMut(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
self.methods
.push((name.as_ref().into(), Self::box_function_mut(function)));
}
#[cfg(feature = "async")]
fn add_async_function<F, A, FR, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
FR: Future<Output = Result<R>> + 'lua,
R: IntoLuaMulti<'lua>,
{
self.async_methods
.push((name.as_ref().into(), Self::box_async_function(function)));
}
fn add_meta_method<M, A, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: Fn(&'lua Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
self.meta_methods
.push((name.as_ref().into(), Self::box_method(method)));
}
fn add_meta_method_mut<M, A, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: FnMut(&'lua Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
self.meta_methods
.push((name.as_ref().into(), Self::box_method_mut(method)));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
where
T: Clone,
M: Fn(&'lua Lua, T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 'lua,
R: IntoLuaMulti<'lua>,
{
self.async_meta_methods
.push((name.as_ref().into(), Self::box_async_method(method)));
}
fn add_meta_function<F, A, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: Fn(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
self.meta_methods
.push((name.as_ref().into(), Self::box_function(function)));
}
fn add_meta_function_mut<F, A, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: FnMut(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
self.meta_methods
.push((name.as_ref().into(), Self::box_function_mut(function)));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
FR: Future<Output = Result<R>> + 'lua,
R: IntoLuaMulti<'lua>,
{
self.async_meta_methods
.push((name.as_ref().into(), Self::box_async_function(function)));
}
// Below are internal methods used in generated code
fn add_callback(&mut self, name: String, callback: Callback<'lua, 'static>) {
self.methods.push((name, callback));
}
#[cfg(feature = "async")]
fn add_async_callback(&mut self, name: String, callback: AsyncCallback<'lua, 'static>) {
self.async_methods.push((name, callback));
}
fn add_meta_callback(&mut self, name: String, callback: Callback<'lua, 'static>) {
self.meta_methods.push((name, callback));
}
#[cfg(feature = "async")]
fn add_async_meta_callback(&mut self, meta: String, callback: AsyncCallback<'lua, 'static>) {
self.async_meta_methods.push((meta, callback))
}
}
impl<'lua, T: UserData + 'static> StaticUserDataMethods<'lua, T> {
fn box_method<M, A, R>(method: M) -> Callback<'lua, 'static>
where
M: Fn(&'lua Lua, &T, A) -> Result<R> + MaybeSend + 'static,
@ -446,35 +306,13 @@ impl<'lua, T: UserData + 'static> StaticUserDataMethods<'lua, T> {
}
}
pub(crate) struct StaticUserDataFields<'lua, T: UserData + 'static> {
pub(crate) field_getters: Vec<(String, Callback<'lua, 'static>)>,
pub(crate) field_setters: Vec<(String, Callback<'lua, 'static>)>,
#[allow(clippy::type_complexity)]
pub(crate) meta_fields: Vec<(
String,
Box<dyn Fn(&'lua Lua) -> Result<Value<'lua>> + 'static>,
)>,
_type: PhantomData<T>,
}
impl<'lua, T: UserData + 'static> Default for StaticUserDataFields<'lua, T> {
fn default() -> StaticUserDataFields<'lua, T> {
StaticUserDataFields {
field_getters: Vec::new(),
field_setters: Vec::new(),
meta_fields: Vec::new(),
_type: PhantomData,
}
}
}
impl<'lua, T: UserData + 'static> UserDataFields<'lua, T> for StaticUserDataFields<'lua, T> {
impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistrar<'lua, T> {
fn add_field_method_get<M, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: Fn(&'lua Lua, &T) -> Result<R> + MaybeSend + 'static,
R: IntoLua<'lua>,
{
let method = StaticUserDataMethods::box_method(move |lua, data, ()| method(lua, data));
let method = Self::box_method(move |lua, data, ()| method(lua, data));
self.field_getters.push((name.as_ref().into(), method));
}
@ -483,7 +321,7 @@ impl<'lua, T: UserData + 'static> UserDataFields<'lua, T> for StaticUserDataFiel
M: FnMut(&'lua Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
A: FromLua<'lua>,
{
let method = StaticUserDataMethods::box_method_mut(method);
let method = Self::box_method_mut(method);
self.field_setters.push((name.as_ref().into(), method));
}
@ -492,7 +330,7 @@ impl<'lua, T: UserData + 'static> UserDataFields<'lua, T> for StaticUserDataFiel
F: Fn(&'lua Lua, AnyUserData<'lua>) -> Result<R> + MaybeSend + 'static,
R: IntoLua<'lua>,
{
let func = StaticUserDataMethods::<T>::box_function(function);
let func = Self::box_function(function);
self.field_getters.push((name.as_ref().into(), func));
}
@ -501,9 +339,7 @@ impl<'lua, T: UserData + 'static> UserDataFields<'lua, T> for StaticUserDataFiel
F: FnMut(&'lua Lua, AnyUserData<'lua>, A) -> Result<()> + MaybeSend + 'static,
A: FromLua<'lua>,
{
let func = StaticUserDataMethods::<T>::box_function_mut(move |lua, (data, val)| {
function(lua, data, val)
});
let func = Self::box_function_mut(move |lua, (data, val)| function(lua, data, val));
self.field_setters.push((name.as_ref().into(), func));
}
@ -545,6 +381,158 @@ impl<'lua, T: UserData + 'static> UserDataFields<'lua, T> for StaticUserDataFiel
}
}
impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistrar<'lua, T> {
fn add_method<M, A, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: Fn(&'lua Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
self.methods
.push((name.as_ref().into(), Self::box_method(method)));
}
fn add_method_mut<M, A, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: FnMut(&'lua Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
self.methods
.push((name.as_ref().into(), Self::box_method_mut(method)));
}
#[cfg(feature = "async")]
fn add_async_method<M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
where
T: Clone,
M: Fn(&'lua Lua, T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 'lua,
R: IntoLuaMulti<'lua>,
{
self.async_methods
.push((name.as_ref().into(), Self::box_async_method(method)));
}
fn add_function<F, A, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: Fn(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
self.methods
.push((name.as_ref().into(), Self::box_function(function)));
}
fn add_function_mut<F, A, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: FnMut(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
self.methods
.push((name.as_ref().into(), Self::box_function_mut(function)));
}
#[cfg(feature = "async")]
fn add_async_function<F, A, FR, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
FR: Future<Output = Result<R>> + 'lua,
R: IntoLuaMulti<'lua>,
{
self.async_methods
.push((name.as_ref().into(), Self::box_async_function(function)));
}
fn add_meta_method<M, A, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: Fn(&'lua Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
self.meta_methods
.push((name.as_ref().into(), Self::box_method(method)));
}
fn add_meta_method_mut<M, A, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: FnMut(&'lua Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
self.meta_methods
.push((name.as_ref().into(), Self::box_method_mut(method)));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
where
T: Clone,
M: Fn(&'lua Lua, T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 'lua,
R: IntoLuaMulti<'lua>,
{
self.async_meta_methods
.push((name.as_ref().into(), Self::box_async_method(method)));
}
fn add_meta_function<F, A, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: Fn(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
self.meta_methods
.push((name.as_ref().into(), Self::box_function(function)));
}
fn add_meta_function_mut<F, A, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: FnMut(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
self.meta_methods
.push((name.as_ref().into(), Self::box_function_mut(function)));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
FR: Future<Output = Result<R>> + 'lua,
R: IntoLuaMulti<'lua>,
{
self.async_meta_methods
.push((name.as_ref().into(), Self::box_async_function(function)));
}
// Below are internal methods used in generated code
fn add_callback(&mut self, name: String, callback: Callback<'lua, 'static>) {
self.methods.push((name, callback));
}
#[cfg(feature = "async")]
fn add_async_callback(&mut self, name: String, callback: AsyncCallback<'lua, 'static>) {
self.async_methods.push((name, callback));
}
fn add_meta_callback(&mut self, name: String, callback: Callback<'lua, 'static>) {
self.meta_methods.push((name, callback));
}
#[cfg(feature = "async")]
fn add_async_meta_callback(&mut self, meta: String, callback: AsyncCallback<'lua, 'static>) {
self.async_meta_methods.push((meta, callback))
}
}
#[inline]
unsafe fn get_userdata_ref<'a, T>(state: *mut ffi::lua_State) -> Result<Ref<'a, T>> {
(*get_userdata::<UserDataCell<T>>(state, -1)).try_borrow()
@ -559,7 +547,7 @@ macro_rules! lua_userdata_impl {
($type:ty) => {
impl<T: UserData + 'static> UserData for $type {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
let mut orig_fields = StaticUserDataFields::default();
let mut orig_fields = UserDataRegistrar::new();
T::add_fields(&mut orig_fields);
for (name, callback) in orig_fields.field_getters {
fields.add_field_getter(name, callback);
@ -570,7 +558,7 @@ macro_rules! lua_userdata_impl {
}
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
let mut orig_methods = StaticUserDataMethods::default();
let mut orig_methods = UserDataRegistrar::new();
T::add_methods(&mut orig_methods);
for (name, callback) in orig_methods.methods {
methods.add_callback(name, callback);

View File

@ -47,7 +47,6 @@ pub unsafe fn check_stack(state: *mut ffi::lua_State, amount: c_int) -> Result<(
pub struct StackGuard {
state: *mut ffi::lua_State,
top: c_int,
extra: c_int,
}
impl StackGuard {
@ -59,17 +58,6 @@ impl StackGuard {
StackGuard {
state,
top: ffi::lua_gettop(state),
extra: 0,
}
}
// Similar to `new`, but checks and keeps `extra` elements from top of the stack on Drop.
#[inline]
pub unsafe fn new_extra(state: *mut ffi::lua_State, extra: c_int) -> StackGuard {
StackGuard {
state,
top: ffi::lua_gettop(state),
extra,
}
}
}
@ -78,14 +66,11 @@ impl Drop for StackGuard {
fn drop(&mut self) {
unsafe {
let top = ffi::lua_gettop(self.state);
if top < self.top + self.extra {
if top < self.top {
mlua_panic!("{} too many stack values popped", self.top - top)
}
if top > self.top + self.extra {
if self.extra > 0 {
ffi::lua_rotate(self.state, self.top + 1, self.extra);
}
ffi::lua_settop(self.state, self.top + self.extra);
if top > self.top {
ffi::lua_settop(self.state, self.top);
}
}
}

View File

@ -1,3 +1,4 @@
use std::string::String as StdString;
use std::sync::Arc;
#[cfg(not(feature = "parking_lot"))]
use std::sync::{Mutex, RwLock};
@ -17,7 +18,7 @@ use mlua::{
};
#[test]
fn test_user_data() -> Result<()> {
fn test_userdata() -> Result<()> {
struct UserData1(i64);
struct UserData2(Box<i64>);
@ -710,3 +711,32 @@ fn test_userdata_proxy() -> Result<()> {
)
.exec()
}
#[test]
fn test_any_userdata() -> Result<()> {
let lua = Lua::new();
lua.register_userdata_type::<StdString>(|reg| {
reg.add_method("get", |_, this, ()| Ok(this.clone()));
reg.add_method_mut("concat", |_, this, s: String| {
this.push_str(&s.to_string_lossy());
Ok(())
});
})?;
let ud = lua.create_any_userdata("hello".to_string())?;
assert_eq!(&*ud.borrow::<StdString>()?, "hello");
lua.globals().set("ud", ud)?;
lua.load(
r#"
assert(ud:get() == "hello")
ud:concat(", world")
assert(ud:get() == "hello, world")
"#,
)
.exec()
.unwrap();
Ok(())
}