Initial design for non-'static scoped userdata

Uses the same UserData trait, and should at least in theory support everything
that 'static UserData does, except that any functions added that rely on
AnyUserData are pretty much useless.

Probably pretty slow and I'm not sure how to make it dramatically faster, which
is a shame because generally when you need non'-static userdata you might be
creating it kind of a lot (if it was long-lived, it would probably be 'static).

Haven't added tests yet, will do that next.
This commit is contained in:
kyren 2018-09-04 03:40:13 -04:00
parent 37165a8201
commit bd00af2bac
10 changed files with 837 additions and 427 deletions

View File

@ -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())

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>> {
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()),

View File

@ -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;

View File

@ -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);

502
src/methods.rs Normal file
View File

@ -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)
})),
);
}
}

View File

@ -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> {

View File

@ -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);

View File

@ -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(())

View File

@ -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 {

View File

@ -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);