diff --git a/src/lib.rs b/src/lib.rs index 5f5985b..b6a256b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,7 @@ mod stdlib; mod string; mod table; mod thread; +mod traits; mod types; mod userdata; mod util; @@ -112,12 +113,13 @@ pub use crate::state::{GCMode, Lua, LuaOptions}; // pub use crate::scope::Scope; pub use crate::stdlib::StdLib; pub use crate::string::{BorrowedBytes, BorrowedStr, String}; -pub use crate::table::{Table, TableExt, TablePairs, TableSequence}; +pub use crate::table::{Table, TablePairs, TableSequence}; pub use crate::thread::{Thread, ThreadStatus}; +pub use crate::traits::ObjectLike; pub use crate::types::{AppDataRef, AppDataRefMut, Integer, LightUserData, MaybeSend, Number, RegistryKey}; pub use crate::userdata::{ - AnyUserData, AnyUserDataExt, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, - UserDataRef, UserDataRefMut, UserDataRegistry, + AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef, + UserDataRefMut, UserDataRegistry, }; pub use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value}; diff --git a/src/prelude.rs b/src/prelude.rs index b616865..faf8f7c 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,17 +2,17 @@ #[doc(no_inline)] pub use crate::{ - AnyUserData as LuaAnyUserData, AnyUserDataExt as LuaAnyUserDataExt, Chunk as LuaChunk, Error as LuaError, - ErrorContext as LuaErrorContext, ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, - FromLua, FromLuaMulti, Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, - Integer as LuaInteger, IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaOptions, - MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, + AnyUserData as LuaAnyUserData, Chunk as LuaChunk, Error as LuaError, ErrorContext as LuaErrorContext, + ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti, + Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, Integer as LuaInteger, + IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaOptions, MetaMethod as LuaMetaMethod, + MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, String as LuaString, - Table as LuaTable, TableExt as LuaTableExt, TablePairs as LuaTablePairs, - TableSequence as LuaTableSequence, Thread as LuaThread, ThreadStatus as LuaThreadStatus, - UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, - UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, - UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, + Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence, Thread as LuaThread, + ThreadStatus as LuaThreadStatus, UserData as LuaUserData, UserDataFields as LuaUserDataFields, + UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, + UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut, + UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, }; #[cfg(not(feature = "luau"))] diff --git a/src/state/raw.rs b/src/state/raw.rs index c10ccc8..ab05642 100644 --- a/src/state/raw.rs +++ b/src/state/raw.rs @@ -489,7 +489,7 @@ impl RawLua { #[cfg(feature = "luau")] ffi::lua_resetthread(thread_state); extra.thread_pool.push(thread.0.index); - thread.0.index = 0; // Prevent reference from being dropped + thread.0.drop = false; // Prevent thread from being garbage collected return true; } false diff --git a/src/table.rs b/src/table.rs index 80c6228..2e0d999 100644 --- a/src/table.rs +++ b/src/table.rs @@ -2,6 +2,7 @@ use std::collections::HashSet; use std::fmt; use std::marker::PhantomData; use std::os::raw::c_void; +use std::string::String as StdString; #[cfg(feature = "serialize")] use { @@ -12,14 +13,14 @@ use { use crate::error::{Error, Result}; use crate::function::Function; -use crate::private::Sealed; use crate::state::{LuaGuard, RawLua}; +use crate::traits::ObjectLike; use crate::types::{Integer, ValueRef}; use crate::util::{assert_stack, check_stack, StackGuard}; use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Nil, Value}; #[cfg(feature = "async")] -use std::future::Future; +use futures_util::future::{self, Either, Future}; /// Handle to an internal Lua table. #[derive(Clone)] @@ -60,11 +61,15 @@ impl Table { /// /// [`raw_set`]: #method.raw_set pub fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { - // Fast track + // Fast track (skip protected call) if !self.has_metatable() { return self.raw_set(key, value); } + self.set_protected(key, value) + } + + pub(crate) fn set_protected(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { @@ -103,11 +108,15 @@ impl Table { /// /// [`raw_get`]: #method.raw_get pub fn get(&self, key: impl IntoLua) -> Result { - // Fast track + // Fast track (skip protected call) if !self.has_metatable() { return self.raw_get(key); } + self.get_protected(key) + } + + pub(crate) fn get_protected(&self, key: impl IntoLua) -> Result { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { @@ -133,7 +142,7 @@ impl Table { /// /// This might invoke the `__len` and `__newindex` metamethods. pub fn push(&self, value: impl IntoLua) -> Result<()> { - // Fast track + // Fast track (skip protected call) if !self.has_metatable() { return self.raw_push(value); } @@ -158,7 +167,7 @@ impl Table { /// /// This might invoke the `__len` and `__newindex` metamethods. pub fn pop(&self) -> Result { - // Fast track + // Fast track (skip protected call) if !self.has_metatable() { return self.raw_pop(); } @@ -433,7 +442,7 @@ impl Table { /// /// [`raw_len`]: #method.raw_len pub fn len(&self) -> Result { - // Fast track + // Fast track (skip protected call) if !self.has_metatable() { return Ok(self.raw_len() as Integer); } @@ -858,107 +867,41 @@ where } } -/// An extension trait for `Table`s that provides a variety of convenient functionality. -pub trait TableExt: Sealed { - /// Calls the table as function assuming it has `__call` metamethod. - /// - /// The metamethod is called with the table as its first argument, followed by the passed - /// arguments. - fn call(&self, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti; +impl ObjectLike for Table { + #[inline] + fn get(&self, key: impl IntoLua) -> Result { + self.get(key) + } - /// Asynchronously calls the table as function assuming it has `__call` metamethod. - /// - /// The metamethod is called with the table as its first argument, followed by the passed - /// arguments. - #[cfg(feature = "async")] - #[cfg_attr(docsrs, doc(cfg(feature = "async")))] - fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> - where - R: FromLuaMulti; + #[inline] + fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { + self.set(key, value) + } - /// Gets the function associated to `key` from the table and executes it, - /// passing the table itself along with `args` as function arguments. - /// - /// This is a shortcut for - /// `table.get::(key)?.call((table.clone(), arg1, ..., argN))` - /// - /// This might invoke the `__index` metamethod. - fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti; - - /// Gets the function associated to `key` from the table and executes it, - /// passing `args` as function arguments. - /// - /// This is a shortcut for - /// `table.get::(key)?.call(args)` - /// - /// This might invoke the `__index` metamethod. - fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti; - - /// 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(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> - where - R: FromLuaMulti; - - /// 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(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> - where - R: FromLuaMulti; -} - -impl TableExt for Table { + #[inline] fn call(&self, args: impl IntoLuaMulti) -> Result where R: FromLuaMulti, { // Convert table to a function and call via pcall that respects the `__call` metamethod. - Function(self.0.clone()).call(args) + Function(self.0.copy()).call(args) } #[cfg(feature = "async")] + #[inline] fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> where R: FromLuaMulti, { - let lua = self.0.lua.lock(); - let args = args.into_lua_multi(lua.lua()); - async move { - let func = Function(self.0.clone()); - func.call_async(args?).await - } + Function(self.0.copy()).call_async(args) } + #[inline] fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result where R: FromLuaMulti, { - self.get::(name)?.call((self, args)) - } - - fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti, - { - self.get::(name)?.call(args) + self.call_function(name, (self, args)) } #[cfg(feature = "async")] @@ -969,18 +912,37 @@ impl TableExt for Table { self.call_async_function(name, (self, args)) } + #[inline] + fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result { + match self.get(name)? { + Value::Function(func) => func.call(args), + val => { + let msg = format!("attempt to call a {} value (function '{name}')", val.type_name()); + Err(Error::runtime(msg)) + } + } + } + #[cfg(feature = "async")] + #[inline] fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> where R: FromLuaMulti, { - let lua = self.0.lua.lock(); - let args = args.into_lua_multi(lua.lua()); - async move { - let func = self.get::(name)?; - func.call_async(args?).await + match self.get(name) { + Ok(Value::Function(func)) => Either::Left(func.call_async(args)), + Ok(val) => { + let msg = format!("attempt to call a {} value (function '{name}')", val.type_name()); + Either::Right(future::ready(Err(Error::RuntimeError(msg)))) + } + Err(err) => Either::Right(future::ready(Err(err))), } } + + #[inline] + fn to_string(&self) -> Result { + Value::Table(self.clone()).to_string() + } } /// A wrapped [`Table`] with customized serialization behavior. @@ -1050,7 +1012,7 @@ impl<'a> Serialize for SerializableTable<'a> { seq.serialize_element(&SerializableValue::new(&value, options, Some(visited))) .map_err(|err| { serialize_err = Some(err); - Error::SerializeError(String::new()) + Error::SerializeError(StdString::new()) }) }); convert_result(res, serialize_err)?; @@ -1075,7 +1037,7 @@ impl<'a> Serialize for SerializableTable<'a> { ) .map_err(|err| { serialize_err = Some(err); - Error::SerializeError(String::new()) + Error::SerializeError(StdString::new()) }) }; diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 0000000..eddbb5f --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,78 @@ +use std::string::String as StdString; + +use crate::error::Result; +use crate::private::Sealed; +use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; + +#[cfg(feature = "async")] +use std::future::Future; + +/// A trait for types that can be used as Lua objects (usually table and userdata). +pub trait ObjectLike: Sealed { + /// Gets the value associated to `key` from the object, assuming it has `__index` metamethod. + fn get(&self, key: impl IntoLua) -> Result; + + /// Sets the value associated to `key` in the object, assuming it has `__newindex` metamethod. + fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()>; + + /// Calls the object as a function assuming it has `__call` metamethod. + /// + /// The metamethod is called with the object as its first argument, followed by the passed + /// arguments. + fn call(&self, args: impl IntoLuaMulti) -> Result + where + R: FromLuaMulti; + + /// Asynchronously calls the object as a function assuming it has `__call` metamethod. + /// + /// The metamethod is called with the object as its first argument, followed by the passed + /// arguments. + #[cfg(feature = "async")] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> + where + R: FromLuaMulti; + + /// Gets the function associated to `key` from the object and calls it, + /// passing the object itself along with `args` as function arguments. + fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result + where + R: FromLuaMulti; + + /// Gets the function associated to `key` from the object and asynchronously calls it, + /// passing the object itself along with `args` as function arguments. + /// + /// Requires `feature = "async"` + /// + /// This might invoke the `__index` metamethod. + #[cfg(feature = "async")] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> + where + R: FromLuaMulti; + + /// Gets the function associated to `key` from the object and calls it, + /// passing `args` as function arguments. + /// + /// This might invoke the `__index` metamethod. + fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result + where + R: FromLuaMulti; + + /// Gets the function associated to `key` from the object and asynchronously calls it, + /// passing `args` as function arguments. + /// + /// Requires `feature = "async"` + /// + /// This might invoke the `__index` metamethod. + #[cfg(feature = "async")] + #[cfg_attr(docsrs, doc(cfg(feature = "async")))] + fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> + where + R: FromLuaMulti; + + /// Converts the object to a string in a human-readable format. + /// + /// This might invoke the `__tostring` metamethod. + fn to_string(&self) -> Result; +} diff --git a/src/types.rs b/src/types.rs index 14f30aa..3f0d31d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -115,6 +115,7 @@ pub(crate) struct DestructedUserdata; pub(crate) struct ValueRef { pub(crate) lua: WeakLua, pub(crate) index: c_int, + pub(crate) drop: bool, } impl ValueRef { @@ -123,6 +124,7 @@ impl ValueRef { ValueRef { lua: lua.weak().clone(), index, + drop: true, } } @@ -131,6 +133,16 @@ impl ValueRef { let lua = self.lua.lock(); unsafe { ffi::lua_topointer(lua.ref_thread(), self.index) } } + + /// Returns a copy of the value, which is valid as long as the original value is held. + #[inline] + pub(crate) fn copy(&self) -> Self { + ValueRef { + lua: self.lua.clone(), + index: self.index, + drop: false, + } + } } impl fmt::Debug for ValueRef { @@ -147,7 +159,7 @@ impl Clone for ValueRef { impl Drop for ValueRef { fn drop(&mut self) { - if self.index > 0 { + if self.drop { if let Some(lua) = self.lua.try_lock() { unsafe { lua.drop_ref(self) }; } diff --git a/src/userdata.rs b/src/userdata.rs index 9424e27..f28b96a 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -26,7 +26,6 @@ use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; // Re-export for convenience pub(crate) use cell::UserDataVariant; pub use cell::{UserDataRef, UserDataRefMut}; -pub use ext::AnyUserDataExt; pub(crate) use registry::UserDataProxy; pub use registry::UserDataRegistry; @@ -1174,8 +1173,8 @@ where } mod cell; -mod ext; mod lock; +mod object; mod registry; #[cfg(test)] diff --git a/src/userdata/ext.rs b/src/userdata/ext.rs deleted file mode 100644 index b258877..0000000 --- a/src/userdata/ext.rs +++ /dev/null @@ -1,168 +0,0 @@ -use crate::error::{Error, Result}; -use crate::private::Sealed; -use crate::userdata::{AnyUserData, MetaMethod}; -use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; - -#[cfg(feature = "async")] -use std::future::Future; - -/// An extension trait for [`AnyUserData`] that provides a variety of convenient functionality. -pub trait AnyUserDataExt: Sealed { - /// Gets the value associated to `key` from the userdata, assuming it has `__index` metamethod. - fn get(&self, key: impl IntoLua) -> Result; - - /// Sets the value associated to `key` in the userdata, assuming it has `__newindex` metamethod. - fn set(&self, key: impl IntoLua, value: impl IntoLua) -> 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(&self, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti; - - /// 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(&self, args: impl IntoLuaMulti) -> impl Future> - where - R: FromLuaMulti; - - /// Calls the userdata method, assuming it has `__index` metamethod - /// and a function associated to `name`. - fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti; - - /// 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(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> - where - R: FromLuaMulti; - - /// Gets the function associated to `key` from the table and executes it, - /// passing `args` as function arguments. - /// - /// This is a shortcut for - /// `table.get::(key)?.call(args)` - /// - /// This might invoke the `__index` metamethod. - fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti; - - /// 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(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> - where - R: FromLuaMulti; -} - -impl AnyUserDataExt for AnyUserData { - fn get(&self, key: impl IntoLua) -> Result { - let metatable = self.get_metatable()?; - match metatable.get::(MetaMethod::Index)? { - Value::Table(table) => table.raw_get(key), - Value::Function(func) => func.call((self, key)), - _ => Err(Error::runtime("attempt to index a userdata value")), - } - } - - fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { - let metatable = self.get_metatable()?; - match metatable.get::(MetaMethod::NewIndex)? { - Value::Table(table) => table.raw_set(key, value), - Value::Function(func) => func.call((self, key, value)), - _ => Err(Error::runtime("attempt to index a userdata value")), - } - } - - fn call(&self, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti, - { - let metatable = self.get_metatable()?; - match metatable.get::(MetaMethod::Call)? { - Value::Function(func) => func.call((self, args)), - _ => Err(Error::runtime("attempt to call a userdata value")), - } - } - - #[cfg(feature = "async")] - fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> - where - R: FromLuaMulti, - { - let lua = self.0.lua.lock(); - let args = (self, args).into_lua_multi(lua.lua()); - async move { - let metatable = self.get_metatable()?; - match metatable.get::(MetaMethod::Call)? { - Value::Function(func) => func.call_async(args?).await, - _ => Err(Error::runtime("attempt to call a userdata value")), - } - } - } - - fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti, - { - self.call_function(name, (self, args)) - } - - #[cfg(feature = "async")] - fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> - where - R: FromLuaMulti, - { - self.call_async_function(name, (self, args)) - } - - fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result - where - R: FromLuaMulti, - { - match self.get(name)? { - Value::Function(func) => func.call(args), - val => { - let msg = format!("attempt to call a {} value", val.type_name()); - Err(Error::runtime(msg)) - } - } - } - - #[cfg(feature = "async")] - fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> - where - R: FromLuaMulti, - { - let lua = self.0.lua.lock(); - let args = args.into_lua_multi(lua.lua()); - async move { - match self.get::(name)? { - Value::Function(func) => func.call_async(args?).await, - val => { - let msg = format!("attempt to call a {} value", val.type_name()); - Err(Error::runtime(msg)) - } - } - } - } -} diff --git a/src/userdata/object.rs b/src/userdata/object.rs new file mode 100644 index 0000000..e9582e5 --- /dev/null +++ b/src/userdata/object.rs @@ -0,0 +1,93 @@ +use std::string::String as StdString; + +use crate::error::{Error, Result}; +use crate::table::Table; +use crate::traits::ObjectLike; +use crate::userdata::AnyUserData; +use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value}; +use crate::Function; + +#[cfg(feature = "async")] +use futures_util::future::{self, Either, Future}; + +impl ObjectLike for AnyUserData { + #[inline] + fn get(&self, key: impl IntoLua) -> Result { + // `lua_gettable` method used under the hood can work with any Lua value + // that has `__index` metamethod + Table(self.0.copy()).get_protected(key) + } + + #[inline] + fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { + // `lua_settable` method used under the hood can work with any Lua value + // that has `__newindex` metamethod + Table(self.0.copy()).set_protected(key, value) + } + + #[inline] + fn call(&self, args: impl IntoLuaMulti) -> Result + where + R: FromLuaMulti, + { + Function(self.0.copy()).call(args) + } + + #[cfg(feature = "async")] + #[inline] + fn call_async(&self, args: impl IntoLuaMulti) -> impl Future> + where + R: FromLuaMulti, + { + Function(self.0.copy()).call_async(args) + } + + #[inline] + fn call_method(&self, name: &str, args: impl IntoLuaMulti) -> Result + where + R: FromLuaMulti, + { + self.call_function(name, (self, args)) + } + + #[cfg(feature = "async")] + fn call_async_method(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> + where + R: FromLuaMulti, + { + self.call_async_function(name, (self, args)) + } + + fn call_function(&self, name: &str, args: impl IntoLuaMulti) -> Result + where + R: FromLuaMulti, + { + match self.get(name)? { + Value::Function(func) => func.call(args), + val => { + let msg = format!("attempt to call a {} value (function '{name}')", val.type_name()); + Err(Error::RuntimeError(msg)) + } + } + } + + #[cfg(feature = "async")] + fn call_async_function(&self, name: &str, args: impl IntoLuaMulti) -> impl Future> + where + R: FromLuaMulti, + { + match self.get(name) { + Ok(Value::Function(func)) => Either::Left(func.call_async(args)), + Ok(val) => { + let msg = format!("attempt to call a {} value (function '{name}')", val.type_name()); + Either::Right(future::ready(Err(Error::RuntimeError(msg)))) + } + Err(err) => Either::Right(future::ready(Err(err))), + } + } + + #[inline] + fn to_string(&self) -> Result { + Value::UserData(self.clone()).to_string() + } +} diff --git a/tests/async.rs b/tests/async.rs index 887965d..29cee04 100644 --- a/tests/async.rs +++ b/tests/async.rs @@ -7,7 +7,7 @@ use futures_util::stream::TryStreamExt; use tokio::sync::Mutex; use mlua::{ - AnyUserDataExt, Error, Function, Lua, LuaOptions, MultiValue, Result, StdLib, Table, TableExt, UserData, + Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, UserData, UserDataMethods, Value, }; @@ -315,7 +315,7 @@ fn test_async_thread_capture() -> Result<()> { } #[tokio::test] -async fn test_async_table() -> Result<()> { +async fn test_async_table_object_like() -> Result<()> { let options = LuaOptions::new().thread_pool_size(4); let lua = Lua::new_with(StdLib::ALL_SAFE, options)?; @@ -334,19 +334,20 @@ async fn test_async_table() -> Result<()> { })?; table.set("set_value", set_value)?; - let sleep = lua.create_async_function(|_, n| async move { - sleep_ms(n).await; - Ok(format!("elapsed:{}ms", n)) - })?; - table.set("sleep", sleep)?; - assert_eq!(table.call_async_method::("get_value", ()).await?, 10); table.call_async_method::<()>("set_value", 15).await?; assert_eq!(table.call_async_method::("get_value", ()).await?, 15); - assert_eq!( - table.call_async_function::("sleep", 7).await?, - "elapsed:7ms" - ); + + let metatable = lua.create_table()?; + metatable.set( + "__call", + lua.create_async_function(|_, table: Table| async move { + sleep_ms(10).await; + table.get::("val") + })?, + )?; + table.set_metatable(Some(metatable)); + assert_eq!(table.call_async::(()).await.unwrap(), 15); Ok(()) } @@ -461,6 +462,7 @@ async fn test_async_userdata() -> Result<()> { .exec_async() .await?; + // ObjectLike methods userdata.call_async_method::<()>("set_value", 24).await?; let n: u64 = userdata.call_async_method("get_value", ()).await?; assert_eq!(n, 24); diff --git a/tests/table.rs b/tests/table.rs index 30b51d9..957d5a4 100644 --- a/tests/table.rs +++ b/tests/table.rs @@ -1,4 +1,4 @@ -use mlua::{Error, Lua, Nil, Result, Table, TableExt, Value}; +use mlua::{Error, Lua, Nil, ObjectLike, Result, Table, Value}; #[test] fn test_globals_set_get() -> Result<()> { @@ -399,7 +399,7 @@ fn test_table_error() -> Result<()> { } #[test] -fn test_table_call() -> Result<()> { +fn test_table_object_like() -> Result<()> { let lua = Lua::new(); lua.load( @@ -408,6 +408,10 @@ fn test_table_call() -> Result<()> { setmetatable(table, { __call = function(t, key) return "call_"..t[key] + end, + + __tostring = function() + return "table object" end }) @@ -424,9 +428,19 @@ fn test_table_call() -> Result<()> { let table: Table = lua.globals().get("table")?; + ::set(&table, "c", 3)?; + assert_eq!(
::get::(&table, "c")?, 3); assert_eq!(table.call::("b")?, "call_2"); assert_eq!(table.call_function::("func", "a")?, "func_a"); assert_eq!(table.call_method::("method", "a")?, "method_1"); + assert_eq!(table.to_string()?, "table object"); + + match table.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:?}"), + } // Test calling non-callable table let table2 = lua.create_table()?; diff --git a/tests/userdata.rs b/tests/userdata.rs index 7c6ae05..e305ae3 100644 --- a/tests/userdata.rs +++ b/tests/userdata.rs @@ -6,8 +6,8 @@ use std::sync::Arc; use std::sync::atomic::{AtomicI64, Ordering}; use mlua::{ - AnyUserData, AnyUserDataExt, Error, ExternalError, Function, Lua, MetaMethod, Nil, Result, String, - UserData, UserDataFields, UserDataMethods, UserDataRef, Value, Variadic, + AnyUserData, Error, ExternalError, Function, Lua, MetaMethod, Nil, ObjectLike, Result, String, UserData, + UserDataFields, UserDataMethods, UserDataRef, Value, Variadic, }; #[test] @@ -726,7 +726,7 @@ fn test_any_userdata_wrap() -> Result<()> { } #[test] -fn test_userdata_ext() -> Result<()> { +fn test_userdata_object_like() -> Result<()> { let lua = Lua::new(); #[derive(Clone, Copy)] @@ -766,6 +766,15 @@ fn test_userdata_ext() -> Result<()> { ud.call_method::<()>("add", 2)?; assert_eq!(ud.get::("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(()) }