Merge pull request #86 from kyren/unstatic

Allow non-'static UserData created from Scope
This commit is contained in:
kyren 2018-09-16 19:42:14 -04:00 committed by GitHub
commit 58b991b83b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 830 additions and 280 deletions

View File

@ -127,7 +127,7 @@ fn guided_tour() -> Result<()> {
struct Vec2(f32, f32); struct Vec2(f32, f32);
impl UserData for Vec2 { 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, ()| { methods.add_method("magnitude", |_, vec, ()| {
let mag_squared = vec.0 * vec.0 + vec.1 * vec.1; let mag_squared = vec.0 * vec.0 + vec.1 * vec.1;
Ok(mag_squared.sqrt()) Ok(mag_squared.sqrt())

View File

@ -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>> { fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::UserData(lua.create_userdata(self)?)) 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> { fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<T> {
match value { match value {
Value::UserData(ud) => Ok(ud.borrow::<T>()?.clone()), Value::UserData(ud) => Ok(ud.borrow::<T>()?.clone()),

View File

@ -4,6 +4,7 @@ use std::collections::HashMap;
use std::ffi::CString; use std::ffi::CString;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::string::String as StdString;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::{mem, ptr, str}; use std::{mem, ptr, str};
@ -20,8 +21,9 @@ use types::{Callback, Integer, LightUserData, LuaRef, Number, RegistryKey};
use userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods}; use userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
use util::{ use util::{
assert_stack, callback_error, check_stack, gc_guard, get_userdata, get_wrapped_error, 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, init_error_metatables, init_userdata_metatable, main_state, pop_error, protect_lua,
push_userdata, push_wrapped_error, safe_pcall, safe_xpcall, userdata_destructor, StackGuard, 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}; 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. /// Create a Lua userdata object from a custom userdata type.
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData> pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData>
where where
T: Send + UserData, T: 'static + Send + UserData,
{ {
unsafe { self.make_userdata(data) } 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 /// 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 /// 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 /// 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 /// 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 /// 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 /// 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 /// thread while `Scope` is live, it is safe to allow !Send datatypes and whose lifetimes only
/// lifetimes only outlive the scope lifetime. /// outlive the scope lifetime.
/// ///
/// Handles that `Lua::scope` produces have a `'lua` lifetime of the scope parameter, to prevent /// 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 /// 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> { pub(crate) unsafe fn userdata_metatable<T: 'static + 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
}
}
if let Some(table_id) = (*extra_data(self.state)) if let Some(table_id) = (*extra_data(self.state))
.registered_userdata .registered_userdata
.get(&TypeId::of::<T>()) .get(&TypeId::of::<T>())
@ -797,27 +779,29 @@ impl Lua {
} }
let _sg = StackGuard::new(self.state); let _sg = StackGuard::new(self.state);
assert_stack(self.state, 6); assert_stack(self.state, 8);
let mut methods = UserDataMethods { let mut methods = StaticUserDataMethods::default();
methods: HashMap::new(),
meta_methods: HashMap::new(),
_type: PhantomData,
};
T::add_methods(&mut methods); T::add_methods(&mut methods);
protect_lua_closure(self.state, 0, 1, |state| { protect_lua_closure(self.state, 0, 1, |state| {
ffi::lua_newtable(state); ffi::lua_newtable(state);
})?; })?;
for (k, m) in methods.meta_methods {
push_string(self.state, k.name())?;
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 { if methods.methods.is_empty() {
push_string(self.state, "__index")?; init_userdata_metatable::<RefCell<T>>(self.state, -1, None)?;
} else {
protect_lua_closure(self.state, 0, 1, |state| { protect_lua_closure(self.state, 0, 1, |state| {
ffi::lua_newtable(state); ffi::lua_newtable(state);
})?; })?;
for (k, m) in methods.methods { for (k, m) in methods.methods {
push_string(self.state, &k)?; push_string(self.state, &k)?;
self.push_value(Value::Function(self.create_callback(m)?)); self.push_value(Value::Function(self.create_callback(m)?));
@ -826,70 +810,10 @@ impl Lua {
})?; })?;
} }
protect_lua_closure(self.state, 3, 1, |state| { init_userdata_metatable::<RefCell<T>>(self.state, -2, Some(-1))?;
ffi::lua_rawset(state, -3); 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, || { let id = gc_guard(self.state, || {
ffi::luaL_ref(self.state, ffi::LUA_REGISTRYINDEX) ffi::luaL_ref(self.state, ffi::LUA_REGISTRYINDEX)
}); });
@ -899,6 +823,14 @@ impl Lua {
Ok(id) Ok(id)
} }
// Creates a Function out of a Callback containing a 'static Fn. This is safe ONLY because the
// Fn is 'static, otherwise it could capture 'callback arguments improperly. Without ATCs, we
// cannot easily deal with the "correct" callback type of:
//
// Box<for<'lua> Fn(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>>)>
//
// So we instead use a caller provided lifetime, which without the 'static requirement would be
// unsafe.
pub(crate) fn create_callback<'lua, 'callback>( pub(crate) fn create_callback<'lua, 'callback>(
&'lua self, &'lua self,
func: Callback<'callback, 'static>, func: Callback<'callback, 'static>,
@ -965,7 +897,7 @@ impl Lua {
// Does not require Send bounds, which can lead to unsafety. // Does not require Send bounds, which can lead to unsafety.
pub(crate) unsafe fn make_userdata<T>(&self, data: T) -> Result<AnyUserData> pub(crate) unsafe fn make_userdata<T>(&self, data: T) -> Result<AnyUserData>
where where
T: UserData, T: 'static + UserData,
{ {
let _sg = StackGuard::new(self.state); let _sg = StackGuard::new(self.state);
assert_stack(self.state, 4); assert_stack(self.state, 4);
@ -1130,3 +1062,170 @@ unsafe fn ref_stack_pop(extra: *mut ExtraData) -> c_int {
} }
static FUNCTION_METATABLE_REGISTRY_KEY: u8 = 0; static FUNCTION_METATABLE_REGISTRY_KEY: u8 = 0;
struct StaticUserDataMethods<'lua, T: 'static + UserData> {
methods: HashMap<StdString, Callback<'lua, 'static>>,
meta_methods: HashMap<MetaMethod, Callback<'lua, 'static>>,
_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)
})
}
}

View File

@ -1,16 +1,23 @@
use std::any::Any; use std::any::Any;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem; use std::mem;
use std::os::raw::c_void;
use std::rc::Rc;
use std::string::String as StdString;
use error::{Error, Result}; use error::{Error, Result};
use ffi; use ffi;
use function::Function; use function::Function;
use lua::Lua; use lua::Lua;
use types::Callback; use types::Callback;
use userdata::{AnyUserData, UserData}; use userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
use util::{assert_stack, take_userdata, StackGuard}; use util::{
use value::{FromLuaMulti, ToLuaMulti}; 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 /// Constructed by the [`Lua::scope`] method, allows temporarily passing to Lua userdata that is
/// !Send, and callbacks that are !Send and not 'static. /// !Send, and callbacks that are !Send and not 'static.
@ -47,31 +54,19 @@ impl<'scope> Scope<'scope> {
R: ToLuaMulti<'lua>, R: ToLuaMulti<'lua>,
F: 'scope + Fn(&'lua Lua, A) -> Result<R>, F: 'scope + Fn(&'lua Lua, A) -> Result<R>,
{ {
// Safe, because 'scope must outlive 'lua (due to Self containing 'scope), however the
// callback itself must be 'scope lifetime, so the function should not be able to capture
// anything of 'lua lifetime. 'scope can't be shortened due to being invariant, and the
// 'lua lifetime here can't be enlarged due to coming from a universal quantification in
// Lua::scope.
//
// I hope I got this explanation right, but in any case this is tested with compiletest_rs
// to make sure callbacks can't capture handles with lifetimes outside the scope, inside the
// scope, and owned inside the callback itself.
unsafe { 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) 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,15 +95,17 @@ impl<'scope> Scope<'scope> {
/// Create a Lua userdata object from a custom userdata type. /// 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 /// 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 /// drop, and does not require that the userdata type be Send (but still requires that the
/// details. /// UserData be 'static). See [`Lua::scope`] for more details.
/// ///
/// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata /// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata
/// [`Lua::scope`]: struct.Lua.html#method.scope /// [`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 where
T: UserData, T: 'static + UserData,
{ {
// Safe even though T may not be Send, because the parent Lua cannot be sent to another
// thread while the Scope is alive (or the returned AnyUserData handle even).
unsafe { unsafe {
let u = self.lua.make_userdata(data)?; let u = self.lua.make_userdata(data)?;
let mut destructors = self.destructors.borrow_mut(); let mut destructors = self.destructors.borrow_mut();
@ -123,6 +120,193 @@ impl<'scope> Scope<'scope> {
Ok(u) 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. This is safe
// though, because actual method callbacks are all 'static so they can't capture 'callback
// handles anyway.
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, k.name())?;
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. 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> { impl<'scope> Drop for Scope<'scope> {
@ -140,3 +324,138 @@ impl<'scope> Drop for Scope<'scope> {
drop(to_drop); drop(to_drop);
} }
} }
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>>>),
}
struct NonStaticUserDataMethods<'lua, T: UserData> {
methods: HashMap<StdString, NonStaticMethod<'lua, T>>,
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)
})),
);
}
}

View File

@ -1,7 +1,7 @@
use std::cell::Cell; use std::cell::Cell;
use std::rc::Rc; use std::rc::Rc;
use {Error, Function, Lua, String, UserData, UserDataMethods}; use {Error, Function, Lua, MetaMethod, String, UserData, UserDataMethods};
#[test] #[test]
fn scope_func() { fn scope_func() {
@ -40,7 +40,7 @@ fn scope_drop() {
struct MyUserdata(Rc<()>); struct MyUserdata(Rc<()>);
impl UserData for MyUserdata { 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(())); methods.add_method("method", |_, _, ()| Ok(()));
} }
} }
@ -51,7 +51,9 @@ fn scope_drop() {
lua.globals() lua.globals()
.set( .set(
"test", "test",
scope.create_userdata(MyUserdata(rc.clone())).unwrap(), scope
.create_static_userdata(MyUserdata(rc.clone()))
.unwrap(),
) )
.unwrap(); .unwrap();
assert_eq!(Rc::strong_count(&rc), 2); assert_eq!(Rc::strong_count(&rc), 2);
@ -98,3 +100,136 @@ fn outer_lua_access() {
}); });
assert_eq!(table.get::<_, String>("a").unwrap(), "b"); assert_eq!(table.get::<_, String>("a").unwrap(), "b");
} }
#[test]
fn scope_userdata_methods() {
struct MyUserData<'a>(&'a Cell<i64>);
impl<'a> UserData for MyUserData<'a> {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("inc", |_, data, ()| {
data.0.set(data.0.get() + 1);
Ok(())
});
methods.add_method("dec", |_, data, ()| {
data.0.set(data.0.get() - 1);
Ok(())
});
}
}
let lua = Lua::new();
let i = Cell::new(42);
lua.scope(|scope| {
let f: Function =
lua.eval(
r#"
function(u)
u:inc()
u:inc()
u:inc()
u:dec()
end
"#,
None,
).unwrap();
f.call::<_, ()>(scope.create_userdata(MyUserData(&i)).unwrap())
.unwrap();
});
assert_eq!(i.get(), 44);
}
#[test]
fn scope_userdata_functions() {
struct MyUserData<'a>(&'a i64);
impl<'a> UserData for MyUserData<'a> {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_function(MetaMethod::Add, |lua, ()| {
let globals = lua.globals();
globals.set("i", globals.get::<_, i64>("i")? + 1)?;
Ok(())
});
methods.add_meta_function(MetaMethod::Sub, |lua, ()| {
let globals = lua.globals();
globals.set("i", globals.get::<_, i64>("i")? + 1)?;
Ok(())
});
}
}
let lua = Lua::new();
let f =
lua.exec::<Function>(
r#"
i = 0
return function(u)
_ = u + u
_ = u - 1
_ = 1 + u
end
"#,
None,
).unwrap();
let dummy = 0;
lua.scope(|scope| {
f.call::<_, ()>(scope.create_userdata(MyUserData(&dummy)).unwrap())
.unwrap();
});
assert_eq!(lua.globals().get::<_, i64>("i").unwrap(), 3);
}
#[test]
fn scope_userdata_mismatch() {
struct MyUserData<'a>(&'a Cell<i64>);
impl<'a> UserData for MyUserData<'a> {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("inc", |_, data, ()| {
data.0.set(data.0.get() + 1);
Ok(())
});
}
}
let lua = Lua::new();
lua.exec::<()>(
r#"
function okay(a, b)
a.inc(a)
b.inc(b)
end
function bad(a, b)
a.inc(b)
end
"#,
None,
).unwrap();
let a = Cell::new(1);
let b = Cell::new(1);
let okay: Function = lua.globals().get("okay").unwrap();
let bad: Function = lua.globals().get("bad").unwrap();
lua.scope(|scope| {
let au = scope.create_userdata(MyUserData(&a)).unwrap();
let bu = scope.create_userdata(MyUserData(&b)).unwrap();
assert!(okay.call::<_, ()>((au.clone(), bu.clone())).is_ok());
match bad.call::<_, ()>((au, bu)) {
Err(Error::CallbackError { ref cause, .. }) => match *cause.as_ref() {
Error::UserDataTypeMismatch => {}
ref other => panic!("wrong error type {:?}", other),
},
Err(other) => panic!("wrong error type {:?}", other),
Ok(_) => panic!("incorrectly returned Ok"),
}
});
}

