Add Lua::create_proxy for easy access to UserData static fields and functions

Closes #178
This commit is contained in:
Alex Orlenko 2022-06-28 18:28:40 +01:00
parent 9af1aaf889
commit e7f494530f
No known key found for this signature in database
GPG key ID: 4C150C250863B96D
3 changed files with 89 additions and 1 deletions

View file

@ -29,7 +29,7 @@ use crate::types::{
Number, RegistryKey,
};
use crate::userdata::{AnyUserData, UserData, UserDataCell};
use crate::userdata_impl::{StaticUserDataFields, StaticUserDataMethods};
use crate::userdata_impl::{StaticUserDataFields, StaticUserDataMethods, UserDataProxy};
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,
@ -1721,6 +1721,7 @@ impl Lua {
}
/// Create a Lua userdata object from a custom userdata type.
#[inline]
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: 'static + MaybeSend + UserData,
@ -1733,6 +1734,7 @@ impl Lua {
/// Requires `feature = "serialize"`
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[inline]
pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: 'static + MaybeSend + UserData + Serialize,
@ -1740,6 +1742,46 @@ impl Lua {
unsafe { self.make_userdata(UserDataCell::new_ser(data)) }
}
/// Create a Lua userdata "proxy" object from a custom userdata type.
///
/// Proxy object is an empty userdata object that has `T` metatable attached.
/// The main purpose of this object is to provide access to static fields and functions
/// without creating an instance of type `T`.
///
/// You can get or set uservalues on this object but you cannot borrow any Rust type.
///
/// # Examples
///
/// ```
/// # use mlua::{Lua, Result, UserData, UserDataFields, UserDataMethods};
/// # fn main() -> Result<()> {
/// # let lua = Lua::new();
/// struct MyUserData(i32);
///
/// impl UserData for MyUserData {
/// fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
/// fields.add_field_method_get("val", |_, this| Ok(this.0));
/// }
///
/// fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
/// methods.add_function("new", |_, value: i32| Ok(MyUserData(value)));
/// }
/// }
///
/// lua.globals().set("MyUserData", lua.create_proxy::<MyUserData>()?)?;
///
/// lua.load("assert(MyUserData.new(321).val == 321)").exec()?;
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn create_proxy<T>(&self) -> Result<AnyUserData>
where
T: 'static + UserData,
{
unsafe { self.make_userdata(UserDataCell::new(UserDataProxy::<T>(PhantomData))) }
}
/// Returns a handle to the global environment.
pub fn globals(&self) -> Table {
unsafe {

View file

@ -622,3 +622,8 @@ lua_userdata_impl!(Arc<RwLock<T>>);
lua_userdata_impl!(Arc<parking_lot::Mutex<T>>);
#[cfg(feature = "parking_lot")]
lua_userdata_impl!(Arc<parking_lot::RwLock<T>>);
// A special proxy object for UserData
pub(crate) struct UserDataProxy<T>(pub(crate) PhantomData<T>);
lua_userdata_impl!(UserDataProxy<T>);

View file

@ -656,3 +656,44 @@ fn test_userdata_wrapped() -> Result<()> {
Ok(())
}
#[test]
fn test_userdata_proxy() -> Result<()> {
struct MyUserData(i64);
impl UserData for MyUserData {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_function_get("static_field", |_, _| Ok(123));
fields.add_field_method_get("n", |_, this| Ok(this.0));
}
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_function("new", |_, n| Ok(Self(n)));
methods.add_method("plus", |_, this, n: i64| Ok(this.0 + n));
}
}
let lua = Lua::new();
let globals = lua.globals();
globals.set("MyUserData", lua.create_proxy::<MyUserData>()?)?;
lua.load(
r#"
assert(MyUserData.static_field == 123)
local data = MyUserData.new(321)
assert(data.static_field == 123)
assert(data.n == 321)
assert(data:plus(1) == 322)
-- Error when accessing the proxy object fields and methods that require instance
local ok = pcall(function() return MyUserData.n end)
assert(not ok)
ok = pcall(function() return MyUserData:plus(1) end)
assert(not ok)
"#,
)
.exec()
}