mlua/
userdata_ext.rs

1use crate::error::{Error, Result};
2use crate::private::Sealed;
3use crate::userdata::{AnyUserData, MetaMethod};
4use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value};
5
6#[cfg(feature = "async")]
7use futures_util::future::{self, LocalBoxFuture};
8
9/// An extension trait for [`AnyUserData`] that provides a variety of convenient functionality.
10pub trait AnyUserDataExt<'lua>: Sealed {
11    /// Gets the value associated to `key` from the userdata, assuming it has `__index` metamethod.
12    fn get<K: IntoLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V>;
13
14    /// Sets the value associated to `key` in the userdata, assuming it has `__newindex` metamethod.
15    fn set<K: IntoLua<'lua>, V: IntoLua<'lua>>(&self, key: K, value: V) -> Result<()>;
16
17    /// Calls the userdata as a function assuming it has `__call` metamethod.
18    ///
19    /// The metamethod is called with the userdata as its first argument, followed by the passed arguments.
20    fn call<A, R>(&self, args: A) -> Result<R>
21    where
22        A: IntoLuaMulti<'lua>,
23        R: FromLuaMulti<'lua>;
24
25    /// Asynchronously calls the userdata as a function assuming it has `__call` metamethod.
26    ///
27    /// The metamethod is called with the userdata as its first argument, followed by the passed arguments.
28    #[cfg(feature = "async")]
29    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
30    fn call_async<A, R>(&self, args: A) -> LocalBoxFuture<'lua, Result<R>>
31    where
32        A: IntoLuaMulti<'lua>,
33        R: FromLuaMulti<'lua> + 'lua;
34
35    /// Calls the userdata method, assuming it has `__index` metamethod
36    /// and a function associated to `name`.
37    fn call_method<A, R>(&self, name: &str, args: A) -> Result<R>
38    where
39        A: IntoLuaMulti<'lua>,
40        R: FromLuaMulti<'lua>;
41
42    /// Gets the function associated to `key` from the table and asynchronously executes it,
43    /// passing the table itself along with `args` as function arguments and returning Future.
44    ///
45    /// Requires `feature = "async"`
46    ///
47    /// This might invoke the `__index` metamethod.
48    #[cfg(feature = "async")]
49    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
50    fn call_async_method<A, R>(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result<R>>
51    where
52        A: IntoLuaMulti<'lua>,
53        R: FromLuaMulti<'lua> + 'lua;
54
55    /// Gets the function associated to `key` from the table and executes it,
56    /// passing `args` as function arguments.
57    ///
58    /// This is a shortcut for
59    /// `table.get::<_, Function>(key)?.call(args)`
60    ///
61    /// This might invoke the `__index` metamethod.
62    fn call_function<A, R>(&self, name: &str, args: A) -> Result<R>
63    where
64        A: IntoLuaMulti<'lua>,
65        R: FromLuaMulti<'lua>;
66
67    /// Gets the function associated to `key` from the table and asynchronously executes it,
68    /// passing `args` as function arguments and returning Future.
69    ///
70    /// Requires `feature = "async"`
71    ///
72    /// This might invoke the `__index` metamethod.
73    #[cfg(feature = "async")]
74    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
75    fn call_async_function<A, R>(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result<R>>
76    where
77        A: IntoLuaMulti<'lua>,
78        R: FromLuaMulti<'lua> + 'lua;
79}
80
81impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> {
82    fn get<K: IntoLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> {
83        let metatable = self.get_metatable()?;
84        match metatable.get::<Value>(MetaMethod::Index)? {
85            Value::Table(table) => table.raw_get(key),
86            Value::Function(func) => func.call((self.clone(), key)),
87            _ => Err(Error::runtime("attempt to index a userdata value")),
88        }
89    }
90
91    fn set<K: IntoLua<'lua>, V: IntoLua<'lua>>(&self, key: K, value: V) -> Result<()> {
92        let metatable = self.get_metatable()?;
93        match metatable.get::<Value>(MetaMethod::NewIndex)? {
94            Value::Table(table) => table.raw_set(key, value),
95            Value::Function(func) => func.call((self.clone(), key, value)),
96            _ => Err(Error::runtime("attempt to index a userdata value")),
97        }
98    }
99
100    fn call<A, R>(&self, args: A) -> Result<R>
101    where
102        A: IntoLuaMulti<'lua>,
103        R: FromLuaMulti<'lua>,
104    {
105        let metatable = self.get_metatable()?;
106        match metatable.get::<Value>(MetaMethod::Call)? {
107            Value::Function(func) => func.call((self.clone(), args)),
108            _ => Err(Error::runtime("attempt to call a userdata value")),
109        }
110    }
111
112    #[cfg(feature = "async")]
113    fn call_async<A, R>(&self, args: A) -> LocalBoxFuture<'lua, Result<R>>
114    where
115        A: IntoLuaMulti<'lua>,
116        R: FromLuaMulti<'lua> + 'lua,
117    {
118        let metatable = match self.get_metatable() {
119            Ok(metatable) => metatable,
120            Err(err) => return Box::pin(future::err(err)),
121        };
122        match metatable.get::<Value>(MetaMethod::Call) {
123            Ok(Value::Function(func)) => {
124                let mut args = match args.into_lua_multi(self.0.lua) {
125                    Ok(args) => args,
126                    Err(e) => return Box::pin(future::err(e)),
127                };
128                args.push_front(Value::UserData(self.clone()));
129                Box::pin(async move { func.call_async(args).await })
130            }
131            Ok(_) => Box::pin(future::err(Error::runtime(
132                "attempt to call a userdata value",
133            ))),
134            Err(err) => Box::pin(future::err(err)),
135        }
136    }
137
138    fn call_method<A, R>(&self, name: &str, args: A) -> Result<R>
139    where
140        A: IntoLuaMulti<'lua>,
141        R: FromLuaMulti<'lua>,
142    {
143        self.call_function(name, (self.clone(), args))
144    }
145
146    #[cfg(feature = "async")]
147    fn call_async_method<A, R>(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result<R>>
148    where
149        A: IntoLuaMulti<'lua>,
150        R: FromLuaMulti<'lua> + 'lua,
151    {
152        self.call_async_function(name, (self.clone(), args))
153    }
154
155    fn call_function<A, R>(&self, name: &str, args: A) -> Result<R>
156    where
157        A: IntoLuaMulti<'lua>,
158        R: FromLuaMulti<'lua>,
159    {
160        match self.get(name)? {
161            Value::Function(func) => func.call(args),
162            val => {
163                let msg = format!("attempt to call a {} value", val.type_name());
164                Err(Error::runtime(msg))
165            }
166        }
167    }
168
169    #[cfg(feature = "async")]
170    fn call_async_function<A, R>(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result<R>>
171    where
172        A: IntoLuaMulti<'lua>,
173        R: FromLuaMulti<'lua> + 'lua,
174    {
175        match self.get(name) {
176            Ok(Value::Function(func)) => {
177                let args = match args.into_lua_multi(self.0.lua) {
178                    Ok(args) => args,
179                    Err(e) => return Box::pin(future::err(e)),
180                };
181                Box::pin(async move { func.call_async(args).await })
182            }
183            Ok(val) => {
184                let msg = format!("attempt to call a {} value", val.type_name());
185                Box::pin(future::err(Error::runtime(msg)))
186            }
187            Err(err) => Box::pin(future::err(err)),
188        }
189    }
190}