View File

@ -15,10 +15,10 @@ fn test_user_data() {
let userdata1 = lua.create_userdata(UserData1(1)).unwrap(); let userdata1 = lua.create_userdata(UserData1(1)).unwrap();
let userdata2 = lua.create_userdata(UserData2(Box::new(2))).unwrap(); let userdata2 = lua.create_userdata(UserData2(Box::new(2))).unwrap();
assert!(userdata1.is::<UserData1>().unwrap()); assert!(userdata1.is::<UserData1>());
assert!(!userdata1.is::<UserData2>().unwrap()); assert!(!userdata1.is::<UserData2>());
assert!(userdata2.is::<UserData2>().unwrap()); assert!(userdata2.is::<UserData2>());
assert!(!userdata2.is::<UserData1>().unwrap()); assert!(!userdata2.is::<UserData1>());
assert_eq!(userdata1.borrow::<UserData1>().unwrap().0, 1); assert_eq!(userdata1.borrow::<UserData1>().unwrap().0, 1);
assert_eq!(*userdata2.borrow::<UserData2>().unwrap().0, 2); assert_eq!(*userdata2.borrow::<UserData2>().unwrap().0, 2);
@ -29,7 +29,7 @@ fn test_methods() {
struct MyUserData(i64); struct MyUserData(i64);
impl UserData for MyUserData { 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("get_value", |_, data, ()| Ok(data.0));
methods.add_method_mut("set_value", |_, data, args| { methods.add_method_mut("set_value", |_, data, args| {
data.0 = args; data.0 = args;
@ -69,7 +69,7 @@ fn test_metamethods() {
struct MyUserData(i64); struct MyUserData(i64);
impl UserData for MyUserData { 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_method("get", |_, data, ()| Ok(data.0));
methods.add_meta_function( methods.add_meta_function(
MetaMethod::Add, MetaMethod::Add,
@ -117,7 +117,7 @@ fn test_gc_userdata() {
} }
impl UserData for MyUserdata { 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, ()| { methods.add_method("access", |_, this, ()| {
assert!(this.id == 123); assert!(this.id == 123);
Ok(()) Ok(())

View File

@ -1,12 +1,9 @@
use std::cell::{Ref, RefCell, RefMut}; 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 error::{Error, Result};
use ffi; use ffi;
use lua::Lua; use lua::Lua;
use types::{Callback, LuaRef}; use types::LuaRef;
use util::{assert_stack, get_userdata, StackGuard}; use util::{assert_stack, get_userdata, StackGuard};
use value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti}; use value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti};
@ -68,16 +65,40 @@ pub enum MetaMethod {
ToString, ToString,
} }
impl MetaMethod {
pub(crate) fn name(self) -> &'static str {
match self {
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. /// Method registry for [`UserData`] implementors.
/// ///
/// [`UserData`]: trait.UserData.html /// [`UserData`]: trait.UserData.html
pub struct UserDataMethods<'lua, T> { pub trait UserDataMethods<'lua, T: 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: UserData> UserDataMethods<'lua, T> {
/// Add a method which accepts a `&T` as the first parameter. /// Add a method which accepts a `&T` as the first parameter.
/// ///
/// Regular methods are implemented by overriding the `__index` metamethod and returning the /// Regular methods are implemented by overriding the `__index` metamethod and returning the
@ -85,30 +106,22 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> {
/// ///
/// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will /// 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. /// 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) fn add_method<A, R, M>(&mut self, name: &str, method: M)
where where
A: FromLuaMulti<'lua>, A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>, R: ToLuaMulti<'lua>,
M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>, 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. /// Add a regular method which accepts a `&mut T` as the first parameter.
/// ///
/// Refer to [`add_method`] for more information about the implementation. /// Refer to [`add_method`] for more information about the implementation.
/// ///
/// [`add_method`]: #method.add_method /// [`add_method`]: #method.add_method
pub fn add_method_mut<A, R, M>(&mut self, name: &str, method: M) fn add_method_mut<A, R, M>(&mut self, name: &str, method: M)
where where
A: FromLuaMulti<'lua>, A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>, R: ToLuaMulti<'lua>,
M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>, 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 /// Add a regular method as a function which accepts generic arguments, the first argument will
/// always be a `UserData` of type T. /// always be a `UserData` of type T.
@ -117,15 +130,11 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> {
/// ///
/// [`add_method`]: #method.add_method /// [`add_method`]: #method.add_method
/// [`add_method_mut`]: #method.add_method_mut /// [`add_method_mut`]: #method.add_method_mut
pub fn add_function<A, R, F>(&mut self, name: &str, function: F) fn add_function<A, R, F>(&mut self, name: &str, function: F)
where where
A: FromLuaMulti<'lua>, A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>, R: ToLuaMulti<'lua>,
F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>, 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 /// Add a regular method as a mutable function which accepts generic arguments, the first
/// argument will always be a `UserData` of type T. /// argument will always be a `UserData` of type T.
@ -133,15 +142,11 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> {
/// This is a version of [`add_function`] that accepts a FnMut argument. /// This is a version of [`add_function`] that accepts a FnMut argument.
/// ///
/// [`add_function`]: #method.add_function /// [`add_function`]: #method.add_function
pub fn add_function_mut<A, R, F>(&mut self, name: &str, function: F) fn add_function_mut<A, R, F>(&mut self, name: &str, function: F)
where where
A: FromLuaMulti<'lua>, A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>, R: ToLuaMulti<'lua>,
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>, 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. /// Add a metamethod which accepts a `&T` as the first parameter.
/// ///
@ -151,14 +156,11 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> {
/// side has a metatable. To prevent this, use [`add_meta_function`]. /// side has a metatable. To prevent this, use [`add_meta_function`].
/// ///
/// [`add_meta_function`]: #method.add_meta_function /// [`add_meta_function`]: #method.add_meta_function
pub fn add_meta_method<A, R, M>(&mut self, meta: MetaMethod, method: M) fn add_meta_method<A, R, M>(&mut self, meta: MetaMethod, method: M)
where where
A: FromLuaMulti<'lua>, A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>, R: ToLuaMulti<'lua>,
M: 'static + Send + Fn(&'lua Lua, &T, A) -> Result<R>, 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. /// Add a metamethod as a function which accepts a `&mut T` as the first parameter.
/// ///
@ -168,113 +170,33 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> {
/// side has a metatable. To prevent this, use [`add_meta_function`]. /// side has a metatable. To prevent this, use [`add_meta_function`].
/// ///
/// [`add_meta_function`]: #method.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) fn add_meta_method_mut<A, R, M>(&mut self, meta: MetaMethod, method: M)
where where
A: FromLuaMulti<'lua>, A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>, R: ToLuaMulti<'lua>,
M: 'static + Send + FnMut(&'lua Lua, &mut T, A) -> Result<R>, 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. /// Add a metamethod which accepts generic arguments.
/// ///
/// Metamethods for binary operators can be triggered if either the left or right argument to /// 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 /// the binary operator has a metatable, so the first argument here is not necessarily a
/// userdata of type `T`. /// userdata of type `T`.
pub fn add_meta_function<A, R, F>(&mut self, meta: MetaMethod, function: F) fn add_meta_function<A, R, F>(&mut self, meta: MetaMethod, function: F)
where where
A: FromLuaMulti<'lua>, A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>, R: ToLuaMulti<'lua>,
F: 'static + Send + Fn(&'lua Lua, A) -> Result<R>, 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. /// Add a metamethod as a mutable function which accepts generic arguments.
/// ///
/// This is a version of [`add_meta_function`] that accepts a FnMut argument. /// This is a version of [`add_meta_function`] that accepts a FnMut argument.
/// ///
/// [`add_meta_function`]: #method.add_meta_function /// [`add_meta_function`]: #method.add_meta_function
pub fn add_meta_function_mut<A, R, F>(&mut self, meta: MetaMethod, function: F) fn add_meta_function_mut<A, R, F>(&mut self, meta: MetaMethod, function: F)
where where
A: FromLuaMulti<'lua>, A: FromLuaMulti<'lua>,
R: ToLuaMulti<'lua>, R: ToLuaMulti<'lua>,
F: 'static + Send + FnMut(&'lua Lua, A) -> Result<R>, 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,
})
}
})
}
} }
/// Trait for custom userdata types. /// Trait for custom userdata types.
@ -315,7 +237,7 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> {
/// struct MyUserData(i32); /// struct MyUserData(i32);
/// ///
/// impl UserData for MyUserData { /// 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, _: ()| { /// methods.add_method("get", |_, this, _: ()| {
/// Ok(this.0) /// Ok(this.0)
/// }); /// });
@ -350,10 +272,10 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> {
/// ///
/// [`ToLua`]: trait.ToLua.html /// [`ToLua`]: trait.ToLua.html
/// [`FromLua`]: trait.FromLua.html /// [`FromLua`]: trait.FromLua.html
/// [`UserDataMethods`]: struct.UserDataMethods.html /// [`UserDataMethods`]: trait.UserDataMethods.html
pub trait UserData: 'static + Sized { pub trait UserData: Sized {
/// Adds custom methods and operators specific to this userdata. /// 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`]. /// Handle to an internal Lua userdata for any type that implements [`UserData`].
@ -377,11 +299,11 @@ pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>);
impl<'lua> AnyUserData<'lua> { impl<'lua> AnyUserData<'lua> {
/// Checks whether the type of this userdata is `T`. /// 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(())) { match self.inspect(|_: &RefCell<T>| Ok(())) {
Ok(()) => Ok(true), Ok(()) => true,
Err(Error::UserDataTypeMismatch) => Ok(false), Err(Error::UserDataTypeMismatch) => false,
Err(err) => Err(err), Err(_) => unreachable!(),
} }
} }
@ -391,7 +313,7 @@ impl<'lua> AnyUserData<'lua> {
/// ///
/// Returns a `UserDataBorrowError` if the userdata is already mutably borrowed. Returns a /// Returns a `UserDataBorrowError` if the userdata is already mutably borrowed. Returns a
/// `UserDataTypeMismatch` if the userdata is not of type `T`. /// `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)?)) self.inspect(|cell| Ok(cell.try_borrow().map_err(|_| Error::UserDataBorrowError)?))
} }
@ -401,7 +323,7 @@ impl<'lua> AnyUserData<'lua> {
/// ///
/// Returns a `UserDataBorrowMutError` if the userdata is already borrowed. Returns a /// Returns a `UserDataBorrowMutError` if the userdata is already borrowed. Returns a
/// `UserDataTypeMismatch` if the userdata is not of type `T`. /// `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| { self.inspect(|cell| {
Ok(cell Ok(cell
.try_borrow_mut() .try_borrow_mut()
@ -444,7 +366,7 @@ impl<'lua> AnyUserData<'lua> {
fn inspect<'a, T, R, F>(&'a self, func: F) -> Result<R> fn inspect<'a, T, R, F>(&'a self, func: F) -> Result<R>
where where
T: UserData, T: 'static + UserData,
F: FnOnce(&'a RefCell<T>) -> Result<R>, F: FnOnce(&'a RefCell<T>) -> Result<R>,
{ {
unsafe { unsafe {

View File

@ -250,6 +250,81 @@ pub unsafe fn take_userdata<T>(state: *mut ffi::lua_State) -> T {
ptr::read(ud) 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 { pub unsafe extern "C" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
callback_error(state, || { callback_error(state, || {
take_userdata::<T>(state); take_userdata::<T>(state);