diff options
-rw-r--r-- | examples/guided_tour.rs | 2 | ||||
-rw-r--r-- | src/conversion.rs | 4 | ||||
-rw-r--r-- | src/lib.rs | 4 | ||||
-rw-r--r-- | src/lua.rs | 126 | ||||
-rw-r--r-- | src/methods.rs | 502 | ||||
-rw-r--r-- | src/scope.rs | 233 | ||||
-rw-r--r-- | src/tests/scope.rs | 6 | ||||
-rw-r--r-- | src/tests/userdata.rs | 14 | ||||
-rw-r--r-- | src/userdata.rs | 298 | ||||
-rw-r--r-- | src/util.rs | 75 |
10 files changed, 837 insertions, 427 deletions
diff --git a/examples/guided_tour.rs b/examples/guided_tour.rs index 85cecda..c7dab44 100644 --- a/examples/guided_tour.rs +++ b/examples/guided_tour.rs @@ -127,7 +127,7 @@ fn guided_tour() -> Result<()> { struct Vec2(f32, f32); impl UserData for Vec2 { - fn add_methods(methods: &mut UserDataMethods<Self>) { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { methods.add_method("magnitude", |_, vec, ()| { let mag_squared = vec.0 * vec.0 + vec.1 * vec.1; Ok(mag_squared.sqrt()) diff --git a/src/conversion.rs b/src/conversion.rs index b65eb4f..b66a9f0 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -112,13 +112,13 @@ impl<'lua> FromLua<'lua> for AnyUserData<'lua> { } } -impl<'lua, T: Send + UserData> ToLua<'lua> for T { +impl<'lua, T: 'static + Send + UserData> ToLua<'lua> for T { fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { Ok(Value::UserData(lua.create_userdata(self)?)) } } -impl<'lua, T: UserData + Clone> FromLua<'lua> for T { +impl<'lua, T: 'static + UserData + Clone> FromLua<'lua> for T { fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<T> { match value { Value::UserData(ud) => Ok(ud.borrow::<T>()?.clone()), @@ -51,6 +51,7 @@ mod macros; mod conversion; mod function; mod lua; +mod methods; mod multi; mod scope; mod string; @@ -67,13 +68,14 @@ mod tests; pub use error::{Error, ExternalError, ExternalResult, Result}; pub use function::Function; pub use lua::Lua; +pub use methods::{MetaMethod, UserDataMethods}; pub use multi::Variadic; pub use scope::Scope; pub use string::String; pub use table::{Table, TablePairs, TableSequence}; pub use thread::{Thread, ThreadStatus}; pub use types::{Integer, LightUserData, Number, RegistryKey}; -pub use userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods}; +pub use userdata::{AnyUserData, UserData}; pub use value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value}; pub mod prelude; @@ -12,16 +12,18 @@ use libc; use error::{Error, Result}; use ffi; use function::Function; +use methods::{meta_method_name, StaticUserDataMethods}; use scope::Scope; use string::String; use table::Table; use thread::Thread; use types::{Callback, Integer, LightUserData, LuaRef, Number, RegistryKey}; -use userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods}; +use userdata::{AnyUserData, UserData}; use util::{ assert_stack, callback_error, check_stack, gc_guard, get_userdata, get_wrapped_error, - init_error_metatables, main_state, pop_error, protect_lua, protect_lua_closure, push_string, - push_userdata, push_wrapped_error, safe_pcall, safe_xpcall, userdata_destructor, StackGuard, + init_error_metatables, init_userdata_metatable, main_state, pop_error, protect_lua, + protect_lua_closure, push_string, push_userdata, push_wrapped_error, safe_pcall, safe_xpcall, + userdata_destructor, StackGuard, }; use value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value}; @@ -312,7 +314,7 @@ impl Lua { /// Create a Lua userdata object from a custom userdata type. pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData> where - T: Send + UserData, + T: 'static + Send + UserData, { unsafe { self.make_userdata(data) } } @@ -328,15 +330,15 @@ impl Lua { } /// Calls the given function with a `Scope` parameter, giving the function the ability to create - /// userdata from rust types that are !Send, and rust callbacks that are !Send and not 'static. + /// userdata and callbacks from rust types that are !Send or non-'static. /// /// The lifetime of any function or userdata created through `Scope` lasts only until the /// completion of this method call, on completion all such created values are automatically /// dropped and Lua references to them are invalidated. If a script accesses a value created /// through `Scope` outside of this method, a Lua error will result. Since we can ensure the /// lifetime of values created through `Scope`, and we know that `Lua` cannot be sent to another - /// thread while `Scope` is live, it is safe to allow !Send datatypes and functions whose - /// lifetimes only outlive the scope lifetime. + /// thread while `Scope` is live, it is safe to allow !Send datatypes and whose lifetimes only + /// outlive the scope lifetime. /// /// Handles that `Lua::scope` produces have a `'lua` lifetime of the scope parameter, to prevent /// the handles from escaping the callback. However, this is not the only way for values to @@ -768,27 +770,7 @@ impl Lua { } } - pub(crate) unsafe fn userdata_metatable<T: UserData>(&self) -> Result<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 { - ffi::luaL_checkstack(state, 2, ptr::null()); - - ffi::lua_pushvalue(state, -1); - ffi::lua_gettable(state, ffi::lua_upvalueindex(1)); - if ffi::lua_isnil(state, -1) == 0 { - ffi::lua_insert(state, -3); - ffi::lua_pop(state, 2); - 1 - } else { - ffi::lua_pop(state, 1); - ffi::lua_pushvalue(state, ffi::lua_upvalueindex(2)); - ffi::lua_insert(state, -3); - ffi::lua_call(state, 2, 1); - 1 - } - } - + pub(crate) unsafe fn userdata_metatable<T: 'static + UserData>(&self) -> Result<c_int> { if let Some(table_id) = (*extra_data(self.state)) .registered_userdata .get(&TypeId::of::<T>()) @@ -797,27 +779,29 @@ impl Lua { } let _sg = StackGuard::new(self.state); - assert_stack(self.state, 6); + assert_stack(self.state, 8); - let mut methods = UserDataMethods { - methods: HashMap::new(), - meta_methods: HashMap::new(), - _type: PhantomData, - }; + let mut methods = StaticUserDataMethods::default(); T::add_methods(&mut methods); protect_lua_closure(self.state, 0, 1, |state| { ffi::lua_newtable(state); })?; + for (k, m) in methods.meta_methods { + push_string(self.state, meta_method_name(k))?; + self.push_value(Value::Function(self.create_callback(m)?)); - let has_methods = !methods.methods.is_empty(); + protect_lua_closure(self.state, 3, 1, |state| { + ffi::lua_rawset(state, -3); + })?; + } - if has_methods { - push_string(self.state, "__index")?; + if methods.methods.is_empty() { + init_userdata_metatable::<RefCell<T>>(self.state, -1, None)?; + } else { protect_lua_closure(self.state, 0, 1, |state| { ffi::lua_newtable(state); })?; - for (k, m) in methods.methods { push_string(self.state, &k)?; self.push_value(Value::Function(self.create_callback(m)?)); @@ -826,70 +810,10 @@ impl Lua { })?; } - protect_lua_closure(self.state, 3, 1, |state| { - ffi::lua_rawset(state, -3); - })?; + init_userdata_metatable::<RefCell<T>>(self.state, -2, Some(-1))?; + ffi::lua_pop(self.state, 1); } - for (k, m) in methods.meta_methods { - if k == MetaMethod::Index && has_methods { - push_string(self.state, "__index")?; - ffi::lua_pushvalue(self.state, -1); - ffi::lua_gettable(self.state, -3); - self.push_value(Value::Function(self.create_callback(m)?)); - protect_lua_closure(self.state, 2, 1, |state| { - ffi::lua_pushcclosure(state, meta_index_impl, 2); - })?; - - protect_lua_closure(self.state, 3, 1, |state| { - ffi::lua_rawset(state, -3); - })?; - } else { - let name = match k { - MetaMethod::Add => "__add", - MetaMethod::Sub => "__sub", - MetaMethod::Mul => "__mul", - MetaMethod::Div => "__div", - MetaMethod::Mod => "__mod", - MetaMethod::Pow => "__pow", - MetaMethod::Unm => "__unm", - MetaMethod::IDiv => "__idiv", - MetaMethod::BAnd => "__band", - MetaMethod::BOr => "__bor", - MetaMethod::BXor => "__bxor", - MetaMethod::BNot => "__bnot", - MetaMethod::Shl => "__shl", - MetaMethod::Shr => "__shr", - MetaMethod::Concat => "__concat", - MetaMethod::Len => "__len", - MetaMethod::Eq => "__eq", - MetaMethod::Lt => "__lt", - MetaMethod::Le => "__le", - MetaMethod::Index => "__index", - MetaMethod::NewIndex => "__newindex", - MetaMethod::Call => "__call", - MetaMethod::ToString => "__tostring", - }; - push_string(self.state, name)?; - self.push_value(Value::Function(self.create_callback(m)?)); - protect_lua_closure(self.state, 3, 1, |state| { - ffi::lua_rawset(state, -3); - })?; - } - } - - push_string(self.state, "__gc")?; - ffi::lua_pushcfunction(self.state, userdata_destructor::<RefCell<T>>); - protect_lua_closure(self.state, 3, 1, |state| { - ffi::lua_rawset(state, -3); - })?; - - push_string(self.state, "__metatable")?; - ffi::lua_pushboolean(self.state, 0); - protect_lua_closure(self.state, 3, 1, |state| { - ffi::lua_rawset(state, -3); - })?; - let id = gc_guard(self.state, || { ffi::luaL_ref(self.state, ffi::LUA_REGISTRYINDEX) }); @@ -965,7 +889,7 @@ impl Lua { // Does not require Send bounds, which can lead to unsafety. pub(crate) unsafe fn make_userdata<T>(&self, data: T) -> Result<AnyUserData> where - T: UserData, + T: 'static + UserData, { let _sg = StackGuard::new(self.state); assert_stack(self.state, 4); diff --git a/src/methods.rs b/src/methods.rs new file mode 100644 index 0000000..d953f96 --- /dev/null +++ b/src/methods.rs @@ -0,0 +1,502 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::marker::PhantomData; +use std::string::String as StdString; + +use error::{Error, Result}; +use lua::Lua; +use types::Callback; +use userdata::{AnyUserData, UserData}; +use value::{FromLua, FromLuaMulti, MultiValue, ToLuaMulti}; + +/// Kinds of metamethods that can be overridden. +/// +/// Currently, this mechanism does not allow overriding the `__gc` metamethod, since there is +/// generally no need to do so: [`UserData`] implementors can instead just implement `Drop`. +/// +/// [`UserData`]: trait.UserData.html +#[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, + /// The `__tostring` metamethod. + /// + /// This is not an operator, but will be called by methods such as `tostring` and `print`. + ToString, +} + +pub(crate) fn meta_method_name(meta: MetaMethod) -> &'static str { + match meta { + MetaMethod::Add => "__add", + MetaMethod::Sub => "__sub", + MetaMethod::Mul => "__mul", + MetaMethod::Div => "__div", + MetaMethod::Mod => "__mod", + MetaMethod::Pow => "__pow", + MetaMethod::Unm => "__unm", + MetaMethod::IDiv => "__idiv", + MetaMethod::BAnd => "__band", + MetaMethod::BOr => "__bor", + MetaMethod::BXor => "__bxor", + MetaMethod::BNot => "__bnot", + MetaMethod::Shl => "__shl", + MetaMethod::Shr => "__shr", + MetaMethod::Concat => "__concat", + MetaMethod::Len => "__len", + MetaMethod::Eq => "__eq", + MetaMethod::Lt => "__lt", + MetaMethod::Le => "__le", + MetaMethod::Index => "__index", + MetaMethod::NewIndex => "__newindex", + MetaMethod::Call => "__call", + MetaMethod::ToString => "__tostring", + } +} + +/// Method registry for [`UserData`] implementors. +/// +/// [`UserData`]: trait.UserData.html +pub trait UserDataMethods<'lua, T: UserData> { + /// 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 set the `__index` metamethod, the `__index` metamethod will + /// be used as a fall-back if no regular method is found. + fn add_method<A, R, M>(&mut self, name: &str, method: M) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>; + + /// 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 + fn add_method_mut<A, R, M>(&mut self, name: &str, method: M) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>; + + /// 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 + fn add_function<A, R, F>(&mut self, name: &str, function: F) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>; + + /// Add a regular method as a mutable function which accepts generic arguments, the first + /// argument will always be a `UserData` of type T. + /// + /// This is a version of [`add_function`] that accepts a FnMut argument. + /// + /// [`add_function`]: #method.add_function + fn add_function_mut<A, R, F>(&mut self, name: &str, function: F) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>; + + /// 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 + fn add_meta_method<A, R, M>(&mut self, meta: MetaMethod, method: M) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>; + + /// 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 + fn add_meta_method_mut<A, R, M>(&mut self, meta: MetaMethod, method: M) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>; + + /// 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`. + fn add_meta_function<A, R, F>(&mut self, meta: MetaMethod, function: F) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>; + + /// Add a metamethod as a mutable function which accepts generic arguments. + /// + /// This is a version of [`add_meta_function`] that accepts a FnMut argument. + /// + /// [`add_meta_function`]: #method.add_meta_function + fn add_meta_function_mut<A, R, F>(&mut self, meta: MetaMethod, function: F) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>; +} + +pub(crate) struct StaticUserDataMethods<'lua, T: 'static + UserData> { + pub(crate) methods: HashMap<StdString, Callback<'lua, 'static>>, + pub(crate) meta_methods: HashMap<MetaMethod, Callback<'lua, 'static>>, + pub(crate) _type: PhantomData<T>, +} + +impl<'lua, T: 'static + UserData> Default for StaticUserDataMethods<'lua, T> { + fn default() -> StaticUserDataMethods<'lua, T> { + StaticUserDataMethods { + methods: HashMap::new(), + meta_methods: HashMap::new(), + _type: PhantomData, + } + } +} + +impl<'lua, T: 'static + UserData> UserDataMethods<'lua, T> for StaticUserDataMethods<'lua, T> { + fn add_method<A, R, M>(&mut self, name: &str, method: M) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>, + { + self.methods + .insert(name.to_owned(), Self::box_method(method)); + } + + fn add_method_mut<A, R, M>(&mut self, name: &str, method: M) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>, + { + self.methods + .insert(name.to_owned(), Self::box_method_mut(method)); + } + + fn add_function<A, R, F>(&mut self, name: &str, function: F) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>, + { + self.methods + .insert(name.to_owned(), Self::box_function(function)); + } + + fn add_function_mut<A, R, F>(&mut self, name: &str, function: F) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>, + { + self.methods + .insert(name.to_owned(), Self::box_function_mut(function)); + } + + fn add_meta_method<A, R, M>(&mut self, meta: MetaMethod, method: M) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>, + { + self.meta_methods.insert(meta, Self::box_method(method)); + } + + fn add_meta_method_mut<A, R, M>(&mut self, meta: MetaMethod, method: M) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>, + { + self.meta_methods.insert(meta, Self::box_method_mut(method)); + } + + fn add_meta_function<A, R, F>(&mut self, meta: MetaMethod, function: F) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>, + { + self.meta_methods.insert(meta, Self::box_function(function)); + } + + fn add_meta_function_mut<A, R, F>(&mut self, meta: MetaMethod, function: F) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>, + { + self.meta_methods + .insert(meta, Self::box_function_mut(function)); + } +} + +impl<'lua, T: 'static + UserData> StaticUserDataMethods<'lua, T> { + fn box_method<A, R, M>(method: M) -> Callback<'lua, 'static> + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>, + { + Box::new(move |lua, mut args| { + if let Some(front) = args.pop_front() { + let userdata = AnyUserData::from_lua(front, lua)?; + let userdata = userdata.borrow::<T>()?; + 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<A, R, M>(method: M) -> Callback<'lua, 'static> + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>, + { + let method = RefCell::new(method); + 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::<T>()?; + let mut method = method + .try_borrow_mut() + .map_err(|_| Error::RecursiveMutCallback)?; + (&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, + }) + } + }) + } + + fn box_function<A, R, F>(function: F) -> Callback<'lua, 'static> + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>, + { + Box::new(move |lua, args| function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)) + } + + fn box_function_mut<A, R, F>(function: F) -> Callback<'lua, 'static> + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>, + { + let function = RefCell::new(function); + Box::new(move |lua, args| { + let function = &mut *function + .try_borrow_mut() + .map_err(|_| Error::RecursiveMutCallback)?; + function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) + }) + } +} + +pub(crate) enum NonStaticMethod<'lua, T> { + Method(Box<Fn(&'lua Lua, &T, MultiValue<'lua>) -> Result<MultiValue<'lua>>>), + MethodMut(Box<FnMut(&'lua Lua, &mut T, MultiValue<'lua>) -> Result<MultiValue<'lua>>>), + Function(Box<Fn(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>>>), + FunctionMut(Box<FnMut(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>>>), +} + +pub(crate) struct NonStaticUserDataMethods<'lua, T: UserData> { + pub(crate) methods: HashMap<StdString, NonStaticMethod<'lua, T>>, + pub(crate) meta_methods: HashMap<MetaMethod, NonStaticMethod<'lua, T>>, +} + +impl<'lua, T: UserData> Default for NonStaticUserDataMethods<'lua, T> { + fn default() -> NonStaticUserDataMethods<'lua, T> { + NonStaticUserDataMethods { + methods: HashMap::new(), + meta_methods: HashMap::new(), + } + } +} + +impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'lua, T> { + fn add_method<A, R, M>(&mut self, name: &str, method: M) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>, + { + self.methods.insert( + name.to_owned(), + NonStaticMethod::Method(Box::new(move |lua, ud, args| { + method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) + })), + ); + } + + fn add_method_mut<A, R, M>(&mut self, name: &str, mut method: M) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>, + { + self.methods.insert( + name.to_owned(), + NonStaticMethod::MethodMut(Box::new(move |lua, ud, args| { + method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) + })), + ); + } + + fn add_function<A, R, F>(&mut self, name: &str, function: F) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>, + { + self.methods.insert( + name.to_owned(), + NonStaticMethod::Function(Box::new(move |lua, args| { + function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) + })), + ); + } + + fn add_function_mut<A, R, F>(&mut self, name: &str, mut function: F) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>, + { + self.methods.insert( + name.to_owned(), + NonStaticMethod::FunctionMut(Box::new(move |lua, args| { + function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) + })), + ); + } + + fn add_meta_method<A, R, M>(&mut self, meta: MetaMethod, method: M) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>, + { + self.meta_methods.insert( + meta, + NonStaticMethod::Method(Box::new(move |lua, ud, args| { + method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) + })), + ); + } + + fn add_meta_method_mut<A, R, M>(&mut self, meta: MetaMethod, mut method: M) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>, + { + self.meta_methods.insert( + meta, + NonStaticMethod::MethodMut(Box::new(move |lua, ud, args| { + method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) + })), + ); + } + + fn add_meta_function<A, R, F>(&mut self, meta: MetaMethod, function: F) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>, + { + self.meta_methods.insert( + meta, + NonStaticMethod::Function(Box::new(move |lua, args| { + function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) + })), + ); + } + + fn add_meta_function_mut<A, R, F>(&mut self, meta: MetaMethod, mut function: F) + where + A: FromLuaMulti<'lua>, + R: ToLuaMulti<'lua>, + F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>, + { + self.meta_methods.insert( + meta, + NonStaticMethod::FunctionMut(Box::new(move |lua, args| { + function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) + })), + ); + } +} diff --git a/src/scope.rs b/src/scope.rs index efda276..8da2e27 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -2,15 +2,21 @@ use std::any::Any; use std::cell::RefCell; use std::marker::PhantomData; use std::mem; +use std::os::raw::c_void; +use std::rc::Rc; use error::{Error, Result}; use ffi; use function::Function; use lua::Lua; +use methods::{meta_method_name, NonStaticMethod, NonStaticUserDataMethods}; use types::Callback; use userdata::{AnyUserData, UserData}; -use util::{assert_stack, take_userdata, StackGuard}; -use value::{FromLuaMulti, ToLuaMulti}; +use util::{ + assert_stack, init_userdata_metatable, protect_lua_closure, push_string, push_userdata, + take_userdata, StackGuard, +}; +use value::{FromLuaMulti, MultiValue, ToLuaMulti, Value}; /// Constructed by the [`Lua::scope`] method, allows temporarily passing to Lua userdata that is /// !Send, and callbacks that are !Send and not 'static. @@ -48,30 +54,9 @@ impl<'scope> Scope<'scope> { F: 'scope + Fn(&'lua Lua, A) -> Result<R>, { unsafe { - let f = Box::new(move |lua, args| { + self.create_callback(Box::new(move |lua, args| { func(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) - }); - let f = mem::transmute::<Callback<'lua, 'scope>, Callback<'lua, 'static>>(f); - let f = self.lua.create_callback(f)?; - - let mut destructors = self.destructors.borrow_mut(); - let f_destruct = f.0.clone(); - destructors.push(Box::new(move || { - let state = f_destruct.lua.state; - let _sg = StackGuard::new(state); - assert_stack(state, 2); - f_destruct.lua.push_ref(&f_destruct); - - ffi::lua_getupvalue(state, -1, 1); - let ud = take_userdata::<Callback>(state); - - ffi::lua_pushnil(state); - ffi::lua_setupvalue(state, -2, 1); - - ffi::lua_pop(state, 1); - Box::new(ud) - })); - Ok(f) + })) } } @@ -100,14 +85,14 @@ impl<'scope> Scope<'scope> { /// Create a Lua userdata object from a custom userdata type. /// /// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on scope - /// drop, and does not require that the userdata type be Send. See [`Lua::scope`] for more - /// details. + /// drop, and does not require that the userdata type be Send (but still requires that the + /// UserData be 'static). See [`Lua::scope`] for more details. /// /// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata /// [`Lua::scope`]: struct.Lua.html#method.scope - pub fn create_userdata<'lua, T>(&'lua self, data: T) -> Result<AnyUserData<'lua>> + pub fn create_static_userdata<'lua, T>(&'lua self, data: T) -> Result<AnyUserData<'lua>> where - T: UserData, + T: 'static + UserData, { unsafe { let u = self.lua.make_userdata(data)?; @@ -123,6 +108,196 @@ impl<'scope> Scope<'scope> { Ok(u) } } + + /// Create a Lua userdata object from a custom userdata type. + /// + /// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on scope + /// drop, and does not require that the userdata type be Send or 'static. See [`Lua::scope`] for + /// more details. + /// + /// Lifting the requirement that the UserData type be 'static comes with some important + /// limitations, so if you only need to eliminate the Send requirement, it is probably better to + /// use [`Scope::create_static_userdata`] instead. + /// + /// The main limitation that comes from using non-'static userdata is that the produced userdata + /// will no longer have a `TypeId` associated with it, becuase `TypeId` can only work for + /// 'static types. This means that it is impossible, once the userdata is created, to get a + /// reference to it back *out* of an `AnyUserData` handle. This also implies that the + /// "function" type methods that can be added via [`UserDataMethods`] (the ones that accept + /// `AnyUserData` as a first parameter) are vastly less useful. Also, there is no way to re-use + /// a single metatable for multiple non-'static types, so there is a higher cost associated with + /// creating the userdata metatable each time a new userdata is created. + /// + /// [`create_static_userdata`]: #method.create_static_userdata + /// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata + /// [`Lua::scope`]: struct.Lua.html#method.scope + /// [`UserDataMethods`]: trait.UserDataMethods.html + pub fn create_userdata<'lua, T>(&'lua self, data: T) -> Result<AnyUserData<'lua>> + where + T: 'scope + UserData, + { + let data = Rc::new(RefCell::new(data)); + + // 'callback outliving 'scope is a lie to make the types work out, required due to the + // inability to work with the "correct" universally quantified callback type. + fn wrap_method<'scope, 'lua, 'callback: 'scope, T: 'scope>( + scope: &'lua Scope<'scope>, + data: Rc<RefCell<T>>, + method: NonStaticMethod<'callback, T>, + ) -> Result<Function<'lua>> { + // On methods that actually receive the userdata, we fake a type check on the passed in + // userdata, where we pretend there is a unique type per call to Scope::create_userdata. + // You can grab a method from a userdata and call it on a mismatched userdata type, + // which when using normal 'static userdata will fail with a type mismatch, but here + // without this check would proceed as though you had called the method on the original + // value (since we otherwise completely ignore the first argument). + let check_data = data.clone(); + let check_ud_type = move |lua: &Lua, value| { + if let Some(value) = value { + if let Value::UserData(u) = value { + unsafe { + let _sg = StackGuard::new(lua.state); + assert_stack(lua.state, 1); + lua.push_ref(&u.0); + ffi::lua_getuservalue(lua.state, -1); + return ffi::lua_touserdata(lua.state, -1) + == check_data.as_ptr() as *mut c_void; + } + } + } + + false + }; + + match method { + NonStaticMethod::Method(method) => { + let method_data = data.clone(); + let f = Box::new(move |lua, mut args: MultiValue<'callback>| { + if !check_ud_type(lua, args.pop_front()) { + return Err(Error::UserDataTypeMismatch); + } + let data = method_data + .try_borrow() + .map_err(|_| Error::UserDataBorrowError)?; + method(lua, &*data, args) + }); + unsafe { scope.create_callback(f) } + } + NonStaticMethod::MethodMut(method) => { + let method = RefCell::new(method); + let method_data = data.clone(); + let f = Box::new(move |lua, mut args: MultiValue<'callback>| { + if !check_ud_type(lua, args.pop_front()) { + return Err(Error::UserDataTypeMismatch); + } + let mut method = method + .try_borrow_mut() + .map_err(|_| Error::RecursiveMutCallback)?; + let mut data = method_data + .try_borrow_mut() + .map_err(|_| Error::UserDataBorrowMutError)?; + (&mut *method)(lua, &mut *data, args) + }); + unsafe { scope.create_callback(f) } + } + NonStaticMethod::Function(function) => unsafe { scope.create_callback(function) }, + NonStaticMethod::FunctionMut(function) => { + let function = RefCell::new(function); + let f = Box::new(move |lua, args| { + (&mut *function + .try_borrow_mut() + .map_err(|_| Error::RecursiveMutCallback)?)( + lua, args + ) + }); + unsafe { scope.create_callback(f) } + } + } + } + + let mut ud_methods = NonStaticUserDataMethods::default(); + T::add_methods(&mut ud_methods); + + unsafe { + let lua = self.lua; + let _sg = StackGuard::new(lua.state); + assert_stack(lua.state, 6); + + push_userdata(lua.state, ())?; + ffi::lua_pushlightuserdata(lua.state, data.as_ptr() as *mut c_void); + ffi::lua_setuservalue(lua.state, -2); + + protect_lua_closure(lua.state, 0, 1, move |state| { + ffi::lua_newtable(state); + })?; + + for (k, m) in ud_methods.meta_methods { + push_string(lua.state, meta_method_name(k))?; + lua.push_value(Value::Function(wrap_method(self, data.clone(), m)?)); + + protect_lua_closure(lua.state, 3, 1, |state| { + ffi::lua_rawset(state, -3); + })?; + } + + if ud_methods.methods.is_empty() { + init_userdata_metatable::<()>(lua.state, -1, None)?; + } else { + protect_lua_closure(lua.state, 0, 1, |state| { + ffi::lua_newtable(state); + })?; + for (k, m) in ud_methods.methods { + push_string(lua.state, &k)?; + lua.push_value(Value::Function(wrap_method(self, data.clone(), m)?)); + protect_lua_closure(lua.state, 3, 1, |state| { + ffi::lua_rawset(state, -3); + })?; + } + + init_userdata_metatable::<()>(lua.state, -2, Some(-1))?; + ffi::lua_pop(lua.state, 1); + } + + ffi::lua_setmetatable(lua.state, -2); + + Ok(AnyUserData(lua.pop_ref())) + } + } + + // Unsafe, because the callback (since it is non-'static) can capture any value with 'callback + // scope, such as improperly holding onto an argument. This is not a problem in + // Lua::create_callback because all normal callbacks are 'static. This pattern happens because + // it is currently extremely hard to deal with (without ATCs) the "correct" callback type of: + // + // Box<for<'lua> Fn(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>>)> + // + // So in order for this to be safe, the callback must NOT capture any arguments. + unsafe fn create_callback<'lua, 'callback>( + &'lua self, + f: Callback<'callback, 'scope>, + ) -> Result<Function<'lua>> { + let f = mem::transmute::<Callback<'callback, 'scope>, Callback<'callback, 'static>>(f); + let f = self.lua.create_callback(f)?; + + let mut destructors = self.destructors.borrow_mut(); + let f_destruct = f.0.clone(); + destructors.push(Box::new(move || { + let state = f_destruct.lua.state; + let _sg = StackGuard::new(state); + assert_stack(state, 2); + f_destruct.lua.push_ref(&f_destruct); + + ffi::lua_getupvalue(state, -1, 1); + let ud = take_userdata::<Callback>(state); + + ffi::lua_pushnil(state); + ffi::lua_setupvalue(state, -2, 1); + + ffi::lua_pop(state, 1); + Box::new(ud) + })); + Ok(f) + } } impl<'scope> Drop for Scope<'scope> { diff --git a/src/tests/scope.rs b/src/tests/scope.rs index 1e4c255..8b2b93c 100644 --- a/src/tests/scope.rs +++ b/src/tests/scope.rs @@ -40,7 +40,7 @@ fn scope_drop() { struct MyUserdata(Rc<()>); impl UserData for MyUserdata { - fn add_methods(methods: &mut UserDataMethods<Self>) { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { methods.add_method("method", |_, _, ()| Ok(())); } } @@ -51,7 +51,9 @@ fn scope_drop() { lua.globals() .set( "test", - scope.create_userdata(MyUserdata(rc.clone())).unwrap(), + scope + .create_static_userdata(MyUserdata(rc.clone())) + .unwrap(), ) .unwrap(); assert_eq!(Rc::strong_count(&rc), 2); diff --git a/src/tests/userdata.rs b/src/tests/userdata.rs index 56d70af..46e5d0e 100644 --- a/src/tests/userdata.rs +++ b/src/tests/userdata.rs @@ -15,10 +15,10 @@ fn test_user_data() { let userdata1 = lua.create_userdata(UserData1(1)).unwrap(); let userdata2 = lua.create_userdata(UserData2(Box::new(2))).unwrap(); - assert!(userdata1.is::<UserData1>().unwrap()); - assert!(!userdata1.is::<UserData2>().unwrap()); - assert!(userdata2.is::<UserData2>().unwrap()); - assert!(!userdata2.is::<UserData1>().unwrap()); + assert!(userdata1.is::<UserData1>()); + assert!(!userdata1.is::<UserData2>()); + assert!(userdata2.is::<UserData2>()); + assert!(!userdata2.is::<UserData1>()); assert_eq!(userdata1.borrow::<UserData1>().unwrap().0, 1); assert_eq!(*userdata2.borrow::<UserData2>().unwrap().0, 2); @@ -29,7 +29,7 @@ fn test_methods() { struct MyUserData(i64); impl UserData for MyUserData { - fn add_methods(methods: &mut UserDataMethods<Self>) { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { methods.add_method("get_value", |_, data, ()| Ok(data.0)); methods.add_method_mut("set_value", |_, data, args| { data.0 = args; @@ -69,7 +69,7 @@ fn test_metamethods() { struct MyUserData(i64); impl UserData for MyUserData { - fn add_methods(methods: &mut UserDataMethods<Self>) { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { methods.add_method("get", |_, data, ()| Ok(data.0)); methods.add_meta_function( MetaMethod::Add, @@ -117,7 +117,7 @@ fn test_gc_userdata() { } impl UserData for MyUserdata { - fn add_methods(methods: &mut UserDataMethods<Self>) { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { methods.add_method("access", |_, this, ()| { assert!(this.id == 123); Ok(()) diff --git a/src/userdata.rs b/src/userdata.rs index 6bacdae..d3d9eba 100644 --- a/src/userdata.rs +++ b/src/userdata.rs @@ -1,281 +1,11 @@ use std::cell::{Ref, RefCell, RefMut}; -use std::collections::HashMap; -use std::marker::PhantomData; -use std::string::String as StdString; use error::{Error, Result}; use ffi; -use lua::Lua; -use types::{Callback, LuaRef}; +use methods::UserDataMethods; +use types::LuaRef; use util::{assert_stack, get_userdata, StackGuard}; -use value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti}; - -/// Kinds of metamethods that can be overridden. -/// -/// Currently, this mechanism does not allow overriding the `__gc` metamethod, since there is -/// generally no need to do so: [`UserData`] implementors can instead just implement `Drop`. -/// -/// [`UserData`]: trait.UserData.html -#[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, - /// The `__tostring` metamethod. - /// - /// This is not an operator, but will be called by methods such as `tostring` and `print`. - ToString, -} - -/// Method registry for [`UserData`] implementors. -/// -/// [`UserData`]: trait.UserData.html -pub struct UserDataMethods<'lua, T> { - pub(crate) methods: HashMap<StdString, Callback<'lua, 'static>>, - pub(crate) meta_methods: HashMap<MetaMethod, Callback<'lua, 'static>>, - pub(crate) _type: PhantomData<T>, -} - -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 set the `__index` metamethod, the `__index` metamethod will - /// be used as a fall-back if no regular method is found. - pub fn add_method<A, R, M>(&mut self, name: &str, method: M) - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>, - { - 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<A, R, M>(&mut self, name: &str, method: M) - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>, - { - 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<A, R, F>(&mut self, name: &str, function: F) - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>, - { - self.methods - .insert(name.to_owned(), Self::box_function(function)); - } - - /// Add a regular method as a mutable function which accepts generic arguments, the first - /// argument will always be a `UserData` of type T. - /// - /// This is a version of [`add_function`] that accepts a FnMut argument. - /// - /// [`add_function`]: #method.add_function - pub fn add_function_mut<A, R, F>(&mut self, name: &str, function: F) - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>, - { - self.methods - .insert(name.to_owned(), Self::box_function_mut(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<A, R, M>(&mut self, meta: MetaMethod, method: M) - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>, - { - 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<A, R, M>(&mut self, meta: MetaMethod, method: M) - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>, - { - 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<A, R, F>(&mut self, meta: MetaMethod, function: F) - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>, - { - self.meta_methods.insert(meta, Self::box_function(function)); - } - - /// Add a metamethod as a mutable function which accepts generic arguments. - /// - /// This is a version of [`add_meta_function`] that accepts a FnMut argument. - /// - /// [`add_meta_function`]: #method.add_meta_function - pub fn add_meta_function_mut<A, R, F>(&mut self, meta: MetaMethod, function: F) - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>, - { - self.meta_methods - .insert(meta, Self::box_function_mut(function)); - } - - fn box_function<A, R, F>(function: F) -> Callback<'lua, 'static> - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>, - { - Box::new(move |lua, args| function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)) - } - - fn box_function_mut<A, R, F>(function: F) -> Callback<'lua, 'static> - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>, - { - let function = RefCell::new(function); - Box::new(move |lua, args| { - let function = &mut *function - .try_borrow_mut() - .map_err(|_| Error::RecursiveMutCallback)?; - function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua) - }) - } - - fn box_method<A, R, M>(method: M) -> Callback<'lua, 'static> - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>, - { - Box::new(move |lua, mut args| { - if let Some(front) = args.pop_front() { - let userdata = AnyUserData::from_lua(front, lua)?; - let userdata = userdata.borrow::<T>()?; - 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<A, R, M>(method: M) -> Callback<'lua, 'static> - where - A: FromLuaMulti<'lua>, - R: ToLuaMulti<'lua>, - M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>, - { - let method = RefCell::new(method); - 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::<T>()?; - let mut method = method - .try_borrow_mut() - .map_err(|_| Error::RecursiveMutCallback)?; - (&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, - }) - } - }) - } -} +use value::{FromLua, ToLua}; /// Trait for custom userdata types. /// @@ -315,7 +45,7 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> { /// struct MyUserData(i32); /// /// impl UserData for MyUserData { -/// fn add_methods(methods: &mut UserDataMethods<Self>) { +/// fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { /// methods.add_method("get", |_, this, _: ()| { /// Ok(this.0) /// }); @@ -350,10 +80,10 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> { /// /// [`ToLua`]: trait.ToLua.html /// [`FromLua`]: trait.FromLua.html -/// [`UserDataMethods`]: struct.UserDataMethods.html -pub trait UserData: 'static + Sized { +/// [`UserDataMethods`]: trait.UserDataMethods.html +pub trait UserData: Sized { /// Adds custom methods and operators specific to this userdata. - fn add_methods(_methods: &mut UserDataMethods<Self>) {} + fn add_methods<'lua, T: UserDataMethods<'lua, Self>>(_methods: &mut T) {} } /// Handle to an internal Lua userdata for any type that implements [`UserData`]. @@ -377,11 +107,11 @@ pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>); impl<'lua> AnyUserData<'lua> { /// Checks whether the type of this userdata is `T`. - pub fn is<T: UserData>(&self) -> Result<bool> { + pub fn is<T: 'static + UserData>(&self) -> bool { match self.inspect(|_: &RefCell<T>| Ok(())) { - Ok(()) => Ok(true), - Err(Error::UserDataTypeMismatch) => Ok(false), - Err(err) => Err(err), + Ok(()) => true, + Err(Error::UserDataTypeMismatch) => false, + Err(_) => unreachable!(), } } @@ -391,7 +121,7 @@ impl<'lua> AnyUserData<'lua> { /// /// Returns a `UserDataBorrowError` if the userdata is already mutably borrowed. Returns a /// `UserDataTypeMismatch` if the userdata is not of type `T`. - pub fn borrow<T: UserData>(&self) -> Result<Ref<T>> { + pub fn borrow<T: 'static + UserData>(&self) -> Result<Ref<T>> { self.inspect(|cell| Ok(cell.try_borrow().map_err(|_| Error::UserDataBorrowError)?)) } @@ -401,7 +131,7 @@ impl<'lua> AnyUserData<'lua> { /// /// Returns a `UserDataBorrowMutError` if the userdata is already borrowed. Returns a /// `UserDataTypeMismatch` if the userdata is not of type `T`. - pub fn borrow_mut<T: UserData>(&self) -> Result<RefMut<T>> { + pub fn borrow_mut<T: 'static + UserData>(&self) -> Result<RefMut<T>> { self.inspect(|cell| { Ok(cell .try_borrow_mut() @@ -444,7 +174,7 @@ impl<'lua> AnyUserData<'lua> { fn inspect<'a, T, R, F>(&'a self, func: F) -> Result<R> where - T: UserData, + T: 'static + UserData, F: FnOnce(&'a RefCell<T>) -> Result<R>, { unsafe { diff --git a/src/util.rs b/src/util.rs index b650d9a..596e45a 100644 --- a/src/util.rs +++ b/src/util.rs @@ -250,6 +250,81 @@ pub unsafe fn take_userdata<T>(state: *mut ffi::lua_State) -> T { ptr::read(ud) } +// Populates the given table with the appropriate members to be a userdata metatable for the given +// type. This function takes the given table at the `metatable` index, and adds an appropriate __gc +// member to it for the given type and a __metatable entry to protect the table from script access. +// The function also, if given a `members` table index, will set up an __index metamethod to return +// the appropriate member on __index. Additionally, if there is already an __index entry on the +// given metatable, instead of simply overwriting the __index, instead the created __index method +// will capture the previous one, and use it as a fallback only if the given key is not found in the +// provided members table. Internally uses 6 stack spaces and does not call checkstack. +pub unsafe fn init_userdata_metatable<T>( + state: *mut ffi::lua_State, + metatable: c_int, + members: Option<c_int>, +) -> Result<()> { + // 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 { + ffi::luaL_checkstack(state, 2, ptr::null()); + + ffi::lua_pushvalue(state, -1); + ffi::lua_gettable(state, ffi::lua_upvalueindex(2)); + if ffi::lua_isnil(state, -1) == 0 { + ffi::lua_insert(state, -3); + ffi::lua_pop(state, 2); + 1 + } else { + ffi::lua_pop(state, 1); + ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1)); + ffi::lua_insert(state, -3); + ffi::lua_call(state, 2, 1); + 1 + } + } + + let members = members.map(|i| ffi::lua_absindex(state, i)); + ffi::lua_pushvalue(state, metatable); + + if let Some(members) = members { + push_string(state, "__index")?; + ffi::lua_pushvalue(state, -1); + + let index_type = ffi::lua_rawget(state, -3); + if index_type == ffi::LUA_TNIL { + ffi::lua_pop(state, 1); + ffi::lua_pushvalue(state, members); + } else if index_type == ffi::LUA_TFUNCTION { + ffi::lua_pushvalue(state, members); + protect_lua_closure(state, 2, 1, |state| { + ffi::lua_pushcclosure(state, meta_index_impl, 2); + })?; + } else { + rlua_panic!("improper __index type {}", index_type); + } + + protect_lua_closure(state, 3, 1, |state| { + ffi::lua_rawset(state, -3); + })?; + } + + push_string(state, "__gc")?; + ffi::lua_pushcfunction(state, userdata_destructor::<T>); + protect_lua_closure(state, 3, 1, |state| { + ffi::lua_rawset(state, -3); + })?; + + push_string(state, "__metatable")?; + ffi::lua_pushboolean(state, 0); + protect_lua_closure(state, 3, 1, |state| { + ffi::lua_rawset(state, -3); + })?; + + ffi::lua_pop(state, 1); + + Ok(()) +} + pub unsafe extern "C" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int { callback_error(state, || { take_userdata::<T>(state); |