mirror of
https://github.com/mlua-rs/mlua
synced 2026-06-08 16:05:43 +00:00
1152519074
They will allow implementing userdata methods that can be called only once, destructing userdata instance during the call.
1398 lines
41 KiB
Rust
1398 lines
41 KiB
Rust
use std::any::TypeId;
|
|
use std::collections::HashMap;
|
|
use std::string::String as StdString;
|
|
use std::sync::Arc;
|
|
|
|
#[cfg(feature = "lua54")]
|
|
use std::sync::atomic::{AtomicI64, Ordering};
|
|
|
|
use mlua::{
|
|
AnyUserData, Error, ExternalError, Function, Lua, MetaMethod, Nil, ObjectLike, Result, String, UserData,
|
|
UserDataFields, UserDataMethods, UserDataRef, UserDataRegistry, Value, Variadic,
|
|
};
|
|
|
|
#[test]
|
|
fn test_userdata() -> Result<()> {
|
|
struct UserData1(i64);
|
|
struct UserData2(Box<i64>);
|
|
|
|
impl UserData for UserData1 {}
|
|
impl UserData for UserData2 {}
|
|
|
|
let lua = Lua::new();
|
|
let userdata1 = lua.create_userdata(UserData1(1))?;
|
|
let userdata2 = lua.create_userdata(UserData2(Box::new(2)))?;
|
|
|
|
assert!(userdata1.is::<UserData1>());
|
|
assert!(userdata1.type_id() == Some(TypeId::of::<UserData1>()));
|
|
assert!(!userdata1.is::<UserData2>());
|
|
assert!(userdata2.is::<UserData2>());
|
|
assert!(!userdata2.is::<UserData1>());
|
|
assert!(userdata2.type_id() == Some(TypeId::of::<UserData2>()));
|
|
|
|
assert_eq!(userdata1.borrow::<UserData1>()?.0, 1);
|
|
assert_eq!(*userdata2.borrow::<UserData2>()?.0, 2);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_methods() -> Result<()> {
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
|
struct MyUserData(i64);
|
|
|
|
impl UserData for MyUserData {
|
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
|
methods.add_method("get_value", |_, data, ()| Ok(data.0));
|
|
methods.add_method_mut("set_value", |_, data, args| {
|
|
data.0 = args;
|
|
Ok(())
|
|
});
|
|
}
|
|
}
|
|
|
|
fn check_methods(lua: &Lua, userdata: AnyUserData) -> Result<()> {
|
|
let globals = lua.globals();
|
|
globals.set("userdata", &userdata)?;
|
|
lua.load(
|
|
r#"
|
|
function get_it()
|
|
return userdata:get_value()
|
|
end
|
|
|
|
function set_it(i)
|
|
return userdata:set_value(i)
|
|
end
|
|
"#,
|
|
)
|
|
.exec()?;
|
|
let get = globals.get::<Function>("get_it")?;
|
|
let set = globals.get::<Function>("set_it")?;
|
|
assert_eq!(get.call::<i64>(())?, 42);
|
|
userdata.borrow_mut::<MyUserData>()?.0 = 64;
|
|
assert_eq!(get.call::<i64>(())?, 64);
|
|
set.call::<()>(100)?;
|
|
assert_eq!(get.call::<i64>(())?, 100);
|
|
Ok(())
|
|
}
|
|
|
|
let lua = Lua::new();
|
|
|
|
check_methods(&lua, lua.create_userdata(MyUserData(42))?)?;
|
|
|
|
// Additionally check serializable userdata
|
|
#[cfg(feature = "serde")]
|
|
check_methods(&lua, lua.create_ser_userdata(MyUserData(42))?)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_method_variadic() -> Result<()> {
|
|
struct MyUserData(i64);
|
|
|
|
impl UserData for MyUserData {
|
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
|
methods.add_method("get", |_, data, ()| Ok(data.0));
|
|
methods.add_method_mut("add", |_, data, vals: Variadic<i64>| {
|
|
data.0 += vals.into_iter().sum::<i64>();
|
|
Ok(())
|
|
});
|
|
}
|
|
}
|
|
|
|
let lua = Lua::new();
|
|
let globals = lua.globals();
|
|
globals.set("userdata", MyUserData(0))?;
|
|
lua.load("userdata:add(1, 5, -10)").exec()?;
|
|
let ud: UserDataRef<MyUserData> = globals.get("userdata")?;
|
|
assert_eq!(ud.0, -4);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_metamethods() -> Result<()> {
|
|
#[derive(Copy, Clone)]
|
|
struct MyUserData(i64);
|
|
|
|
impl UserData for MyUserData {
|
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
|
methods.add_method("get", |_, data, ()| Ok(data.0));
|
|
methods.add_meta_function(
|
|
MetaMethod::Add,
|
|
|_, (lhs, rhs): (UserDataRef<Self>, UserDataRef<Self>)| Ok(MyUserData(lhs.0 + rhs.0)),
|
|
);
|
|
methods.add_meta_function(
|
|
MetaMethod::Sub,
|
|
|_, (lhs, rhs): (UserDataRef<Self>, UserDataRef<Self>)| Ok(MyUserData(lhs.0 - rhs.0)),
|
|
);
|
|
methods.add_meta_function(
|
|
MetaMethod::Eq,
|
|
|_, (lhs, rhs): (UserDataRef<Self>, UserDataRef<Self>)| Ok(lhs.0 == rhs.0),
|
|
);
|
|
methods.add_meta_method(MetaMethod::Index, |_, data, index: String| {
|
|
if index.to_str()? == "inner" {
|
|
Ok(data.0)
|
|
} else {
|
|
Err("no such custom index".into_lua_err())
|
|
}
|
|
});
|
|
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))]
|
|
methods.add_meta_method(MetaMethod::Pairs, |lua, data, ()| {
|
|
use std::iter::FromIterator;
|
|
let stateless_iter = lua.create_function(|_, (data, i): (UserDataRef<Self>, i64)| {
|
|
let i = i + 1;
|
|
if i <= data.0 {
|
|
return Ok(mlua::Variadic::from_iter(vec![i, i]));
|
|
}
|
|
return Ok(mlua::Variadic::new());
|
|
})?;
|
|
Ok((stateless_iter, data.clone(), 0))
|
|
});
|
|
}
|
|
}
|
|
|
|
let lua = Lua::new();
|
|
let globals = lua.globals();
|
|
globals.set("userdata1", MyUserData(7))?;
|
|
globals.set("userdata2", MyUserData(3))?;
|
|
globals.set("userdata3", MyUserData(3))?;
|
|
assert_eq!(
|
|
lua.load("userdata1 + userdata2")
|
|
.eval::<UserDataRef<MyUserData>>()?
|
|
.0,
|
|
10
|
|
);
|
|
|
|
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))]
|
|
let pairs_it = lua
|
|
.load(
|
|
r#"
|
|
function()
|
|
local r = 0
|
|
for i, v in pairs(userdata1) do
|
|
r = r + v
|
|
end
|
|
return r
|
|
end
|
|
"#,
|
|
)
|
|
.eval::<Function>()?;
|
|
|
|
assert_eq!(
|
|
lua.load("userdata1 - userdata2")
|
|
.eval::<UserDataRef<MyUserData>>()?
|
|
.0,
|
|
4
|
|
);
|
|
assert_eq!(lua.load("userdata1:get()").eval::<i64>()?, 7);
|
|
assert_eq!(lua.load("userdata2.inner").eval::<i64>()?, 3);
|
|
assert!(lua.load("userdata2.nonexist_field").eval::<()>().is_err());
|
|
|
|
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))]
|
|
assert_eq!(pairs_it.call::<i64>(())?, 28);
|
|
|
|
let userdata2: Value = globals.get("userdata2")?;
|
|
let userdata3: Value = globals.get("userdata3")?;
|
|
|
|
assert!(lua.load("userdata2 == userdata3").eval::<bool>()?);
|
|
assert!(userdata2 != userdata3); // because references are differ
|
|
assert!(userdata2.equals(&userdata3)?);
|
|
|
|
let userdata1: AnyUserData = globals.get("userdata1")?;
|
|
assert!(userdata1.metatable()?.contains(MetaMethod::Add)?);
|
|
assert!(userdata1.metatable()?.contains(MetaMethod::Sub)?);
|
|
assert!(userdata1.metatable()?.contains(MetaMethod::Index)?);
|
|
assert!(!userdata1.metatable()?.contains(MetaMethod::Pow)?);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(feature = "lua54")]
|
|
#[test]
|
|
fn test_metamethod_close() -> Result<()> {
|
|
#[derive(Clone)]
|
|
struct MyUserData(Arc<AtomicI64>);
|
|
|
|
impl UserData for MyUserData {
|
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
|
methods.add_method("get", |_, data, ()| Ok(data.0.load(Ordering::Relaxed)));
|
|
methods.add_meta_method(MetaMethod::Close, |_, data, _err: Value| {
|
|
data.0.store(0, Ordering::Relaxed);
|
|
Ok(())
|
|
});
|
|
}
|
|
}
|
|
|
|
let lua = Lua::new();
|
|
let globals = lua.globals();
|
|
|
|
let ud = MyUserData(Arc::new(AtomicI64::new(-1)));
|
|
let ud2 = ud.clone();
|
|
|
|
globals.set(
|
|
"new_userdata",
|
|
lua.create_function(move |_lua, val: i64| {
|
|
let ud = ud2.clone();
|
|
ud.0.store(val, Ordering::Relaxed);
|
|
Ok(ud)
|
|
})?,
|
|
)?;
|
|
|
|
lua.load(
|
|
r#"
|
|
do
|
|
local ud <close> = new_userdata(7)
|
|
assert(ud:get() == 7)
|
|
end
|
|
"#,
|
|
)
|
|
.exec()?;
|
|
|
|
assert_eq!(ud.0.load(Ordering::Relaxed), 0);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_gc_userdata() -> Result<()> {
|
|
struct MyUserdata {
|
|
id: u8,
|
|
}
|
|
|
|
impl UserData for MyUserdata {
|
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
|
methods.add_method("access", |_, this, ()| {
|
|
assert_eq!(this.id, 123);
|
|
Ok(())
|
|
});
|
|
}
|
|
}
|
|
|
|
let lua = Lua::new();
|
|
lua.globals().set("userdata", MyUserdata { id: 123 })?;
|
|
|
|
assert!(lua
|
|
.load(
|
|
r#"
|
|
local tbl = setmetatable({
|
|
userdata = userdata
|
|
}, { __gc = function(self)
|
|
-- resurrect userdata
|
|
hatch = self.userdata
|
|
end })
|
|
|
|
tbl = nil
|
|
userdata = nil -- make table and userdata collectable
|
|
collectgarbage("collect")
|
|
hatch:access()
|
|
"#
|
|
)
|
|
.exec()
|
|
.is_err());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_userdata_take() -> Result<()> {
|
|
#[derive(Debug)]
|
|
struct MyUserdata(Arc<i64>);
|
|
|
|
impl UserData for MyUserdata {
|
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
|
methods.add_method("num", |_, this, ()| Ok(*this.0))
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl serde::Serialize for MyUserdata {
|
|
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
serializer.serialize_i64(*self.0)
|
|
}
|
|
}
|
|
|
|
fn check_userdata_take(lua: &Lua, userdata: AnyUserData, rc: Arc<i64>) -> Result<()> {
|
|
lua.globals().set("userdata", &userdata)?;
|
|
assert_eq!(Arc::strong_count(&rc), 3);
|
|
{
|
|
let _value = userdata.borrow::<MyUserdata>()?;
|
|
// We should not be able to take userdata if it's borrowed
|
|
match userdata.take::<MyUserdata>() {
|
|
Err(Error::UserDataBorrowMutError) => {}
|
|
r => panic!("expected `UserDataBorrowMutError` error, got {:?}", r),
|
|
}
|
|
}
|
|
|
|
let value = userdata.take::<MyUserdata>()?;
|
|
assert_eq!(*value.0, 18);
|
|
drop(value);
|
|
assert_eq!(Arc::strong_count(&rc), 2);
|
|
|
|
match userdata.borrow::<MyUserdata>() {
|
|
Err(Error::UserDataDestructed) => {}
|
|
r => panic!("expected `UserDataDestructed` error, got {:?}", r),
|
|
}
|
|
match lua.load("userdata:num()").exec() {
|
|
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
|
|
Error::UserDataDestructed => {}
|
|
err => panic!("expected `UserDataDestructed`, got {:?}", err),
|
|
},
|
|
r => panic!("improper return for destructed userdata: {:?}", r),
|
|
}
|
|
|
|
assert!(!userdata.is::<MyUserdata>());
|
|
|
|
drop(userdata);
|
|
lua.globals().raw_remove("userdata")?;
|
|
lua.gc_collect()?;
|
|
lua.gc_collect()?;
|
|
assert_eq!(Arc::strong_count(&rc), 1);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
let lua = Lua::new();
|
|
|
|
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
|
|
#[cfg(feature = "serde")]
|
|
{
|
|
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)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_userdata_destroy() -> Result<()> {
|
|
struct MyUserdata(#[allow(unused)] Arc<()>);
|
|
|
|
impl UserData for MyUserdata {
|
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
|
methods.add_method("try_destroy", |lua, _this, ()| {
|
|
let ud = lua.globals().get::<AnyUserData>("ud")?;
|
|
match ud.destroy() {
|
|
Err(Error::UserDataBorrowMutError) => {}
|
|
r => panic!("expected `UserDataBorrowMutError` error, got {:?}", r),
|
|
}
|
|
Ok(())
|
|
});
|
|
}
|
|
}
|
|
|
|
let rc = Arc::new(());
|
|
|
|
let lua = Lua::new();
|
|
let ud = lua.create_userdata(MyUserdata(rc.clone()))?;
|
|
ud.set_user_value(MyUserdata(rc.clone()))?;
|
|
lua.globals().set("userdata", ud)?;
|
|
|
|
assert_eq!(Arc::strong_count(&rc), 3);
|
|
|
|
// Should destroy all objects
|
|
lua.globals().raw_remove("userdata")?;
|
|
lua.gc_collect()?;
|
|
lua.gc_collect()?;
|
|
|
|
assert_eq!(Arc::strong_count(&rc), 1);
|
|
|
|
let ud = lua.create_userdata(MyUserdata(rc.clone()))?;
|
|
assert_eq!(Arc::strong_count(&rc), 2);
|
|
let ud_ref = ud.borrow::<MyUserdata>()?;
|
|
// With active `UserDataRef` this methods only marks userdata as destructed
|
|
// without running destructor
|
|
ud.destroy().unwrap();
|
|
assert_eq!(Arc::strong_count(&rc), 2);
|
|
drop(ud_ref);
|
|
assert_eq!(Arc::strong_count(&rc), 1);
|
|
|
|
// We cannot destroy (internally) borrowed userdata
|
|
let ud = lua.create_userdata(MyUserdata(rc.clone()))?;
|
|
lua.globals().set("ud", &ud)?;
|
|
lua.load("ud:try_destroy()").exec().unwrap();
|
|
ud.destroy().unwrap();
|
|
assert_eq!(Arc::strong_count(&rc), 1);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_userdata_method_once() -> Result<()> {
|
|
struct MyUserdata(Arc<i64>);
|
|
|
|
impl UserData for MyUserdata {
|
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
|
methods.add_method_once("take_value", |_, this, ()| Ok(*this.0));
|
|
}
|
|
}
|
|
|
|
let lua = Lua::new();
|
|
let rc = Arc::new(42);
|
|
let userdata = lua.create_userdata(MyUserdata(rc.clone()))?;
|
|
lua.globals().set("userdata", &userdata)?;
|
|
|
|
// Control userdata
|
|
let userdata2 = lua.create_userdata(MyUserdata(rc.clone()))?;
|
|
lua.globals().set("userdata2", userdata2)?;
|
|
|
|
assert_eq!(lua.load("userdata:take_value()").eval::<i64>()?, 42);
|
|
match lua.load("userdata2.take_value(userdata)").eval::<i64>() {
|
|
Err(Error::CallbackError { cause, .. }) => {
|
|
let err = cause.to_string();
|
|
assert!(err.contains("bad argument `self` to `MyUserdata.take_value`"));
|
|
assert!(err.contains("userdata has been destructed"));
|
|
}
|
|
r => panic!("expected Err(CallbackError), got {r:?}"),
|
|
}
|
|
assert_eq!(Arc::strong_count(&rc), 2);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_user_values() -> Result<()> {
|
|
struct MyUserData;
|
|
|
|
impl UserData for MyUserData {}
|
|
|
|
let lua = Lua::new();
|
|
let ud = lua.create_userdata(MyUserData)?;
|
|
|
|
ud.set_nth_user_value(1, "hello")?;
|
|
ud.set_nth_user_value(2, "world")?;
|
|
ud.set_nth_user_value(65535, 321)?;
|
|
assert_eq!(ud.nth_user_value::<String>(1)?, "hello");
|
|
assert_eq!(ud.nth_user_value::<String>(2)?, "world");
|
|
assert_eq!(ud.nth_user_value::<Value>(3)?, Value::Nil);
|
|
assert_eq!(ud.nth_user_value::<i32>(65535)?, 321);
|
|
|
|
assert!(ud.nth_user_value::<Value>(0).is_err());
|
|
assert!(ud.nth_user_value::<Value>(65536).is_err());
|
|
|
|
// Named user values
|
|
let ud = lua.create_userdata(MyUserData)?;
|
|
ud.set_named_user_value("name", "alex")?;
|
|
ud.set_named_user_value("age", 10)?;
|
|
|
|
assert_eq!(ud.named_user_value::<String>("name")?, "alex");
|
|
assert_eq!(ud.named_user_value::<i32>("age")?, 10);
|
|
assert_eq!(ud.named_user_value::<Value>("nonexist")?, Value::Nil);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_functions() -> Result<()> {
|
|
struct MyUserData(i64);
|
|
|
|
impl UserData for MyUserData {
|
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
|
methods.add_function("get_value", |_, ud: AnyUserData| Ok(ud.borrow::<MyUserData>()?.0));
|
|
methods.add_function_mut("set_value", |_, (ud, value): (AnyUserData, i64)| {
|
|
ud.borrow_mut::<MyUserData>()?.0 = value;
|
|
Ok(())
|
|
});
|
|
methods.add_function("get_constant", |_, ()| Ok(7));
|
|
}
|
|
}
|
|
|
|
let lua = Lua::new();
|
|
let globals = lua.globals();
|
|
let userdata = lua.create_userdata(MyUserData(42))?;
|
|
globals.set("userdata", &userdata)?;
|
|
lua.load(
|
|
r#"
|
|
function get_it()
|
|
return userdata:get_value()
|
|
end
|
|
|
|
function set_it(i)
|
|
return userdata:set_value(i)
|
|
end
|
|
|
|
function get_constant()
|
|
return userdata.get_constant()
|
|
end
|
|
"#,
|
|
)
|
|
.exec()?;
|
|
let get = globals.get::<Function>("get_it")?;
|
|
let set = globals.get::<Function>("set_it")?;
|
|
let get_constant = globals.get::<Function>("get_constant")?;
|
|
assert_eq!(get.call::<i64>(())?, 42);
|
|
userdata.borrow_mut::<MyUserData>()?.0 = 64;
|
|
assert_eq!(get.call::<i64>(())?, 64);
|
|
set.call::<()>(100)?;
|
|
assert_eq!(get.call::<i64>(())?, 100);
|
|
assert_eq!(get_constant.call::<i64>(())?, 7);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_fields() -> Result<()> {
|
|
let lua = Lua::new();
|
|
let globals = lua.globals();
|
|
|
|
#[derive(Copy, Clone)]
|
|
struct MyUserData(i64);
|
|
|
|
impl UserData for MyUserData {
|
|
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
|
fields.add_field("static", "constant");
|
|
fields.add_field_method_get("val", |_, data| Ok(data.0));
|
|
fields.add_field_method_set("val", |_, data, val| {
|
|
data.0 = val;
|
|
Ok(())
|
|
});
|
|
|
|
// Field that emulates method
|
|
fields.add_field_function_get("val_fget", |lua, ud| {
|
|
lua.create_function(move |_, ()| Ok(ud.borrow::<MyUserData>()?.0))
|
|
});
|
|
|
|
// Use userdata "uservalue" storage
|
|
fields.add_field_function_get("uval", |_, ud| ud.user_value::<Option<String>>());
|
|
fields.add_field_function_set("uval", |_, ud, s: Option<String>| ud.set_user_value(s));
|
|
|
|
fields.add_meta_field(MetaMethod::Index, HashMap::from([("f", 321)]));
|
|
fields.add_meta_field_with(MetaMethod::NewIndex, |lua| {
|
|
lua.create_function(|lua, (_, field, val): (AnyUserData, String, Value)| {
|
|
lua.globals().set(field, val)?;
|
|
Ok(())
|
|
})
|
|
})
|
|
}
|
|
|
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
|
methods.add_method("dummy", |_, _, ()| Ok(()));
|
|
}
|
|
}
|
|
|
|
globals.set("ud", MyUserData(7))?;
|
|
lua.load(
|
|
r#"
|
|
assert(ud.static == "constant")
|
|
assert(ud.val == 7)
|
|
ud.val = 10
|
|
assert(ud.val == 10)
|
|
assert(ud:val_fget() == 10)
|
|
|
|
assert(ud.uval == nil)
|
|
ud.uval = "hello"
|
|
assert(ud.uval == "hello")
|
|
|
|
assert(ud.f == 321)
|
|
|
|
ud.unknown = 789
|
|
assert(unknown == 789)
|
|
"#,
|
|
)
|
|
.exec()?;
|
|
|
|
// Case: fields + __index metamethod (function)
|
|
struct MyUserData2(i64);
|
|
|
|
impl UserData for MyUserData2 {
|
|
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
|
fields.add_field("z", 0);
|
|
fields.add_field_method_get("x", |_, data| Ok(data.0));
|
|
}
|
|
|
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
|
methods.add_meta_method(MetaMethod::Index, |_, _, name: StdString| match &*name {
|
|
"y" => Ok(Some(-1)),
|
|
_ => Ok(None),
|
|
});
|
|
}
|
|
}
|
|
|
|
globals.set("ud", MyUserData2(1))?;
|
|
lua.load(
|
|
r#"
|
|
assert(ud.x == 1)
|
|
assert(ud.y == -1)
|
|
assert(ud.z == 0)
|
|
"#,
|
|
)
|
|
.exec()?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_metatable() -> Result<()> {
|
|
#[derive(Copy, Clone)]
|
|
struct MyUserData;
|
|
|
|
impl UserData for MyUserData {
|
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
|
methods.add_function("my_type_name", |_, data: AnyUserData| {
|
|
let metatable = data.metatable()?;
|
|
metatable.get::<String>(MetaMethod::Type)
|
|
});
|
|
}
|
|
}
|
|
|
|
let lua = Lua::new();
|
|
let globals = lua.globals();
|
|
globals.set("ud", MyUserData)?;
|
|
lua.load(r#"assert(ud:my_type_name() == "MyUserData")"#).exec()?;
|
|
|
|
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
|
|
lua.load(r#"assert(tostring(ud):sub(1, 11) == "MyUserData:")"#)
|
|
.exec()?;
|
|
#[cfg(feature = "luau")]
|
|
lua.load(r#"assert(typeof(ud) == "MyUserData")"#).exec()?;
|
|
|
|
let ud: AnyUserData = globals.get("ud")?;
|
|
let metatable = ud.metatable()?;
|
|
|
|
match metatable.get::<Value>("__gc") {
|
|
Ok(_) => panic!("expected MetaMethodRestricted, got no error"),
|
|
Err(Error::MetaMethodRestricted(_)) => {}
|
|
Err(e) => panic!("expected MetaMethodRestricted, got {:?}", e),
|
|
}
|
|
|
|
match metatable.set(MetaMethod::Index, Nil) {
|
|
Ok(_) => panic!("expected MetaMethodRestricted, got no error"),
|
|
Err(Error::MetaMethodRestricted(_)) => {}
|
|
Err(e) => panic!("expected MetaMethodRestricted, got {:?}", e),
|
|
}
|
|
|
|
let mut methods = metatable
|
|
.pairs()
|
|
.map(|kv: Result<(_, Value)>| Ok(kv?.0))
|
|
.collect::<Result<Vec<_>>>()?;
|
|
methods.sort();
|
|
assert_eq!(methods, vec!["__index", MetaMethod::Type.name()]);
|
|
|
|
#[derive(Copy, Clone)]
|
|
struct MyUserData2;
|
|
|
|
impl UserData for MyUserData2 {
|
|
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
|
fields.add_meta_field_with("__index", |_| Ok(1));
|
|
}
|
|
}
|
|
|
|
match lua.create_userdata(MyUserData2) {
|
|
Ok(_) => panic!("expected MetaMethodTypeError, got no error"),
|
|
Err(Error::MetaMethodTypeError { .. }) => {}
|
|
Err(e) => panic!("expected MetaMethodTypeError, got {:?}", e),
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
struct MyUserData3;
|
|
|
|
impl UserData for MyUserData3 {
|
|
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
|
fields.add_meta_field_with(MetaMethod::Type, |_| Ok("CustomName"));
|
|
}
|
|
}
|
|
|
|
let ud = lua.create_userdata(MyUserData3)?;
|
|
let metatable = ud.metatable()?;
|
|
assert_eq!(metatable.get::<String>(MetaMethod::Type)?.to_str()?, "CustomName");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_userdata_proxy() -> Result<()> {
|
|
struct MyUserData(i64);
|
|
|
|
impl UserData for MyUserData {
|
|
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
|
fields.add_field("static_field", 123);
|
|
fields.add_field_method_get("n", |_, this| Ok(this.0));
|
|
}
|
|
|
|
fn add_methods<M: UserDataMethods<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()
|
|
}
|
|
|
|
#[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(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_any_userdata_wrap() -> Result<()> {
|
|
let lua = Lua::new();
|
|
|
|
lua.register_userdata_type::<StdString>(|reg| {
|
|
reg.add_method("get", |_, this, ()| Ok(this.clone()));
|
|
})?;
|
|
|
|
lua.globals().set("s", AnyUserData::wrap("hello".to_string()))?;
|
|
lua.load(
|
|
r#"
|
|
assert(s:get() == "hello")
|
|
"#,
|
|
)
|
|
.exec()
|
|
.unwrap();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_userdata_object_like() -> Result<()> {
|
|
let lua = Lua::new();
|
|
|
|
#[derive(Clone, Copy)]
|
|
struct MyUserData(u32);
|
|
|
|
impl UserData for MyUserData {
|
|
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
|
fields.add_field_method_get("n", |_, this| Ok(this.0));
|
|
fields.add_field_method_set("n", |_, this, val| {
|
|
this.0 = val;
|
|
Ok(())
|
|
});
|
|
}
|
|
|
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
|
methods.add_meta_method(MetaMethod::Call, |_, _this, ()| Ok("called"));
|
|
methods.add_method_mut("add", |_, this, x: u32| {
|
|
this.0 += x;
|
|
Ok(())
|
|
});
|
|
}
|
|
}
|
|
|
|
let ud = lua.create_userdata(MyUserData(123))?;
|
|
|
|
assert_eq!(ud.get::<u32>("n")?, 123);
|
|
ud.set("n", 321)?;
|
|
assert_eq!(ud.get::<u32>("n")?, 321);
|
|
assert_eq!(ud.get::<Option<u32>>("non-existent")?, None);
|
|
match ud.set("non-existent", 123) {
|
|
Err(Error::RuntimeError(_)) => {}
|
|
r => panic!("expected RuntimeError, got {r:?}"),
|
|
}
|
|
|
|
assert_eq!(ud.call::<String>(())?, "called");
|
|
|
|
ud.call_method::<()>("add", 2)?;
|
|
assert_eq!(ud.get::<u32>("n")?, 323);
|
|
|
|
match ud.call_method::<()>("non_existent", ()) {
|
|
Err(Error::RuntimeError(err)) => {
|
|
assert!(err.contains("attempt to call a nil value (function 'non_existent')"))
|
|
}
|
|
r => panic!("expected RuntimeError, got {r:?}"),
|
|
}
|
|
|
|
assert!(ud.to_string()?.starts_with("MyUserData"));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_userdata_method_errors() -> Result<()> {
|
|
struct MyUserData(i64);
|
|
|
|
impl UserData for MyUserData {
|
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
|
methods.add_method("get_value", |_, data, ()| Ok(data.0));
|
|
}
|
|
}
|
|
|
|
let lua = Lua::new();
|
|
|
|
let ud = lua.create_userdata(MyUserData(123))?;
|
|
let res = ud.call_function::<()>("get_value", "not a userdata");
|
|
match res {
|
|
Err(Error::CallbackError { cause, .. }) => match cause.as_ref() {
|
|
Error::BadArgument {
|
|
to,
|
|
name,
|
|
cause: cause2,
|
|
..
|
|
} => {
|
|
assert_eq!(to.as_deref(), Some("MyUserData.get_value"));
|
|
assert_eq!(name.as_deref(), Some("self"));
|
|
assert_eq!(
|
|
cause2.to_string(),
|
|
"error converting Lua string to userdata (expected userdata of type 'MyUserData')"
|
|
);
|
|
}
|
|
err => panic!("expected BadArgument, got {err:?}"),
|
|
},
|
|
r => panic!("expected CallbackError, got {r:?}"),
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_userdata_pointer() -> Result<()> {
|
|
let lua = Lua::new();
|
|
|
|
let ud1 = lua.create_any_userdata("hello")?;
|
|
let ud2 = lua.create_any_userdata("hello")?;
|
|
|
|
assert_eq!(ud1.to_pointer(), ud1.clone().to_pointer());
|
|
// Different userdata objects with the same value should have different pointers
|
|
assert_ne!(ud1.to_pointer(), ud2.to_pointer());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(feature = "macros")]
|
|
#[test]
|
|
fn test_userdata_derive() -> Result<()> {
|
|
let lua = Lua::new();
|
|
|
|
// Simple struct
|
|
|
|
#[derive(Clone, Copy, mlua::FromLua)]
|
|
struct MyUserData(i32);
|
|
|
|
lua.register_userdata_type::<MyUserData>(|reg| {
|
|
reg.add_function("val", |_, this: MyUserData| Ok(this.0));
|
|
})?;
|
|
|
|
lua.globals().set("ud", AnyUserData::wrap(MyUserData(123)))?;
|
|
lua.load("assert(ud:val() == 123)").exec()?;
|
|
|
|
// More complex struct where generics and where clause
|
|
|
|
#[derive(Clone, Copy, mlua::FromLua)]
|
|
struct MyUserData2<'a, T: ?Sized>(&'a T)
|
|
where
|
|
T: Copy;
|
|
|
|
lua.register_userdata_type::<MyUserData2<'static, i32>>(|reg| {
|
|
reg.add_function("val", |_, this: MyUserData2<'static, i32>| Ok(*this.0));
|
|
})?;
|
|
|
|
lua.globals().set("ud", AnyUserData::wrap(MyUserData2(&321)))?;
|
|
lua.load("assert(ud:val() == 321)").exec()?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_nested_userdata_gc() -> Result<()> {
|
|
let lua = Lua::new();
|
|
|
|
let counter = Arc::new(());
|
|
let arr = vec![lua.create_any_userdata(counter.clone())?];
|
|
let arr_ud = lua.create_any_userdata(arr)?;
|
|
|
|
assert_eq!(Arc::strong_count(&counter), 2);
|
|
drop(arr_ud);
|
|
// On first iteration Lua will destroy the array, on second - userdata
|
|
lua.gc_collect()?;
|
|
lua.gc_collect()?;
|
|
assert_eq!(Arc::strong_count(&counter), 1);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(feature = "userdata-wrappers")]
|
|
#[test]
|
|
fn test_userdata_wrappers() -> Result<()> {
|
|
#[derive(Debug)]
|
|
struct MyUserData(i64);
|
|
|
|
impl UserData for MyUserData {
|
|
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
|
fields.add_field("static", "constant");
|
|
fields.add_field_method_get("data", |_, this| Ok(this.0));
|
|
fields.add_field_method_set("data", |_, this, val| {
|
|
this.0 = val;
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
|
methods.add_method("dbg", |_, this, ()| Ok(format!("{this:?}")));
|
|
}
|
|
}
|
|
|
|
let lua = Lua::new();
|
|
let globals = lua.globals();
|
|
|
|
// Rc<T>
|
|
#[cfg(not(feature = "send"))]
|
|
{
|
|
use std::rc::Rc;
|
|
|
|
let ud = Rc::new(MyUserData(1));
|
|
globals.set("ud", ud.clone())?;
|
|
lua.load(
|
|
r#"
|
|
assert(ud.static == "constant")
|
|
local ok, err = pcall(function() ud.data = 2 end)
|
|
assert(
|
|
tostring(err):find("error mutably borrowing userdata") ~= nil,
|
|
"expected 'error mutably borrowing userdata', got '" .. tostring(err) .. "'"
|
|
)
|
|
assert(ud.data == 1)
|
|
assert(ud:dbg(), "MyUserData(1)")
|
|
"#,
|
|
)
|
|
.exec()
|
|
.unwrap();
|
|
|
|
// Test borrowing original userdata
|
|
{
|
|
let ud = globals.get::<AnyUserData>("ud")?;
|
|
assert!(ud.is::<Rc<MyUserData>>());
|
|
assert!(!ud.is::<MyUserData>());
|
|
|
|
assert_eq!(ud.borrow::<MyUserData>()?.0, 1);
|
|
assert!(matches!(
|
|
ud.borrow_mut::<MyUserData>(),
|
|
Err(Error::UserDataBorrowMutError)
|
|
));
|
|
assert!(ud.borrow_mut::<Rc<MyUserData>>().is_ok());
|
|
|
|
assert_eq!(ud.borrow_scoped::<MyUserData, _>(|x| x.0)?, 1);
|
|
assert!(matches!(
|
|
ud.borrow_mut_scoped::<MyUserData, _>(|_| ()),
|
|
Err(Error::UserDataBorrowMutError)
|
|
));
|
|
}
|
|
|
|
// Collect userdata
|
|
globals.set("ud", Nil)?;
|
|
lua.gc_collect()?;
|
|
assert_eq!(Rc::strong_count(&ud), 1);
|
|
|
|
// We must be able to mutate userdata when having one reference only
|
|
globals.set("ud", ud)?;
|
|
lua.load(
|
|
r#"
|
|
ud.data = 2
|
|
assert(ud.data == 2)
|
|
"#,
|
|
)
|
|
.exec()
|
|
.unwrap();
|
|
}
|
|
|
|
// Rc<RefCell<T>>
|
|
#[cfg(not(feature = "send"))]
|
|
{
|
|
use std::cell::RefCell;
|
|
use std::rc::Rc;
|
|
|
|
let ud = Rc::new(RefCell::new(MyUserData(2)));
|
|
globals.set("ud", ud.clone())?;
|
|
lua.load(
|
|
r#"
|
|
assert(ud.static == "constant")
|
|
assert(ud.data == 2)
|
|
ud.data = 10
|
|
assert(ud.data == 10)
|
|
assert(ud:dbg() == "MyUserData(10)")
|
|
"#,
|
|
)
|
|
.exec()
|
|
.unwrap();
|
|
|
|
// Test borrowing original userdata
|
|
{
|
|
let ud = globals.get::<AnyUserData>("ud")?;
|
|
assert!(ud.is::<Rc<RefCell<MyUserData>>>());
|
|
assert!(!ud.is::<MyUserData>());
|
|
|
|
assert_eq!(ud.borrow::<MyUserData>()?.0, 10);
|
|
assert_eq!(ud.borrow_mut::<MyUserData>()?.0, 10);
|
|
ud.borrow_mut::<MyUserData>()?.0 = 20;
|
|
assert_eq!(ud.borrow::<MyUserData>()?.0, 20);
|
|
|
|
assert_eq!(ud.borrow_scoped::<MyUserData, _>(|x| x.0)?, 20);
|
|
ud.borrow_mut_scoped::<MyUserData, _>(|x| x.0 = 30)?;
|
|
assert_eq!(ud.borrow::<MyUserData>()?.0, 30);
|
|
|
|
// Double (read) borrow is okay
|
|
let _borrow = ud.borrow::<MyUserData>()?;
|
|
assert_eq!(ud.borrow::<MyUserData>()?.0, 30);
|
|
assert!(matches!(
|
|
ud.borrow_mut::<MyUserData>(),
|
|
Err(Error::UserDataBorrowMutError)
|
|
));
|
|
}
|
|
|
|
// Collect userdata
|
|
globals.set("ud", Nil)?;
|
|
lua.gc_collect()?;
|
|
assert_eq!(Rc::strong_count(&ud), 1);
|
|
|
|
// Check destroying wrapped UserDataRef without references in Lua
|
|
let ud = lua.convert::<UserDataRef<MyUserData>>(ud)?;
|
|
lua.gc_collect()?;
|
|
assert_eq!(ud.0, 30);
|
|
drop(ud);
|
|
}
|
|
|
|
// Arc<T>
|
|
{
|
|
let ud = Arc::new(MyUserData(3));
|
|
globals.set("ud", ud.clone())?;
|
|
lua.load(
|
|
r#"
|
|
assert(ud.static == "constant")
|
|
local ok, err = pcall(function() ud.data = 4 end)
|
|
assert(
|
|
tostring(err):find("error mutably borrowing userdata") ~= nil,
|
|
"expected 'error mutably borrowing userdata', got '" .. tostring(err) .. "'"
|
|
)
|
|
assert(ud.data == 3)
|
|
assert(ud:dbg() == "MyUserData(3)")
|
|
"#,
|
|
)
|
|
.exec()
|
|
.unwrap();
|
|
|
|
// Test borrowing original userdata
|
|
{
|
|
let ud = globals.get::<AnyUserData>("ud")?;
|
|
assert!(ud.is::<Arc<MyUserData>>());
|
|
assert!(!ud.is::<MyUserData>());
|
|
|
|
assert_eq!(ud.borrow::<MyUserData>()?.0, 3);
|
|
assert!(matches!(
|
|
ud.borrow_mut::<MyUserData>(),
|
|
Err(Error::UserDataBorrowMutError)
|
|
));
|
|
assert!(ud.borrow_mut::<Arc<MyUserData>>().is_ok());
|
|
|
|
assert_eq!(ud.borrow_scoped::<MyUserData, _>(|x| x.0)?, 3);
|
|
assert!(matches!(
|
|
ud.borrow_mut_scoped::<MyUserData, _>(|_| ()),
|
|
Err(Error::UserDataBorrowMutError)
|
|
));
|
|
}
|
|
|
|
// Collect userdata
|
|
globals.set("ud", Nil)?;
|
|
lua.gc_collect()?;
|
|
assert_eq!(Arc::strong_count(&ud), 1);
|
|
|
|
// We must be able to mutate userdata when having one reference only
|
|
globals.set("ud", ud)?;
|
|
lua.load(
|
|
r#"
|
|
ud.data = 4
|
|
assert(ud.data == 4)
|
|
"#,
|
|
)
|
|
.exec()
|
|
.unwrap();
|
|
}
|
|
|
|
// Arc<Mutex<T>>
|
|
{
|
|
use std::sync::Mutex;
|
|
|
|
let ud = Arc::new(Mutex::new(MyUserData(5)));
|
|
globals.set("ud", ud.clone())?;
|
|
lua.load(
|
|
r#"
|
|
assert(ud.static == "constant")
|
|
assert(ud.data == 5)
|
|
ud.data = 6
|
|
assert(ud.data == 6)
|
|
assert(ud:dbg() == "MyUserData(6)")
|
|
"#,
|
|
)
|
|
.exec()
|
|
.unwrap();
|
|
|
|
// Test borrowing original userdata
|
|
{
|
|
let ud = globals.get::<AnyUserData>("ud")?;
|
|
assert!(ud.is::<Arc<Mutex<MyUserData>>>());
|
|
assert!(!ud.is::<MyUserData>());
|
|
|
|
#[rustfmt::skip]
|
|
assert!(matches!(ud.borrow::<MyUserData>(), Err(Error::UserDataTypeMismatch)));
|
|
#[rustfmt::skip]
|
|
assert!(matches!(ud.borrow_mut::<MyUserData>(), Err(Error::UserDataTypeMismatch)));
|
|
|
|
assert_eq!(ud.borrow_scoped::<MyUserData, _>(|x| x.0)?, 6);
|
|
ud.borrow_mut_scoped::<MyUserData, _>(|x| x.0 = 8)?;
|
|
assert_eq!(ud.borrow_scoped::<MyUserData, _>(|x| x.0)?, 8);
|
|
}
|
|
|
|
// Collect userdata
|
|
globals.set("ud", Nil)?;
|
|
lua.gc_collect()?;
|
|
assert_eq!(Arc::strong_count(&ud), 1);
|
|
}
|
|
|
|
// Arc<RwLock<T>>
|
|
{
|
|
use std::sync::RwLock;
|
|
|
|
let ud = Arc::new(RwLock::new(MyUserData(9)));
|
|
globals.set("ud", ud.clone())?;
|
|
lua.load(
|
|
r#"
|
|
assert(ud.static == "constant")
|
|
assert(ud.data == 9)
|
|
ud.data = 10
|
|
assert(ud.data == 10)
|
|
assert(ud:dbg() == "MyUserData(10)")
|
|
"#,
|
|
)
|
|
.exec()
|
|
.unwrap();
|
|
|
|
// Test borrowing original userdata
|
|
{
|
|
let ud = globals.get::<AnyUserData>("ud")?;
|
|
assert!(ud.is::<Arc<RwLock<MyUserData>>>());
|
|
assert!(!ud.is::<MyUserData>());
|
|
|
|
#[rustfmt::skip]
|
|
assert!(matches!(ud.borrow::<MyUserData>(), Err(Error::UserDataTypeMismatch)));
|
|
#[rustfmt::skip]
|
|
assert!(matches!(ud.borrow_mut::<MyUserData>(), Err(Error::UserDataTypeMismatch)));
|
|
|
|
assert_eq!(ud.borrow_scoped::<MyUserData, _>(|x| x.0)?, 10);
|
|
ud.borrow_mut_scoped::<MyUserData, _>(|x| x.0 = 12)?;
|
|
assert_eq!(ud.borrow_scoped::<MyUserData, _>(|x| x.0)?, 12);
|
|
}
|
|
|
|
// Collect userdata
|
|
globals.set("ud", Nil)?;
|
|
lua.gc_collect()?;
|
|
assert_eq!(Arc::strong_count(&ud), 1);
|
|
}
|
|
|
|
// Arc<parking_lot::Mutex<T>>
|
|
{
|
|
use parking_lot::Mutex;
|
|
|
|
let ud = Arc::new(Mutex::new(MyUserData(13)));
|
|
globals.set("ud", ud.clone())?;
|
|
lua.load(
|
|
r#"
|
|
assert(ud.static == "constant")
|
|
assert(ud.data == 13)
|
|
ud.data = 14
|
|
assert(ud.data == 14)
|
|
assert(ud:dbg() == "MyUserData(14)")
|
|
"#,
|
|
)
|
|
.exec()
|
|
.unwrap();
|
|
|
|
// Test borrowing original userdata
|
|
{
|
|
let ud = globals.get::<AnyUserData>("ud")?;
|
|
assert!(ud.is::<Arc<Mutex<MyUserData>>>());
|
|
assert!(!ud.is::<MyUserData>());
|
|
|
|
assert_eq!(ud.borrow::<MyUserData>()?.0, 14);
|
|
assert_eq!(ud.borrow_mut::<MyUserData>()?.0, 14);
|
|
ud.borrow_mut::<MyUserData>()?.0 = 15;
|
|
assert_eq!(ud.borrow::<MyUserData>()?.0, 15);
|
|
|
|
assert_eq!(ud.borrow_scoped::<MyUserData, _>(|x| x.0)?, 15);
|
|
ud.borrow_mut_scoped::<MyUserData, _>(|x| x.0 = 16)?;
|
|
assert_eq!(ud.borrow::<MyUserData>()?.0, 16);
|
|
|
|
// Double borrow is not allowed
|
|
let _borrow = ud.borrow::<MyUserData>()?;
|
|
assert!(matches!(
|
|
ud.borrow::<MyUserData>(),
|
|
Err(Error::UserDataBorrowError)
|
|
));
|
|
}
|
|
|
|
// Collect userdata
|
|
globals.set("ud", Nil)?;
|
|
lua.gc_collect()?;
|
|
assert_eq!(Arc::strong_count(&ud), 1);
|
|
|
|
// Check destroying wrapped UserDataRef without references in Lua
|
|
let ud = lua.convert::<UserDataRef<MyUserData>>(ud)?;
|
|
lua.gc_collect()?;
|
|
assert_eq!(ud.0, 16);
|
|
drop(ud);
|
|
}
|
|
|
|
// Arc<parking_lot::RwLock<T>>
|
|
{
|
|
use parking_lot::RwLock;
|
|
|
|
let ud = Arc::new(RwLock::new(MyUserData(17)));
|
|
globals.set("ud", ud.clone())?;
|
|
lua.load(
|
|
r#"
|
|
assert(ud.static == "constant")
|
|
assert(ud.data == 17)
|
|
ud.data = 18
|
|
assert(ud.data == 18)
|
|
assert(ud:dbg() == "MyUserData(18)")
|
|
"#,
|
|
)
|
|
.exec()
|
|
.unwrap();
|
|
|
|
// Test borrowing original userdata
|
|
{
|
|
let ud = globals.get::<AnyUserData>("ud")?;
|
|
assert!(ud.is::<Arc<RwLock<MyUserData>>>());
|
|
assert!(!ud.is::<MyUserData>());
|
|
|
|
assert_eq!(ud.borrow::<MyUserData>()?.0, 18);
|
|
assert_eq!(ud.borrow_mut::<MyUserData>()?.0, 18);
|
|
ud.borrow_mut::<MyUserData>()?.0 = 19;
|
|
assert_eq!(ud.borrow::<MyUserData>()?.0, 19);
|
|
|
|
assert_eq!(ud.borrow_scoped::<MyUserData, _>(|x| x.0)?, 19);
|
|
ud.borrow_mut_scoped::<MyUserData, _>(|x| x.0 = 20)?;
|
|
assert_eq!(ud.borrow::<MyUserData>()?.0, 20);
|
|
|
|
// Multiple read borrows are allowed with parking_lot::RwLock
|
|
let _borrow1 = ud.borrow::<MyUserData>()?;
|
|
let _borrow2 = ud.borrow::<MyUserData>()?;
|
|
assert!(matches!(
|
|
ud.borrow_mut::<MyUserData>(),
|
|
Err(Error::UserDataBorrowMutError)
|
|
));
|
|
}
|
|
|
|
// Collect userdata
|
|
globals.set("ud", Nil)?;
|
|
lua.gc_collect()?;
|
|
assert_eq!(Arc::strong_count(&ud), 1);
|
|
|
|
// Check destroying wrapped UserDataRef without references in Lua
|
|
let ud = lua.convert::<UserDataRef<MyUserData>>(ud)?;
|
|
lua.gc_collect()?;
|
|
assert_eq!(ud.0, 20);
|
|
drop(ud);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(feature = "luau")]
|
|
#[test]
|
|
fn test_userdata_namecall() -> Result<()> {
|
|
let lua = Lua::new();
|
|
|
|
struct MyUserData;
|
|
|
|
impl UserData for MyUserData {
|
|
fn register(registry: &mut mlua::UserDataRegistry<Self>) {
|
|
registry.add_method("method", |_, _, ()| Ok("method called"));
|
|
registry.add_field_method_get("field", |_, _| Ok("field value"));
|
|
|
|
registry.add_meta_method(MetaMethod::Index, |_, _, key: StdString| Ok(key));
|
|
|
|
registry.enable_namecall();
|
|
}
|
|
}
|
|
|
|
let ud = lua.create_userdata(MyUserData)?;
|
|
lua.globals().set("ud", &ud)?;
|
|
lua.load(
|
|
r#"
|
|
assert(ud:method() == "method called")
|
|
assert(ud.field == "field value")
|
|
assert(ud.dynamic_field == "dynamic_field")
|
|
local ok, err = pcall(function() return ud:dynamic_field() end)
|
|
assert(tostring(err):find("attempt to call an unknown method 'dynamic_field'") ~= nil)
|
|
"#,
|
|
)
|
|
.exec()?;
|
|
|
|
ud.destroy()?;
|
|
let err = lua.load("ud:method()").exec().unwrap_err();
|
|
assert!(err.to_string().contains("userdata has been destructed"));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_userdata_get_path() -> Result<()> {
|
|
let lua = Lua::new();
|
|
|
|
struct MyUd;
|
|
impl UserData for MyUd {
|
|
fn register(registry: &mut UserDataRegistry<Self>) {
|
|
registry.add_field("value", "userdata_value");
|
|
}
|
|
}
|
|
|
|
let ud = lua.create_userdata(MyUd)?;
|
|
assert_eq!(ud.get_path::<String>(".value")?, "userdata_value");
|
|
|
|
Ok(())
|
|
}
|