Add `AnyUserDataExt` trait with auxiliary functions for `AnyUserData`

This commit is contained in:
Alex Orlenko 2023-02-18 23:40:28 +00:00
parent b8e3290f35
commit 94f01e597c
No known key found for this signature in database
GPG Key ID: 4C150C250863B96D
4 changed files with 265 additions and 3 deletions

View File

@ -98,6 +98,7 @@ mod table;
mod thread;
mod types;
mod userdata;
mod userdata_ext;
mod userdata_impl;
mod util;
mod value;
@ -121,6 +122,7 @@ pub use crate::types::{Integer, LightUserData, Number, RegistryKey};
pub use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods,
};
pub use crate::userdata_ext::AnyUserDataExt;
pub use crate::userdata_impl::UserDataRegistrar;
pub use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value};

204
src/userdata_ext.rs Normal file
View File

@ -0,0 +1,204 @@
use crate::error::{Error, Result};
use crate::userdata::{AnyUserData, MetaMethod};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value};
#[cfg(feature = "async")]
use {futures_core::future::LocalBoxFuture, futures_util::future};
/// An extension trait for [`AnyUserData`] that provides a variety of convenient functionality.
pub trait AnyUserDataExt<'lua> {
/// Gets the value associated to `key` from the userdata, assuming it has `__index` metamethod.
fn get<K: IntoLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V>;
/// Sets the value associated to `key` in the userdata, assuming it has `__newindex` metamethod.
fn set<K: IntoLua<'lua>, V: IntoLua<'lua>>(&self, key: K, value: V) -> Result<()>;
/// Calls the userdata as a function assuming it has `__call` metamethod.
///
/// The metamethod is called with the userdata as its first argument, followed by the passed arguments.
fn call<A, R>(&self, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Asynchronously calls the userdata as a function assuming it has `__call` metamethod.
///
/// The metamethod is called with the userdata as its first argument, followed by the passed arguments.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
/// Calls the userdata method, assuming it has `__index` metamethod
/// and a function associated to `name`.
fn call_method<A, R>(&self, name: impl AsRef<str>, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Gets the function associated to `key` from the table and asynchronously executes it,
/// passing the table itself along with `args` as function arguments and returning Future.
///
/// Requires `feature = "async"`
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_method<'fut, A, R>(
&self,
name: impl AsRef<str>,
args: A,
) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
/// Gets the function associated to `key` from the table and executes it,
/// passing `args` as function arguments.
///
/// This is a shortcut for
/// `table.get::<_, Function>(key)?.call(args)`
///
/// This might invoke the `__index` metamethod.
fn call_function<A, R>(&self, name: impl AsRef<str>, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Gets the function associated to `key` from the table and asynchronously executes it,
/// passing `args` as function arguments and returning Future.
///
/// Requires `feature = "async"`
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_function<'fut, A, R>(
&self,
name: impl AsRef<str>,
args: A,
) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
}
impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> {
fn get<K: IntoLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> {
let metatable = self.get_metatable()?;
match metatable.get::<Value>(MetaMethod::Index)? {
Value::Table(table) => table.raw_get(key),
Value::Function(func) => func.call((self.clone(), key)),
_ => Err(Error::RuntimeError(
"attempt to index a userdata value".to_string(),
)),
}
}
fn set<K: IntoLua<'lua>, V: IntoLua<'lua>>(&self, key: K, value: V) -> Result<()> {
let metatable = self.get_metatable()?;
match metatable.get::<Value>(MetaMethod::NewIndex)? {
Value::Table(table) => table.raw_set(key, value),
Value::Function(func) => func.call((self.clone(), key, value)),
_ => Err(Error::RuntimeError(
"attempt to index a userdata value".to_string(),
)),
}
}
fn call<A, R>(&self, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
let metatable = self.get_metatable()?;
match metatable.get::<Value>(MetaMethod::Call)? {
Value::Function(func) => func.call((self.clone(), args)),
_ => Err(Error::RuntimeError(
"attempt to call a userdata value".to_string(),
)),
}
}
#[cfg(feature = "async")]
fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
let metatable = match self.get_metatable() {
Ok(metatable) => metatable,
Err(err) => return Box::pin(future::err(err)),
};
match metatable.get::<Value>(MetaMethod::Call) {
Ok(Value::Function(func)) => func.call_async((self.clone(), args)),
Ok(_) => Box::pin(future::err(Error::RuntimeError(
"attempt to call a userdata value".to_string(),
))),
Err(err) => Box::pin(future::err(err)),
}
}
fn call_method<A, R>(&self, name: impl AsRef<str>, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
self.call_function(name, (self.clone(), args))
}
#[cfg(feature = "async")]
fn call_async_method<'fut, A, R>(
&self,
name: impl AsRef<str>,
args: A,
) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
self.call_async_function(name, (self.clone(), args))
}
fn call_function<A, R>(&self, name: impl AsRef<str>, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
match self.get(name.as_ref())? {
Value::Function(func) => func.call(args),
val => Err(Error::RuntimeError(format!(
"attempt to call a {} value",
val.type_name()
))),
}
}
#[cfg(feature = "async")]
fn call_async_function<'fut, A, R>(
&self,
name: impl AsRef<str>,
args: A,
) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
{
match self.get(name.as_ref()) {
Ok(Value::Function(func)) => func.call_async(args),
Ok(val) => Box::pin(future::err(Error::RuntimeError(format!(
"attempt to call a {} value",
val.type_name()
)))),
Err(err) => Box::pin(future::err(err)),
}
}
}

View File

@ -8,7 +8,8 @@ use futures_timer::Delay;
use futures_util::stream::TryStreamExt;
use mlua::{
Error, Function, Lua, LuaOptions, Result, StdLib, Table, TableExt, UserData, UserDataMethods,
AnyUserDataExt, Error, Function, Lua, LuaOptions, Result, StdLib, Table, TableExt, UserData,
UserDataMethods,
};
#[tokio::test]
@ -432,6 +433,14 @@ async fn test_async_userdata() -> Result<()> {
.exec_async()
.await?;
userdata.call_async_method("set_value", 24).await?;
let n: u64 = userdata.call_async_method("get_value", ()).await?;
assert_eq!(n, 24);
userdata.call_async_function("sleep", 15).await?;
#[cfg(not(any(feature = "lua51", feature = "luau")))]
assert_eq!(userdata.call_async::<_, String>(()).await?, "elapsed:24ms");
Ok(())
}

View File

@ -13,8 +13,8 @@ use std::{cell::RefCell, rc::Rc};
use std::sync::atomic::{AtomicI64, Ordering};
use mlua::{
AnyUserData, Error, ExternalError, FromLua, Function, Lua, MetaMethod, Nil, Result, String,
UserData, UserDataFields, UserDataMethods, Value,
AnyUserData, AnyUserDataExt, Error, ExternalError, FromLua, Function, Lua, MetaMethod, Nil,
Result, String, UserData, UserDataFields, UserDataMethods, Value,
};
#[test]
@ -740,3 +740,50 @@ fn test_any_userdata() -> Result<()> {
Ok(())
}
#[test]
fn test_userdata_ext() -> Result<()> {
let lua = Lua::new();
#[derive(Clone, Copy)]
struct MyUserData(u32);
impl UserData for MyUserData {
fn add_fields<'lua, F: UserDataFields<'lua, 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<'lua, M: UserDataMethods<'lua, 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);
match ud.get::<_, u32>("non-existent") {
Err(Error::RuntimeError(_)) => {}
r => panic!("expected RuntimeError, got {r:?}"),
}
match ud.set::<_, u32>("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);
Ok(())
}