Drop Scope support
This commit is contained in:
parent
ffc6aa1d33
commit
ebe6c66e9b
|
@ -164,34 +164,6 @@ fn main() -> Result<()> {
|
|||
< f32::EPSILON
|
||||
);
|
||||
|
||||
// Normally, Rust types passed to `Lua` must be `Send`, because `Lua` itself is `Send`, and
|
||||
// must be `'static`, because there is no way to be sure of their lifetime inside the Lua
|
||||
// state. There is, however, a limited way to lift both of these requirements. You can
|
||||
// call `Lua::scope` to create userdata and callbacks types that only live for as long
|
||||
// as the call to scope, but do not have to be `Send` OR `'static`.
|
||||
|
||||
// {
|
||||
// let mut rust_val = 0;
|
||||
|
||||
// lua.scope(|scope| {
|
||||
// // We create a 'sketchy' Lua callback that holds a mutable reference to the variable
|
||||
// // `rust_val`. Outside of a `Lua::scope` call, this would not be allowed
|
||||
// // because it could be unsafe.
|
||||
|
||||
// lua.globals().set(
|
||||
// "sketchy",
|
||||
// scope.create_function_mut(|_, ()| {
|
||||
// rust_val = 42;
|
||||
// Ok(())
|
||||
// })?,
|
||||
// )?;
|
||||
|
||||
// lua.load("sketchy()").exec()
|
||||
// })?;
|
||||
|
||||
// assert_eq!(rust_val, 42);
|
||||
// }
|
||||
|
||||
// We were able to run our 'sketchy' function inside the scope just fine. However, if we
|
||||
// try to run our 'sketchy' function outside of the scope, the function we created will have
|
||||
// been invalidated and we will generate an error. If our function wasn't invalidated, we
|
||||
|
|
|
@ -49,7 +49,6 @@ mod ffi;
|
|||
mod function;
|
||||
mod lua;
|
||||
mod multi;
|
||||
mod scope;
|
||||
mod stdlib;
|
||||
mod string;
|
||||
mod table;
|
||||
|
@ -65,7 +64,6 @@ pub use crate::error::{Error, ExternalError, ExternalResult, Result};
|
|||
pub use crate::function::Function;
|
||||
pub use crate::lua::{Chunk, Lua};
|
||||
pub use crate::multi::Variadic;
|
||||
pub use crate::scope::Scope;
|
||||
pub use crate::stdlib::StdLib;
|
||||
pub use crate::string::String;
|
||||
pub use crate::table::{Table, TablePairs, TableSequence};
|
||||
|
|
25
src/lua.rs
25
src/lua.rs
|
@ -22,7 +22,6 @@ use {
|
|||
use crate::error::{Error, Result};
|
||||
use crate::ffi;
|
||||
use crate::function::Function;
|
||||
use crate::scope::Scope;
|
||||
use crate::stdlib::StdLib;
|
||||
use crate::string::String;
|
||||
use crate::table::Table;
|
||||
|
@ -589,30 +588,6 @@ impl Lua {
|
|||
}
|
||||
}
|
||||
|
||||
/// Calls the given function with a `Scope` parameter, giving the function the ability to create
|
||||
/// userdata and callbacks from rust types that are 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.
|
||||
///
|
||||
/// Inside the scope callback, all handles created through Scope will share the same unique 'lua
|
||||
/// lifetime of the parent `Lua`. This allows scoped and non-scoped values to be mixed in
|
||||
/// API calls, which is very useful (e.g. passing a scoped userdata to a non-scoped function).
|
||||
/// However, this also enables handles to scoped values to be trivially leaked from the given
|
||||
/// callback. This is not dangerous, though! After the callback returns, all scoped values are
|
||||
/// invalidated, which means that though references may exist, the Rust types backing them have
|
||||
/// dropped. `Function` types will error when called, and `AnyUserData` will be typeless. It
|
||||
/// would be impossible to prevent handles to scoped values from escaping anyway, since you
|
||||
/// would always be able to smuggle them through Lua state.
|
||||
pub fn scope<'scope, F, T>(&self, f: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(&Scope<'scope>) -> Result<T>,
|
||||
{
|
||||
f(&Scope::new(self))
|
||||
}
|
||||
|
||||
/// Attempts to coerce a Lua value into a String in a manner consistent with Lua's internal
|
||||
/// behavior.
|
||||
///
|
||||
|
|
474
src/scope.rs
474
src/scope.rs
|
@ -1,474 +0,0 @@
|
|||
use std::any::Any;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::os::raw::c_void;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::ffi;
|
||||
use crate::function::Function;
|
||||
use crate::lua::Lua;
|
||||
use crate::types::{Callback, LuaRef};
|
||||
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
|
||||
use crate::util::{
|
||||
assert_stack, init_userdata_metatable, protect_lua_closure, push_string, push_userdata,
|
||||
take_userdata, StackGuard,
|
||||
};
|
||||
use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti, Value};
|
||||
|
||||
/// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and
|
||||
/// callbacks that are not required to be 'static.
|
||||
///
|
||||
/// See [`Lua::scope`] for more details.
|
||||
///
|
||||
/// [`Lua::scope`]: struct.Lua.html#method.scope
|
||||
pub struct Scope<'scope> {
|
||||
lua: Lua,
|
||||
destructors: RefCell<Vec<(LuaRef, fn(LuaRef) -> Box<dyn Any>)>>,
|
||||
_scope_invariant: PhantomData<Cell<&'scope ()>>,
|
||||
}
|
||||
|
||||
impl<'scope> Scope<'scope> {
|
||||
pub(crate) fn new(lua: &Lua) -> Scope<'scope> {
|
||||
Scope {
|
||||
lua: lua.clone(),
|
||||
destructors: RefCell::new(Vec::new()),
|
||||
_scope_invariant: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps a Rust function or closure, creating a callable Lua function handle to it.
|
||||
///
|
||||
/// This is a version of [`Lua::create_function`] that creates a callback which expires on
|
||||
/// scope drop. See [`Lua::scope`] for more details.
|
||||
///
|
||||
/// [`Lua::create_function`]: struct.Lua.html#method.create_function
|
||||
/// [`Lua::scope`]: struct.Lua.html#method.scope
|
||||
pub fn create_function<A, R, F>(&self, func: F) -> Result<Function>
|
||||
where
|
||||
A: FromLuaMulti,
|
||||
R: ToLuaMulti,
|
||||
F: 'scope + Fn(&Lua, A) -> Result<R>,
|
||||
{
|
||||
// Safe, because 'scope must outlive 'callback (due to Self containing 'scope), however the
|
||||
// callback itself must be 'scope lifetime, so the function should not be able to capture
|
||||
// anything of 'callback lifetime. 'scope can't be shortened due to being invariant, and
|
||||
// the 'callback 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 lifetime outside the scope, inside the
|
||||
// scope, and owned inside the callback itself.
|
||||
unsafe {
|
||||
self.create_callback(Box::new(move |ref lua, args| {
|
||||
func(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps a Rust mutable closure, creating a callable Lua function handle to it.
|
||||
///
|
||||
/// This is a version of [`Lua::create_function_mut`] that creates a callback which expires
|
||||
/// on scope drop. See [`Lua::scope`] and [`Scope::create_function`] for more details.
|
||||
///
|
||||
/// [`Lua::create_function_mut`]: struct.Lua.html#method.create_function_mut
|
||||
/// [`Lua::scope`]: struct.Lua.html#method.scope
|
||||
/// [`Scope::create_function`]: #method.create_function
|
||||
pub fn create_function_mut<A, R, F>(&self, func: F) -> Result<Function>
|
||||
where
|
||||
A: FromLuaMulti,
|
||||
R: ToLuaMulti,
|
||||
F: 'scope + FnMut(&Lua, A) -> Result<R>,
|
||||
{
|
||||
let func = RefCell::new(func);
|
||||
self.create_function(move |lua, args| {
|
||||
(&mut *func
|
||||
.try_borrow_mut()
|
||||
.map_err(|_| Error::RecursiveMutCallback)?)(lua, args)
|
||||
})
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// 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_static_userdata<T>(&self, data: T) -> Result<AnyUserData>
|
||||
where
|
||||
T: 'static + UserData,
|
||||
{
|
||||
unsafe {
|
||||
let u = self.lua.make_userdata(data)?;
|
||||
self.destructors.borrow_mut().push((u.0.clone(), |u| {
|
||||
let state = u.lua.state;
|
||||
assert_stack(state, 2);
|
||||
u.lua.push_ref(&u);
|
||||
// We know the destructor has not run yet because we hold a reference to the
|
||||
// userdata.
|
||||
Box::new(take_userdata::<RefCell<T>>(state))
|
||||
}));
|
||||
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 'static. See
|
||||
/// [`Lua::scope`] for more details.
|
||||
///
|
||||
/// 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_nonstatic_userdata<T>(&self, data: T) -> Result<AnyUserData>
|
||||
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 more correct callback type that is universally quantified over
|
||||
// 'lua. This is safe though, because `UserData::add_methods` does not get to pick the 'lua
|
||||
// lifetime, so none of the static methods UserData types can add can possibly capture
|
||||
// parameters.
|
||||
fn wrap_method<'scope, T: 'scope>(
|
||||
scope: &Scope<'scope>,
|
||||
data: Rc<RefCell<T>>,
|
||||
method: NonStaticMethod<T>,
|
||||
) -> Result<Function> {
|
||||
// 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_nonstatic_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 {
|
||||
assert_stack(lua.state, 1);
|
||||
lua.push_ref(&u.0);
|
||||
ffi::lua_getuservalue(lua.state, -1);
|
||||
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
|
||||
{
|
||||
ffi::lua_pushinteger(lua.state, 1);
|
||||
ffi::lua_gettable(lua.state, -2);
|
||||
ffi::lua_remove(lua.state, -2);
|
||||
}
|
||||
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 |ref lua, mut args: MultiValue| {
|
||||
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 |ref lua, mut args: MultiValue| {
|
||||
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 {
|
||||
let f = Box::new(move |ref lua, args: MultiValue| function(lua, args));
|
||||
scope.create_callback(f)
|
||||
},
|
||||
NonStaticMethod::FunctionMut(function) => {
|
||||
let function = RefCell::new(function);
|
||||
let f = Box::new(move |ref 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, ())?;
|
||||
#[cfg(feature = "lua53")]
|
||||
ffi::lua_pushlightuserdata(lua.state, data.as_ptr() as *mut c_void);
|
||||
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
|
||||
protect_lua_closure(lua.state, 0, 1, |state| {
|
||||
// Lua 5.2/5.1 allows to store only table. Then we will wrap the value.
|
||||
ffi::lua_createtable(state, 1, 0);
|
||||
ffi::lua_pushinteger(state, 1);
|
||||
ffi::lua_pushlightuserdata(state, data.as_ptr() as *mut c_void);
|
||||
ffi::lua_settable(state, -3);
|
||||
})?;
|
||||
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 can improperly capture any value with 'callback scope, such as
|
||||
// improperly capturing an argument. Since the 'callback lifetime is chosen by the user and the
|
||||
// lifetime of the callback itself is 'scope (non-'static), the borrow checker will happily pick
|
||||
// a 'callback that outlives 'scope to allow this. In order for this to be safe, the callback
|
||||
// must NOT capture any parameters.
|
||||
unsafe fn create_callback(&self, f: Callback<'scope>) -> Result<Function> {
|
||||
let f = mem::transmute::<Callback<'scope>, Callback<'static>>(f);
|
||||
let f = self.lua.create_callback(f)?;
|
||||
|
||||
let mut destructors = self.destructors.borrow_mut();
|
||||
destructors.push((f.0.clone(), |f| {
|
||||
let state = f.lua.state;
|
||||
assert_stack(state, 3);
|
||||
f.lua.push_ref(&f);
|
||||
|
||||
ffi::lua_getupvalue(state, -1, 1);
|
||||
// We know the destructor has not run yet because we hold a reference to the callback.
|
||||
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> {
|
||||
fn drop(&mut self) {
|
||||
// We separate the action of invalidating the userdata in Lua and actually dropping the
|
||||
// userdata type into two phases. This is so that, in the event a userdata drop panics, we
|
||||
// can be sure that all of the userdata in Lua is actually invalidated.
|
||||
|
||||
// All destructors are non-panicking, so this is fine
|
||||
let to_drop = self
|
||||
.destructors
|
||||
.get_mut()
|
||||
.drain(..)
|
||||
.map(|(r, dest)| dest(r))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
drop(to_drop);
|
||||
}
|
||||
}
|
||||
|
||||
enum NonStaticMethod<T> {
|
||||
Method(Box<dyn Fn(&Lua, &T, MultiValue) -> Result<MultiValue>>),
|
||||
MethodMut(Box<dyn FnMut(&Lua, &mut T, MultiValue) -> Result<MultiValue>>),
|
||||
Function(Box<dyn Fn(&Lua, MultiValue) -> Result<MultiValue>>),
|
||||
FunctionMut(Box<dyn FnMut(&Lua, MultiValue) -> Result<MultiValue>>),
|
||||
}
|
||||
|
||||
struct NonStaticUserDataMethods<T: UserData> {
|
||||
methods: Vec<(Vec<u8>, NonStaticMethod<T>)>,
|
||||
meta_methods: Vec<(MetaMethod, NonStaticMethod<T>)>,
|
||||
}
|
||||
|
||||
impl<T: UserData> Default for NonStaticUserDataMethods<T> {
|
||||
fn default() -> NonStaticUserDataMethods<T> {
|
||||
NonStaticUserDataMethods {
|
||||
methods: Vec::new(),
|
||||
meta_methods: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: UserData> UserDataMethods<T> for NonStaticUserDataMethods<T> {
|
||||
fn add_method<S, A, R, M>(&mut self, name: &S, method: M)
|
||||
where
|
||||
S: ?Sized + AsRef<[u8]>,
|
||||
A: FromLuaMulti,
|
||||
R: ToLuaMulti,
|
||||
M: 'static + Fn(&Lua, &T, A) -> Result<R>,
|
||||
{
|
||||
self.methods.push((
|
||||
name.as_ref().to_vec(),
|
||||
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<S, A, R, M>(&mut self, name: &S, mut method: M)
|
||||
where
|
||||
S: ?Sized + AsRef<[u8]>,
|
||||
A: FromLuaMulti,
|
||||
R: ToLuaMulti,
|
||||
M: 'static + FnMut(&Lua, &mut T, A) -> Result<R>,
|
||||
{
|
||||
self.methods.push((
|
||||
name.as_ref().to_vec(),
|
||||
NonStaticMethod::MethodMut(Box::new(move |lua, ud, args| {
|
||||
method(lua, ud, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
fn add_function<S, A, R, F>(&mut self, name: &S, function: F)
|
||||
where
|
||||
S: ?Sized + AsRef<[u8]>,
|
||||
A: FromLuaMulti,
|
||||
R: ToLuaMulti,
|
||||
F: 'static + Fn(&Lua, A) -> Result<R>,
|
||||
{
|
||||
self.methods.push((
|
||||
name.as_ref().to_vec(),
|
||||
NonStaticMethod::Function(Box::new(move |lua, args| {
|
||||
function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
fn add_function_mut<S, A, R, F>(&mut self, name: &S, mut function: F)
|
||||
where
|
||||
S: ?Sized + AsRef<[u8]>,
|
||||
A: FromLuaMulti,
|
||||
R: ToLuaMulti,
|
||||
F: 'static + FnMut(&Lua, A) -> Result<R>,
|
||||
{
|
||||
self.methods.push((
|
||||
name.as_ref().to_vec(),
|
||||
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,
|
||||
R: ToLuaMulti,
|
||||
M: 'static + Fn(&Lua, &T, A) -> Result<R>,
|
||||
{
|
||||
self.meta_methods.push((
|
||||
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,
|
||||
R: ToLuaMulti,
|
||||
M: 'static + FnMut(&Lua, &mut T, A) -> Result<R>,
|
||||
{
|
||||
self.meta_methods.push((
|
||||
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,
|
||||
R: ToLuaMulti,
|
||||
F: 'static + Fn(&Lua, A) -> Result<R>,
|
||||
{
|
||||
self.meta_methods.push((
|
||||
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,
|
||||
R: ToLuaMulti,
|
||||
F: 'static + FnMut(&Lua, A) -> Result<R>,
|
||||
{
|
||||
self.meta_methods.push((
|
||||
meta,
|
||||
NonStaticMethod::FunctionMut(Box::new(move |lua, args| {
|
||||
function(lua, A::from_lua_multi(args, lua)?)?.to_lua_multi(lua)
|
||||
})),
|
||||
));
|
||||
}
|
||||
}
|
13
src/types.rs
13
src/types.rs
|
@ -27,22 +27,17 @@ pub(crate) type AsyncCallback<'a> =
|
|||
|
||||
/// An auto generated key into the Lua registry.
|
||||
///
|
||||
/// This is a handle to a value stored inside the Lua registry. It is not directly usable like the
|
||||
/// `Table` or `Function` handle types, but since it doesn't hold a reference to a parent Lua and is
|
||||
/// 'static, it is much more flexible and can be used in many situations where it is impossible to
|
||||
/// directly store a normal handle type. It is not automatically garbage collected on Drop,
|
||||
/// but it can be removed with [`Lua::remove_registry_value`], and instances not manually
|
||||
/// removed can be garbage collected with [`Lua::expire_registry_values`].
|
||||
/// This is a handle to a value stored inside the Lua registry. It is not automatically
|
||||
/// garbage collected on Drop, but it can be removed with [`Lua::remove_registry_value`],
|
||||
/// and instances not manually removed can be garbage collected with [`Lua::expire_registry_values`].
|
||||
///
|
||||
/// Be warned, If you place this into Lua via a `UserData` type or a rust callback, it is *very
|
||||
/// easy* to accidentally cause reference cycles that the Lua garbage collector cannot resolve.
|
||||
/// Instead of placing a `RegistryKey` into a `UserData` type, prefer instead to use
|
||||
/// [`UserData::set_user_value`] / [`UserData::get_user_value`], and instead of moving a RegistryKey
|
||||
/// into a callback, prefer [`Lua::scope`].
|
||||
/// [`UserData::set_user_value`] / [`UserData::get_user_value`].
|
||||
///
|
||||
/// [`Lua::remove_registry_value`]: struct.Lua.html#method.remove_registry_value
|
||||
/// [`Lua::expire_registry_values`]: struct.Lua.html#method.expire_registry_values
|
||||
/// [`Lua::scope`]: struct.Lua.html#method.scope
|
||||
/// [`UserData::set_user_value`]: struct.UserData.html#method.set_user_value
|
||||
/// [`UserData::get_user_value`]: struct.UserData.html#method.get_user_value
|
||||
pub struct RegistryKey {
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
use mlua::{Lua, Table, Result};
|
||||
|
||||
struct Test {
|
||||
field: i32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let lua = Lua::new();
|
||||
lua.scope(|scope| -> Result<()> {
|
||||
let mut inner: Option<Table> = None;
|
||||
let f = scope
|
||||
.create_function_mut(|_, t: Table| {
|
||||
inner = Some(t);
|
||||
Ok(())
|
||||
})?;
|
||||
f.call::<_, ()>(lua.create_table()?)?;
|
||||
Ok(())
|
||||
});
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
error[E0373]: closure may outlive the current function, but it borrows `inner`, which is owned by the current function
|
||||
--> $DIR/scope_callback_inner.rs:12:34
|
||||
|
|
||||
9 | lua.scope(|scope| -> Result<()> {
|
||||
| ----- has type `&mlua::scope::Scope<'1>`
|
||||
...
|
||||
12 | .create_function_mut(|_, t: Table| {
|
||||
| ^^^^^^^^^^^^^ may outlive borrowed value `inner`
|
||||
13 | inner = Some(t);
|
||||
| ----- `inner` is borrowed here
|
||||
|
|
||||
note: function requires argument type to outlive `'1`
|
||||
--> $DIR/scope_callback_inner.rs:11:17
|
||||
|
|
||||
11 | let f = scope
|
||||
| _________________^
|
||||
12 | | .create_function_mut(|_, t: Table| {
|
||||
13 | | inner = Some(t);
|
||||
14 | | Ok(())
|
||||
15 | | })?;
|
||||
| |______________^
|
||||
help: to force the closure to take ownership of `inner` (and any other referenced variables), use the `move` keyword
|
||||
|
|
||||
12 | .create_function_mut(move |_, t: Table| {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
|
@ -1,23 +0,0 @@
|
|||
use mlua::{Lua, Result};
|
||||
|
||||
struct Test {
|
||||
field: i32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let lua = Lua::new();
|
||||
lua.scope(|scope| -> Result<()> {
|
||||
let f = {
|
||||
let mut test = Test { field: 0 };
|
||||
|
||||
scope
|
||||
.create_function_mut(|_, ()| {
|
||||
test.field = 42;
|
||||
//~^ error: `test` does not live long enough
|
||||
Ok(())
|
||||
})?
|
||||
};
|
||||
|
||||
f.call::<_, ()>(())
|
||||
});
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
error[E0373]: closure may outlive the current function, but it borrows `test`, which is owned by the current function
|
||||
--> $DIR/scope_invariance.rs:14:38
|
||||
|
|
||||
9 | lua.scope(|scope| -> Result<()> {
|
||||
| ----- has type `&mlua::scope::Scope<'1>`
|
||||
...
|
||||
14 | .create_function_mut(|_, ()| {
|
||||
| ^^^^^^^ may outlive borrowed value `test`
|
||||
15 | test.field = 42;
|
||||
| ---- `test` is borrowed here
|
||||
|
|
||||
note: function requires argument type to outlive `'1`
|
||||
--> $DIR/scope_invariance.rs:13:13
|
||||
|
|
||||
13 | / scope
|
||||
14 | | .create_function_mut(|_, ()| {
|
||||
15 | | test.field = 42;
|
||||
16 | | //~^ error: `test` does not live long enough
|
||||
17 | | Ok(())
|
||||
18 | | })?
|
||||
| |__________________^
|
||||
help: to force the closure to take ownership of `test` (and any other referenced variables), use the `move` keyword
|
||||
|
|
||||
14 | .create_function_mut(move |_, ()| {
|
||||
| ^^^^^^^^^^^^
|
|
@ -1,15 +0,0 @@
|
|||
use mlua::{Lua, UserData, Result};
|
||||
|
||||
struct MyUserData<'a>(&'a mut i32);
|
||||
impl<'a> UserData for MyUserData<'a> {}
|
||||
|
||||
fn main() {
|
||||
let mut i = 1;
|
||||
|
||||
let lua = Lua::new();
|
||||
lua.scope(|scope| -> Result<()> {
|
||||
let _a = scope.create_nonstatic_userdata(MyUserData(&mut i))?;
|
||||
let _b = scope.create_nonstatic_userdata(MyUserData(&mut i))?;
|
||||
Ok(())
|
||||
});
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
error[E0499]: cannot borrow `i` as mutable more than once at a time
|
||||
--> $DIR/scope_mutable_aliasing.rs:12:61
|
||||
|
|
||||
11 | let _a = scope.create_nonstatic_userdata(MyUserData(&mut i))?;
|
||||
| ------ first mutable borrow occurs here
|
||||
12 | let _b = scope.create_nonstatic_userdata(MyUserData(&mut i))?;
|
||||
| ------------------------- ^^^^^^ second mutable borrow occurs here
|
||||
| |
|
||||
| first borrow later used by call
|
|
@ -1,20 +0,0 @@
|
|||
use mlua::{Lua, UserData, Result};
|
||||
|
||||
struct MyUserData<'a>(&'a i32);
|
||||
impl<'a> UserData for MyUserData<'a> {}
|
||||
|
||||
fn main() {
|
||||
// Should not allow userdata borrow to outlive lifetime of AnyUserData handle
|
||||
|
||||
let igood = 1;
|
||||
|
||||
let lua = Lua::new();
|
||||
lua.scope(|scope| -> Result<()> {
|
||||
let _ugood = scope.create_nonstatic_userdata(MyUserData(&igood))?;
|
||||
let _ubad = {
|
||||
let ibad = 42;
|
||||
scope.create_nonstatic_userdata(MyUserData(&ibad))?;
|
||||
};
|
||||
Ok(())
|
||||
});
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
error[E0597]: `ibad` does not live long enough
|
||||
--> $DIR/scope_userdata_borrow.rs:16:56
|
||||
|
|
||||
12 | lua.scope(|scope| -> Result<()> {
|
||||
| ----- has type `&mlua::scope::Scope<'1>`
|
||||
...
|
||||
16 | scope.create_nonstatic_userdata(MyUserData(&ibad))?;
|
||||
| -------------------------------------------^^^^^--
|
||||
| | |
|
||||
| | borrowed value does not live long enough
|
||||
| argument requires that `ibad` is borrowed for `'1`
|
||||
17 | };
|
||||
| - `ibad` dropped here while still borrowed
|
231
tests/scope.rs
231
tests/scope.rs
|
@ -1,231 +0,0 @@
|
|||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use mlua::{Error, Function, Lua, MetaMethod, Result, String, UserData, UserDataMethods};
|
||||
|
||||
#[test]
|
||||
fn scope_func() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let rc = Rc::new(Cell::new(0));
|
||||
lua.scope(|scope| {
|
||||
let r = rc.clone();
|
||||
let f = scope.create_function(move |_, ()| {
|
||||
r.set(42);
|
||||
Ok(())
|
||||
})?;
|
||||
lua.globals().set("bad", f.clone())?;
|
||||
f.call::<_, ()>(())?;
|
||||
assert_eq!(Rc::strong_count(&rc), 2);
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(rc.get(), 42);
|
||||
assert_eq!(Rc::strong_count(&rc), 1);
|
||||
|
||||
match lua.globals().get::<_, Function>("bad")?.call::<_, ()>(()) {
|
||||
Err(Error::CallbackError { .. }) => {}
|
||||
r => panic!("improper return for destructed function: {:?}", r),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope_drop() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
struct MyUserdata(Rc<()>);
|
||||
impl UserData for MyUserdata {
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("method", |_, _, ()| Ok(()));
|
||||
}
|
||||
}
|
||||
|
||||
let rc = Rc::new(());
|
||||
|
||||
lua.scope(|scope| {
|
||||
lua.globals().set(
|
||||
"test",
|
||||
scope.create_static_userdata(MyUserdata(rc.clone()))?,
|
||||
)?;
|
||||
assert_eq!(Rc::strong_count(&rc), 2);
|
||||
Ok(())
|
||||
})?;
|
||||
assert_eq!(Rc::strong_count(&rc), 1);
|
||||
|
||||
match lua.load("test:method()").exec() {
|
||||
Err(Error::CallbackError { .. }) => {}
|
||||
r => panic!("improper return for destructed userdata: {:?}", r),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope_capture() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let mut i = 0;
|
||||
lua.scope(|scope| {
|
||||
scope
|
||||
.create_function_mut(|_, ()| {
|
||||
i = 42;
|
||||
Ok(())
|
||||
})?
|
||||
.call::<_, ()>(())
|
||||
})?;
|
||||
assert_eq!(i, 42);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn outer_lua_access() -> Result<()> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let table = lua.create_table()?;
|
||||
lua.scope(|scope| {
|
||||
scope
|
||||
.create_function_mut(|_, ()| table.set("a", "b"))?
|
||||
.call::<_, ()>(())
|
||||
})?;
|
||||
assert_eq!(table.get::<_, String>("a")?, "b");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope_userdata_methods() -> Result<()> {
|
||||
struct MyUserData<'a>(&'a Cell<i64>);
|
||||
|
||||
impl<'a> UserData for MyUserData<'a> {
|
||||
fn add_methods<M: UserDataMethods<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);
|
||||
let f: Function = lua
|
||||
.load(
|
||||
r#"
|
||||
function(u)
|
||||
u:inc()
|
||||
u:inc()
|
||||
u:inc()
|
||||
u:dec()
|
||||
end
|
||||
"#,
|
||||
)
|
||||
.eval()?;
|
||||
|
||||
lua.scope(|scope| f.call::<_, ()>(scope.create_nonstatic_userdata(MyUserData(&i))?))?;
|
||||
|
||||
assert_eq!(i.get(), 44);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope_userdata_functions() -> Result<()> {
|
||||
struct MyUserData<'a>(&'a i64);
|
||||
|
||||
impl<'a> UserData for MyUserData<'a> {
|
||||
fn add_methods<M: UserDataMethods<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 dummy = 0;
|
||||
let f = lua
|
||||
.load(
|
||||
r#"
|
||||
i = 0
|
||||
return function(u)
|
||||
_ = u + u
|
||||
_ = u - 1
|
||||
_ = 1 + u
|
||||
end
|
||||
"#,
|
||||
)
|
||||
.eval::<Function>()?;
|
||||
|
||||
lua.scope(|scope| f.call::<_, ()>(scope.create_nonstatic_userdata(MyUserData(&dummy))?))?;
|
||||
|
||||
assert_eq!(lua.globals().get::<_, i64>("i")?, 3);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope_userdata_mismatch() -> Result<()> {
|
||||
struct MyUserData<'a>(&'a Cell<i64>);
|
||||
|
||||
impl<'a> UserData for MyUserData<'a> {
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("inc", |_, data, ()| {
|
||||
data.0.set(data.0.get() + 1);
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let lua = Lua::new();
|
||||
|
||||
lua.load(
|
||||
r#"
|
||||
function okay(a, b)
|
||||
a.inc(a)
|
||||
b.inc(b)
|
||||
end
|
||||
|
||||
function bad(a, b)
|
||||
a.inc(b)
|
||||
end
|
||||
"#,
|
||||
)
|
||||
.exec()?;
|
||||
|
||||
let a = Cell::new(1);
|
||||
let b = Cell::new(1);
|
||||
|
||||
let okay: Function = lua.globals().get("okay")?;
|
||||
let bad: Function = lua.globals().get("bad")?;
|
||||
|
||||
lua.scope(|scope| {
|
||||
let au = scope.create_nonstatic_userdata(MyUserData(&a))?;
|
||||
let bu = scope.create_nonstatic_userdata(MyUserData(&b))?;
|
||||
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"),
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in a new issue