diff --git a/src/conversion.rs b/src/conversion.rs index 3569529..78dbc82 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -6,6 +6,7 @@ use error::*; use lua::*; use string::String; use table::Table; +use userdata::{LightUserData, UserData, AnyUserData}; impl<'lua> ToLua<'lua> for Value<'lua> { fn to_lua(self, _: &'lua Lua) -> Result> { diff --git a/src/lib.rs b/src/lib.rs index a112cc4..f07756e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,16 +51,17 @@ mod conversion; mod multi; mod string; mod table; +mod userdata; #[cfg(test)] mod tests; pub use error::{Error, Result, ExternalError, ExternalResult}; pub use lua::{Value, Nil, ToLua, FromLua, MultiValue, ToLuaMulti, FromLuaMulti, Integer, Number, - LightUserData, Function, ThreadStatus, Thread, - MetaMethod, UserDataMethods, UserData, AnyUserData, Lua}; + Function, ThreadStatus, Thread, Lua}; pub use multi::Variadic; pub use string::String; pub use table::{Table, TablePairs, TableSequence}; +pub use userdata::{LightUserData, MetaMethod, UserDataMethods, UserData, AnyUserData}; pub mod prelude; diff --git a/src/lua.rs b/src/lua.rs index 15223ef..a6b8074 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -1,7 +1,7 @@ use std::{fmt, ptr, str}; use std::ops::{Deref, DerefMut}; use std::iter::FromIterator; -use std::cell::{RefCell, Ref, RefMut}; +use std::cell::RefCell; use std::ffi::CString; use std::any::TypeId; use std::marker::PhantomData; @@ -9,7 +9,6 @@ use std::collections::{HashMap, VecDeque}; use std::collections::hash_map::Entry as HashMapEntry; use std::os::raw::{c_char, c_int, c_void}; use std::process; -use std::string::String as StdString; use libc; @@ -18,6 +17,7 @@ use error::*; use util::*; use string::String; use table::Table; +use userdata::{LightUserData, UserDataMethods, MetaMethod, UserData, AnyUserData}; /// A dynamically typed Lua value. #[derive(Debug, Clone)] @@ -145,7 +145,10 @@ pub trait FromLuaMulti<'lua>: Sized { fn from_lua_multi(values: MultiValue<'lua>, lua: &'lua Lua) -> Result; } -type Callback<'lua> = Box) -> Result> + 'lua>; +pub(crate) type Callback<'lua> = Box< + FnMut(&'lua Lua, MultiValue<'lua>) -> Result> + + 'lua, +>; pub(crate) struct LuaRef<'lua> { pub lua: &'lua Lua, @@ -180,10 +183,6 @@ pub type Integer = ffi::lua_Integer; /// Type of Lua floating point numbers. pub type Number = ffi::lua_Number; -/// A "light" userdata value. Equivalent to an unmanaged raw pointer. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct LightUserData(pub *mut c_void); - /// Handle to an internal Lua function. #[derive(Clone, Debug)] pub struct Function<'lua>(LuaRef<'lua>); @@ -468,391 +467,6 @@ impl<'lua> Thread<'lua> { } } -/// Kinds of metamethods that can be overridden. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum MetaMethod { - /// The `+` operator. - Add, - /// The `-` operator. - Sub, - /// The `*` operator. - Mul, - /// The `/` operator. - Div, - /// The `%` operator. - Mod, - /// The `^` operator. - Pow, - /// The unary minus (`-`) operator. - Unm, - /// The floor division (//) operator. - IDiv, - /// The bitwise AND (&) operator. - BAnd, - /// The bitwise OR (|) operator. - BOr, - /// The bitwise XOR (binary ~) operator. - BXor, - /// The bitwise NOT (unary ~) operator. - BNot, - /// The bitwise left shift (<<) operator. - Shl, - /// The bitwise right shift (>>) operator. - Shr, - /// The string concatenation operator `..`. - Concat, - /// The length operator `#`. - Len, - /// The `==` operator. - Eq, - /// The `<` operator. - Lt, - /// The `<=` operator. - Le, - /// Index access `obj[key]`. - Index, - /// Index write access `obj[key] = value`. - NewIndex, - /// The call "operator" `obj(arg1, args2, ...)`. - Call, - /// tostring(ud) will call this if it exists - ToString, -} - -/// Method registry for [`UserData`] implementors. -/// -/// [`UserData`]: trait.UserData.html -pub struct UserDataMethods<'lua, T> { - methods: HashMap>, - meta_methods: HashMap>, - _type: PhantomData, -} - -impl<'lua, T: UserData> UserDataMethods<'lua, T> { - /// Add a method which accepts a `&T` as the first parameter. - /// - /// Regular methods are implemented by overriding the `__index` metamethod and returning the - /// accessed method. This allows them to be used with the expected `userdata:method()` syntax. - /// - /// If `add_meta_method` is used to override the `__index` metamethod, this approach will fall - /// back to the user-provided metamethod if no regular method was found. - pub fn add_method(&mut self, name: &str, method: M) - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - M: 'static + for<'a> FnMut(&'lua Lua, &'a T, A) -> Result, - { - self.methods.insert( - name.to_owned(), - Self::box_method(method), - ); - } - - /// Add a regular method which accepts a `&mut T` as the first parameter. - /// - /// Refer to [`add_method`] for more information about the implementation. - /// - /// [`add_method`]: #method.add_method - pub fn add_method_mut(&mut self, name: &str, method: M) - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - M: 'static + for<'a> FnMut(&'lua Lua, &'a mut T, A) -> Result, - { - self.methods.insert( - name.to_owned(), - Self::box_method_mut(method), - ); - } - - /// Add a regular method as a function which accepts generic arguments, the first argument will - /// always be a `UserData` of type T. - /// - /// Prefer to use [`add_method`] or [`add_method_mut`] as they are easier to use. - /// - /// [`add_method`]: #method.add_method - /// [`add_method_mut`]: #method.add_method_mut - pub fn add_function(&mut self, name: &str, function: F) - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - F: 'static + FnMut(&'lua Lua, A) -> Result, - { - self.methods.insert( - name.to_owned(), - Self::box_function(function), - ); - } - - /// Add a metamethod which accepts a `&T` as the first parameter. - /// - /// # Note - /// - /// This can cause an error with certain binary metamethods that can trigger if only the right - /// side has a metatable. To prevent this, use [`add_meta_function`]. - /// - /// [`add_meta_function`]: #method.add_meta_function - pub fn add_meta_method(&mut self, meta: MetaMethod, method: M) - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - M: 'static + for<'a> FnMut(&'lua Lua, &'a T, A) -> Result, - { - self.meta_methods.insert(meta, Self::box_method(method)); - } - - /// Add a metamethod as a function which accepts a `&mut T` as the first parameter. - /// - /// # Note - /// - /// This can cause an error with certain binary metamethods that can trigger if only the right - /// side has a metatable. To prevent this, use [`add_meta_function`]. - /// - /// [`add_meta_function`]: #method.add_meta_function - pub fn add_meta_method_mut(&mut self, meta: MetaMethod, method: M) - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - M: 'static + for<'a> FnMut(&'lua Lua, &'a mut T, A) -> Result, - { - self.meta_methods.insert(meta, Self::box_method_mut(method)); - } - - /// Add a metamethod which accepts generic arguments. - /// - /// Metamethods for binary operators can be triggered if either the left or right argument to - /// the binary operator has a metatable, so the first argument here is not necessarily a - /// userdata of type `T`. - pub fn add_meta_function(&mut self, meta: MetaMethod, function: F) - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - F: 'static + FnMut(&'lua Lua, A) -> Result, - { - self.meta_methods.insert(meta, Self::box_function(function)); - } - - fn box_function(mut function: F) -> Callback<'lua> - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - F: 'static + FnMut(&'lua Lua, A) -> Result, - { - Box::new(move |lua, args| { - function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi( - lua, - ) - }) - } - - fn box_method(mut method: M) -> Callback<'lua> - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - M: 'static + for<'a> FnMut(&'lua Lua, &'a T, A) -> Result, - { - Box::new(move |lua, mut args| if let Some(front) = args.pop_front() { - let userdata = AnyUserData::from_lua(front, lua)?; - let userdata = userdata.borrow::()?; - method(lua, &userdata, A::from_lua_multi(args, lua)?)? - .to_lua_multi(lua) - } else { - Err(Error::FromLuaConversionError { - from: "missing argument", - to: "userdata", - message: None, - }) - }) - } - - fn box_method_mut(mut method: M) -> Callback<'lua> - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - M: 'static + for<'a> FnMut(&'lua Lua, &'a mut T, A) -> Result, - { - Box::new(move |lua, mut args| if let Some(front) = args.pop_front() { - let userdata = AnyUserData::from_lua(front, lua)?; - let mut userdata = userdata.borrow_mut::()?; - method(lua, &mut userdata, A::from_lua_multi(args, lua)?)? - .to_lua_multi(lua) - } else { - Err(Error::FromLuaConversionError { - from: "missing argument", - to: "userdata", - message: None, - }) - }) - } -} - -/// Trait for custom userdata types. -/// -/// By implementing this trait, a struct becomes eligible for use inside Lua code. Implementations -/// of [`ToLua`] and [`FromLua`] are automatically provided. -/// -/// # Examples -/// -/// ``` -/// # extern crate rlua; -/// # use rlua::{Lua, UserData, Result}; -/// # fn try_main() -> Result<()> { -/// struct MyUserData(i32); -/// -/// impl UserData for MyUserData {} -/// -/// let lua = Lua::new(); -/// -/// // `MyUserData` now implements `ToLua`: -/// lua.globals().set("myobject", MyUserData(123))?; -/// -/// lua.exec::<()>("assert(type(myobject) == 'userdata')", None)?; -/// # Ok(()) -/// # } -/// # fn main() { -/// # try_main().unwrap(); -/// # } -/// ``` -/// -/// Custom methods and operators can be provided by implementing `add_methods` (refer to -/// [`UserDataMethods`] for more information): -/// -/// ``` -/// # extern crate rlua; -/// # use rlua::{Lua, MetaMethod, UserData, UserDataMethods, Result}; -/// # fn try_main() -> Result<()> { -/// struct MyUserData(i32); -/// -/// impl UserData for MyUserData { -/// fn add_methods(methods: &mut UserDataMethods) { -/// methods.add_method("get", |_, this, _: ()| { -/// Ok(this.0) -/// }); -/// -/// methods.add_method_mut("add", |_, this, value: i32| { -/// this.0 += value; -/// Ok(()) -/// }); -/// -/// methods.add_meta_method(MetaMethod::Add, |_, this, value: i32| { -/// Ok(this.0 + value) -/// }); -/// } -/// } -/// -/// let lua = Lua::new(); -/// -/// lua.globals().set("myobject", MyUserData(123))?; -/// -/// lua.exec::<()>(r#" -/// assert(myobject:get() == 123) -/// myobject:add(7) -/// assert(myobject:get() == 130) -/// assert(myobject + 10 == 140) -/// "#, None)?; -/// # Ok(()) -/// # } -/// # fn main() { -/// # try_main().unwrap(); -/// # } -/// ``` -/// -/// [`ToLua`]: trait.ToLua.html -/// [`FromLua`]: trait.FromLua.html -/// [`UserDataMethods`]: struct.UserDataMethods.html -pub trait UserData: 'static + Sized { - /// Adds custom methods and operators specific to this userdata. - fn add_methods(_methods: &mut UserDataMethods) {} -} - -/// Handle to an internal Lua userdata for any type that implements [`UserData`]. -/// -/// Similar to `std::any::Any`, this provides an interface for dynamic type checking via the [`is`] -/// and [`borrow`] methods. -/// -/// Internally, instances are stored in a `RefCell`, to best match the mutable semantics of the Lua -/// language. -/// -/// # Note -/// -/// This API should only be used when necessary. Implementing [`UserData`] already allows defining -/// methods which check the type and acquire a borrow behind the scenes. -/// -/// [`UserData`]: trait.UserData.html -/// [`is`]: #method.is -/// [`borrow`]: #method.borrow -#[derive(Clone, Debug)] -pub struct AnyUserData<'lua>(LuaRef<'lua>); - -impl<'lua> AnyUserData<'lua> { - /// Checks whether the type of this userdata is `T`. - pub fn is(&self) -> bool { - self.inspect(|_: &RefCell| ()).is_some() - } - - /// Borrow this userdata immutably if it is of type `T`. - /// - /// # Errors - /// - /// Returns a `UserDataBorrowError` if the userdata is already mutably borrowed. Returns a - /// `UserDataTypeMismatch` if the userdata is not of type `T`. - pub fn borrow(&self) -> Result> { - self.inspect(|cell| { - Ok(cell.try_borrow().map_err(|_| Error::UserDataBorrowError)?) - }).ok_or(Error::UserDataTypeMismatch)? - } - - /// Borrow this userdata mutably if it is of type `T`. - /// - /// # Errors - /// - /// Returns a `UserDataBorrowMutError` if the userdata is already borrowed. Returns a - /// `UserDataTypeMismatch` if the userdata is not of type `T`. - pub fn borrow_mut(&self) -> Result> { - self.inspect(|cell| { - Ok(cell.try_borrow_mut().map_err( - |_| Error::UserDataBorrowMutError, - )?) - }).ok_or(Error::UserDataTypeMismatch)? - } - - fn inspect<'a, T, R, F>(&'a self, func: F) -> Option - where - T: UserData, - F: FnOnce(&'a RefCell) -> R, - { - unsafe { - let lua = self.0.lua; - stack_guard(lua.state, 0, move || { - check_stack(lua.state, 3); - - lua.push_ref(lua.state, &self.0); - - lua_assert!( - lua.state, - ffi::lua_getmetatable(lua.state, -1) != 0, - "AnyUserData missing metatable" - ); - - ffi::lua_rawgeti( - lua.state, - ffi::LUA_REGISTRYINDEX, - lua.userdata_metatable::() as ffi::lua_Integer, - ); - - if ffi::lua_rawequal(lua.state, -1, -2) == 0 { - ffi::lua_pop(lua.state, 3); - None - } else { - let res = func(&*get_userdata::>(lua.state, -3)); - ffi::lua_pop(lua.state, 3); - Some(res) - } - }) - } - } -} - /// Top level Lua struct which holds the Lua state itself. pub struct Lua { pub(crate) state: *mut ffi::lua_State, @@ -1499,7 +1113,7 @@ impl Lua { } } - unsafe fn userdata_metatable(&self) -> c_int { + pub(crate) unsafe fn userdata_metatable(&self) -> c_int { // Used if both an __index metamethod is set and regular methods, checks methods table // first, then __index metamethod. unsafe extern "C" fn meta_index_impl(state: *mut ffi::lua_State) -> c_int { diff --git a/src/userdata.rs b/src/userdata.rs new file mode 100644 index 0000000..814ce15 --- /dev/null +++ b/src/userdata.rs @@ -0,0 +1,399 @@ +use std::cell::{RefCell, Ref, RefMut}; +use std::marker::PhantomData; +use std::collections::HashMap; +use std::os::raw::c_void; +use std::string::String as StdString; + +use ffi; +use error::*; +use util::*; +use lua::{FromLua, FromLuaMulti, ToLuaMulti, Callback, LuaRef, Lua}; + +/// A "light" userdata value. Equivalent to an unmanaged raw pointer. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct LightUserData(pub *mut c_void); + +/// Kinds of metamethods that can be overridden. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum MetaMethod { + /// The `+` operator. + Add, + /// The `-` operator. + Sub, + /// The `*` operator. + Mul, + /// The `/` operator. + Div, + /// The `%` operator. + Mod, + /// The `^` operator. + Pow, + /// The unary minus (`-`) operator. + Unm, + /// The floor division (//) operator. + IDiv, + /// The bitwise AND (&) operator. + BAnd, + /// The bitwise OR (|) operator. + BOr, + /// The bitwise XOR (binary ~) operator. + BXor, + /// The bitwise NOT (unary ~) operator. + BNot, + /// The bitwise left shift (<<) operator. + Shl, + /// The bitwise right shift (>>) operator. + Shr, + /// The string concatenation operator `..`. + Concat, + /// The length operator `#`. + Len, + /// The `==` operator. + Eq, + /// The `<` operator. + Lt, + /// The `<=` operator. + Le, + /// Index access `obj[key]`. + Index, + /// Index write access `obj[key] = value`. + NewIndex, + /// The call "operator" `obj(arg1, args2, ...)`. + Call, + /// tostring(ud) will call this if it exists + ToString, +} + +/// Method registry for [`UserData`] implementors. +/// +/// [`UserData`]: trait.UserData.html +pub struct UserDataMethods<'lua, T> { + pub(crate) methods: HashMap>, + pub(crate) meta_methods: HashMap>, + pub(crate) _type: PhantomData, +} + +impl<'lua, T: UserData> UserDataMethods<'lua, T> { + /// Add a method which accepts a `&T` as the first parameter. + /// + /// Regular methods are implemented by overriding the `__index` metamethod and returning the + /// accessed method. This allows them to be used with the expected `userdata:method()` syntax. + /// + /// If `add_meta_method` is used to override the `__index` metamethod, this approach will fall + /// back to the user-provided metamethod if no regular method was found. + pub fn add_method(&mut self, name: &str, method: M) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + for<'a> FnMut(&'lua Lua, &'a T, A) -> Result, + { + self.methods.insert( + name.to_owned(), + Self::box_method(method), + ); + } + + /// Add a regular method which accepts a `&mut T` as the first parameter. + /// + /// Refer to [`add_method`] for more information about the implementation. + /// + /// [`add_method`]: #method.add_method + pub fn add_method_mut(&mut self, name: &str, method: M) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + for<'a> FnMut(&'lua Lua, &'a mut T, A) -> Result, + { + self.methods.insert( + name.to_owned(), + Self::box_method_mut(method), + ); + } + + /// Add a regular method as a function which accepts generic arguments, the first argument will + /// always be a `UserData` of type T. + /// + /// Prefer to use [`add_method`] or [`add_method_mut`] as they are easier to use. + /// + /// [`add_method`]: #method.add_method + /// [`add_method_mut`]: #method.add_method_mut + pub fn add_function(&mut self, name: &str, function: F) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + FnMut(&'lua Lua, A) -> Result, + { + self.methods.insert( + name.to_owned(), + Self::box_function(function), + ); + } + + /// Add a metamethod which accepts a `&T` as the first parameter. + /// + /// # Note + /// + /// This can cause an error with certain binary metamethods that can trigger if only the right + /// side has a metatable. To prevent this, use [`add_meta_function`]. + /// + /// [`add_meta_function`]: #method.add_meta_function + pub fn add_meta_method(&mut self, meta: MetaMethod, method: M) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + for<'a> FnMut(&'lua Lua, &'a T, A) -> Result, + { + self.meta_methods.insert(meta, Self::box_method(method)); + } + + /// Add a metamethod as a function which accepts a `&mut T` as the first parameter. + /// + /// # Note + /// + /// This can cause an error with certain binary metamethods that can trigger if only the right + /// side has a metatable. To prevent this, use [`add_meta_function`]. + /// + /// [`add_meta_function`]: #method.add_meta_function + pub fn add_meta_method_mut(&mut self, meta: MetaMethod, method: M) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + for<'a> FnMut(&'lua Lua, &'a mut T, A) -> Result, + { + self.meta_methods.insert(meta, Self::box_method_mut(method)); + } + + /// Add a metamethod which accepts generic arguments. + /// + /// Metamethods for binary operators can be triggered if either the left or right argument to + /// the binary operator has a metatable, so the first argument here is not necessarily a + /// userdata of type `T`. + pub fn add_meta_function(&mut self, meta: MetaMethod, function: F) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + FnMut(&'lua Lua, A) -> Result, + { + self.meta_methods.insert(meta, Self::box_function(function)); + } + + fn box_function(mut function: F) -> Callback<'lua> + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + FnMut(&'lua Lua, A) -> Result, + { + Box::new(move |lua, args| { + function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi( + lua, + ) + }) + } + + fn box_method(mut method: M) -> Callback<'lua> + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + for<'a> FnMut(&'lua Lua, &'a T, A) -> Result, + { + Box::new(move |lua, mut args| if let Some(front) = args.pop_front() { + let userdata = AnyUserData::from_lua(front, lua)?; + let userdata = userdata.borrow::()?; + method(lua, &userdata, A::from_lua_multi(args, lua)?)? + .to_lua_multi(lua) + } else { + Err(Error::FromLuaConversionError { + from: "missing argument", + to: "userdata", + message: None, + }) + }) + } + + fn box_method_mut(mut method: M) -> Callback<'lua> + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + for<'a> FnMut(&'lua Lua, &'a mut T, A) -> Result, + { + Box::new(move |lua, mut args| if let Some(front) = args.pop_front() { + let userdata = AnyUserData::from_lua(front, lua)?; + let mut userdata = userdata.borrow_mut::()?; + method(lua, &mut userdata, A::from_lua_multi(args, lua)?)? + .to_lua_multi(lua) + } else { + Err(Error::FromLuaConversionError { + from: "missing argument", + to: "userdata", + message: None, + }) + }) + } +} + +/// Trait for custom userdata types. +/// +/// By implementing this trait, a struct becomes eligible for use inside Lua code. Implementations +/// of [`ToLua`] and [`FromLua`] are automatically provided. +/// +/// # Examples +/// +/// ``` +/// # extern crate rlua; +/// # use rlua::{Lua, UserData, Result}; +/// # fn try_main() -> Result<()> { +/// struct MyUserData(i32); +/// +/// impl UserData for MyUserData {} +/// +/// let lua = Lua::new(); +/// +/// // `MyUserData` now implements `ToLua`: +/// lua.globals().set("myobject", MyUserData(123))?; +/// +/// lua.exec::<()>("assert(type(myobject) == 'userdata')", None)?; +/// # Ok(()) +/// # } +/// # fn main() { +/// # try_main().unwrap(); +/// # } +/// ``` +/// +/// Custom methods and operators can be provided by implementing `add_methods` (refer to +/// [`UserDataMethods`] for more information): +/// +/// ``` +/// # extern crate rlua; +/// # use rlua::{Lua, MetaMethod, UserData, UserDataMethods, Result}; +/// # fn try_main() -> Result<()> { +/// struct MyUserData(i32); +/// +/// impl UserData for MyUserData { +/// fn add_methods(methods: &mut UserDataMethods) { +/// methods.add_method("get", |_, this, _: ()| { +/// Ok(this.0) +/// }); +/// +/// methods.add_method_mut("add", |_, this, value: i32| { +/// this.0 += value; +/// Ok(()) +/// }); +/// +/// methods.add_meta_method(MetaMethod::Add, |_, this, value: i32| { +/// Ok(this.0 + value) +/// }); +/// } +/// } +/// +/// let lua = Lua::new(); +/// +/// lua.globals().set("myobject", MyUserData(123))?; +/// +/// lua.exec::<()>(r#" +/// assert(myobject:get() == 123) +/// myobject:add(7) +/// assert(myobject:get() == 130) +/// assert(myobject + 10 == 140) +/// "#, None)?; +/// # Ok(()) +/// # } +/// # fn main() { +/// # try_main().unwrap(); +/// # } +/// ``` +/// +/// [`ToLua`]: trait.ToLua.html +/// [`FromLua`]: trait.FromLua.html +/// [`UserDataMethods`]: struct.UserDataMethods.html +pub trait UserData: 'static + Sized { + /// Adds custom methods and operators specific to this userdata. + fn add_methods(_methods: &mut UserDataMethods) {} +} + +/// Handle to an internal Lua userdata for any type that implements [`UserData`]. +/// +/// Similar to `std::any::Any`, this provides an interface for dynamic type checking via the [`is`] +/// and [`borrow`] methods. +/// +/// Internally, instances are stored in a `RefCell`, to best match the mutable semantics of the Lua +/// language. +/// +/// # Note +/// +/// This API should only be used when necessary. Implementing [`UserData`] already allows defining +/// methods which check the type and acquire a borrow behind the scenes. +/// +/// [`UserData`]: trait.UserData.html +/// [`is`]: #method.is +/// [`borrow`]: #method.borrow +#[derive(Clone, Debug)] +pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>); + +impl<'lua> AnyUserData<'lua> { + /// Checks whether the type of this userdata is `T`. + pub fn is(&self) -> bool { + self.inspect(|_: &RefCell| ()).is_some() + } + + /// Borrow this userdata immutably if it is of type `T`. + /// + /// # Errors + /// + /// Returns a `UserDataBorrowError` if the userdata is already mutably borrowed. Returns a + /// `UserDataTypeMismatch` if the userdata is not of type `T`. + pub fn borrow(&self) -> Result> { + self.inspect(|cell| { + Ok(cell.try_borrow().map_err(|_| Error::UserDataBorrowError)?) + }).ok_or(Error::UserDataTypeMismatch)? + } + + /// Borrow this userdata mutably if it is of type `T`. + /// + /// # Errors + /// + /// Returns a `UserDataBorrowMutError` if the userdata is already borrowed. Returns a + /// `UserDataTypeMismatch` if the userdata is not of type `T`. + pub fn borrow_mut(&self) -> Result> { + self.inspect(|cell| { + Ok(cell.try_borrow_mut().map_err( + |_| Error::UserDataBorrowMutError, + )?) + }).ok_or(Error::UserDataTypeMismatch)? + } + + fn inspect<'a, T, R, F>(&'a self, func: F) -> Option + where + T: UserData, + F: FnOnce(&'a RefCell) -> R, + { + unsafe { + let lua = self.0.lua; + stack_guard(lua.state, 0, move || { + check_stack(lua.state, 3); + + lua.push_ref(lua.state, &self.0); + + lua_assert!( + lua.state, + ffi::lua_getmetatable(lua.state, -1) != 0, + "AnyUserData missing metatable" + ); + + ffi::lua_rawgeti( + lua.state, + ffi::LUA_REGISTRYINDEX, + lua.userdata_metatable::() as ffi::lua_Integer, + ); + + if ffi::lua_rawequal(lua.state, -1, -2) == 0 { + ffi::lua_pop(lua.state, 3); + None + } else { + let res = func(&*get_userdata::>(lua.state, -3)); + ffi::lua_pop(lua.state, 3); + Some(res) + } + }) + } + } +}