diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 3056662..6658244 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -59,6 +59,22 @@ fn create_string_table(c: &mut Criterion) { }); } +fn create_function(c: &mut Criterion) { + let lua = Lua::new(); + + c.bench_function("create [function] 10", |b| { + b.iter_batched( + || collect_gc_twice(&lua), + |_| { + for i in 0..10 { + lua.create_function(move |_, ()| Ok(i)).unwrap(); + } + }, + BatchSize::SmallInput, + ); + }); +} + fn call_lua_function(c: &mut Criterion) { let lua = Lua::new(); @@ -258,6 +274,7 @@ criterion_group! { create_table, create_array, create_string_table, + create_function, call_lua_function, call_sum_callback, call_async_sum_callback, diff --git a/src/lua.rs b/src/lua.rs index 87645a7..735485d 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -19,7 +19,8 @@ use crate::string::String; use crate::table::Table; use crate::thread::Thread; use crate::types::{ - Callback, HookCallback, Integer, LightUserData, LuaRef, MaybeSend, Number, RegistryKey, + Callback, CallbackUpvalue, HookCallback, Integer, LightUserData, LuaRef, MaybeSend, Number, + RegistryKey, }; use crate::userdata::{ AnyUserData, MetaMethod, UserData, UserDataCell, UserDataFields, UserDataMethods, @@ -38,7 +39,7 @@ use std::rc::Rc; #[cfg(feature = "async")] use { - crate::types::AsyncCallback, + crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue}, futures_core::{ future::{Future, LocalBoxFuture}, task::{Context, Poll, Waker}, @@ -396,12 +397,13 @@ impl Lua { // to prevent them from being garbage collected. init_gc_metatable_for::(state, None)?; - init_gc_metatable_for::(state, None)?; + init_gc_metatable_for::(state, None)?; init_gc_metatable_for::>>(state, None)?; #[cfg(feature = "async")] { init_gc_metatable_for::(state, None)?; - init_gc_metatable_for::>>(state, None)?; + init_gc_metatable_for::(state, None)?; + init_gc_metatable_for::(state, None)?; init_gc_metatable_for::>(state, None)?; // Create empty Waker slot @@ -1777,22 +1779,22 @@ impl Lua { 'lua: 'callback, { unsafe extern "C" fn call_callback(state: *mut ffi::lua_State) -> c_int { - callback_error2(state, |nargs| { - let upvalue_idx1 = ffi::lua_upvalueindex(1); - let upvalue_idx2 = ffi::lua_upvalueindex(2); - if ffi::lua_type(state, upvalue_idx1) == ffi::LUA_TNIL - || ffi::lua_type(state, upvalue_idx2) == ffi::LUA_TNIL - { + let get_extra = |state| { + let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); + (*upvalue).lua.extra.clone() + }; + callback_error_ext(state, get_extra, |nargs| { + let upvalue_idx = ffi::lua_upvalueindex(1); + if ffi::lua_type(state, upvalue_idx) == ffi::LUA_TNIL { return Err(Error::CallbackDestructed); } - let func = get_userdata::(state, upvalue_idx1); - let lua = get_userdata::(state, upvalue_idx2); + let upvalue = get_userdata::(state, upvalue_idx); if nargs < ffi::LUA_MINSTACK { check_stack(state, ffi::LUA_MINSTACK - nargs)?; } - let lua = &mut *lua; + let lua = &mut (*upvalue).lua; lua.state = state; let mut args = MultiValue::new(); @@ -1801,7 +1803,7 @@ impl Lua { args.push_front(lua.pop_value()); } - let results = (*func)(lua, args)?; + let results = ((*upvalue).func)(lua, args)?; let nresults = results.len() as c_int; check_stack(state, nresults)?; @@ -1815,12 +1817,13 @@ impl Lua { unsafe { let _sg = StackGuard::new(self.state); - check_stack(self.state, 5)?; + check_stack(self.state, 4)?; - push_gc_userdata::(self.state, mem::transmute(func))?; - push_gc_userdata(self.state, self.clone())?; - protect_lua(self.state, 2, 1, |state| { - ffi::lua_pushcclosure(state, call_callback, 2); + let lua = self.clone(); + let func = mem::transmute(func); + push_gc_userdata(self.state, CallbackUpvalue { lua, func })?; + protect_lua(self.state, 1, 1, |state| { + ffi::lua_pushcclosure(state, call_callback, 1); })?; Ok(Function(self.pop_ref())) @@ -1844,22 +1847,22 @@ impl Lua { } unsafe extern "C" fn call_callback(state: *mut ffi::lua_State) -> c_int { - callback_error2(state, |nargs| { - let upvalue_idx1 = ffi::lua_upvalueindex(1); - let upvalue_idx2 = ffi::lua_upvalueindex(2); - if ffi::lua_type(state, upvalue_idx1) == ffi::LUA_TNIL - || ffi::lua_type(state, upvalue_idx2) == ffi::LUA_TNIL - { + let get_extra = |state| { + let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); + (*upvalue).lua.extra.clone() + }; + callback_error_ext(state, get_extra, |nargs| { + let upvalue_idx = ffi::lua_upvalueindex(1); + if ffi::lua_type(state, upvalue_idx) == ffi::LUA_TNIL { return Err(Error::CallbackDestructed); } - let func = get_userdata::(state, upvalue_idx1); - let lua = get_userdata::(state, upvalue_idx2); + let upvalue = get_userdata::(state, upvalue_idx); if nargs < ffi::LUA_MINSTACK { check_stack(state, ffi::LUA_MINSTACK - nargs)?; } - let lua = &mut *lua; + let lua = &mut (*upvalue).lua; lua.state = state; let mut args = MultiValue::new(); @@ -1868,12 +1871,11 @@ impl Lua { args.push_front(lua.pop_value()); } - let fut = (*func)(lua, args); - push_gc_userdata(state, fut)?; - push_gc_userdata(state, lua.clone())?; - - protect_lua(state, 2, 1, |state| { - ffi::lua_pushcclosure(state, poll_future, 2); + let fut = ((*upvalue).func)(lua, args); + let lua = lua.clone(); + push_gc_userdata(state, AsyncPollUpvalue { lua, fut })?; + protect_lua(state, 1, 1, |state| { + ffi::lua_pushcclosure(state, poll_future, 1); })?; Ok(1) @@ -1881,22 +1883,22 @@ impl Lua { } unsafe extern "C" fn poll_future(state: *mut ffi::lua_State) -> c_int { - callback_error2(state, |nargs| { - let upvalue_idx1 = ffi::lua_upvalueindex(1); - let upvalue_idx2 = ffi::lua_upvalueindex(2); - if ffi::lua_type(state, upvalue_idx1) == ffi::LUA_TNIL - || ffi::lua_type(state, upvalue_idx2) == ffi::LUA_TNIL - { + let get_extra = |state| { + let upvalue = get_userdata::(state, ffi::lua_upvalueindex(1)); + (*upvalue).lua.extra.clone() + }; + callback_error_ext(state, get_extra, |nargs| { + let upvalue_idx = ffi::lua_upvalueindex(1); + if ffi::lua_type(state, upvalue_idx) == ffi::LUA_TNIL { return Err(Error::CallbackDestructed); } - let fut = get_userdata::>>(state, upvalue_idx1); - let lua = get_userdata::(state, upvalue_idx2); + let upvalue = get_userdata::(state, upvalue_idx); if nargs < ffi::LUA_MINSTACK { check_stack(state, ffi::LUA_MINSTACK - nargs)?; } - let lua = &mut *lua; + let lua = &mut (*upvalue).lua; lua.state = state; // Try to get an outer poll waker @@ -1910,7 +1912,8 @@ impl Lua { let mut ctx = Context::from_waker(&waker); - match (*fut).as_mut().poll(&mut ctx) { + let fut = &mut (*upvalue).fut; + match fut.as_mut().poll(&mut ctx) { Poll::Pending => { check_stack(state, 1)?; ffi::lua_pushboolean(state, 0); @@ -1932,12 +1935,13 @@ impl Lua { let get_poll = unsafe { let _sg = StackGuard::new(self.state); - check_stack(self.state, 5)?; + check_stack(self.state, 4)?; - push_gc_userdata::(self.state, mem::transmute(func))?; - push_gc_userdata(self.state, self.clone())?; - protect_lua(self.state, 2, 1, |state| { - ffi::lua_pushcclosure(state, call_callback, 2); + let lua = self.clone(); + let func = mem::transmute(func); + push_gc_userdata(self.state, AsyncCallbackUpvalue { lua, func })?; + protect_lua(self.state, 1, 1, |state| { + ffi::lua_pushcclosure(state, call_callback, 1); })?; Function(self.pop_ref()) @@ -2287,13 +2291,14 @@ impl<'lua, T: AsRef<[u8]> + ?Sized> AsChunk<'lua> for T { // An optimized version of `callback_error` that does not allocate `WrappedError+Panic` userdata // and instead reuses unsed and cached values from previous calls (or allocates new). -// It assumes that ephemeral `Lua` struct is passed as a 2nd upvalue. -pub unsafe fn callback_error2(state: *mut ffi::lua_State, f: F) -> R +// It requires `get_extra` function to return `ExtraData` value. +unsafe fn callback_error_ext(state: *mut ffi::lua_State, get_extra: E, f: F) -> R where + E: Fn(*mut ffi::lua_State) -> Arc>, F: FnOnce(c_int) -> Result, { - let upvalue_idx2 = ffi::lua_upvalueindex(2); - if ffi::lua_type(state, upvalue_idx2) == ffi::LUA_TNIL { + let upvalue_idx = ffi::lua_upvalueindex(1); + if ffi::lua_type(state, upvalue_idx) == ffi::LUA_TNIL { return callback_error(state, f); } @@ -2314,9 +2319,9 @@ where // We cannot shadow Rust errors with Lua ones, so we need to obtain pre-allocated memory // to store a wrapped error or panic *before* we proceed. - let lua = get_userdata::(state, upvalue_idx2); + let extra = get_extra(state); let prealloc_err = { - let mut extra = mlua_expect!((*lua).extra.lock(), "extra is poisoned"); + let mut extra = mlua_expect!(extra.lock(), "extra is poisoned"); match extra.prealloc_wrapped_errors.pop() { Some(index) => PreallocatedError::Cached(index), None => { @@ -2329,7 +2334,7 @@ where }; let get_prealloc_err = || { - let mut extra = mlua_expect!((*lua).extra.lock(), "extra is poisoned"); + let mut extra = mlua_expect!(extra.lock(), "extra is poisoned"); match prealloc_err { PreallocatedError::New(ud) => { ffi::lua_settop(state, 1); @@ -2350,7 +2355,7 @@ where match catch_unwind(AssertUnwindSafe(|| f(nargs))) { Ok(Ok(r)) => { // Return unused WrappedError+Panic to the cache - let mut extra = mlua_expect!((*lua).extra.lock(), "extra is poisoned"); + let mut extra = mlua_expect!(extra.lock(), "extra is poisoned"); match prealloc_err { PreallocatedError::New(_) if extra.prealloc_wrapped_errors.len() < 16 => { ffi::lua_rotate(state, 1, -1); diff --git a/src/scope.rs b/src/scope.rs index 6467e15..985db3e 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -13,7 +13,7 @@ use crate::error::{Error, Result}; use crate::ffi; use crate::function::Function; use crate::lua::Lua; -use crate::types::{Callback, LuaRef, MaybeSend}; +use crate::types::{Callback, CallbackUpvalue, LuaRef, MaybeSend}; use crate::userdata::{ AnyUserData, MetaMethod, UserData, UserDataCell, UserDataFields, UserDataMethods, }; @@ -25,8 +25,8 @@ use crate::value::{FromLua, FromLuaMulti, MultiValue, ToLua, ToLuaMulti, Value}; #[cfg(feature = "async")] use { - crate::types::AsyncCallback, - futures_core::future::{Future, LocalBoxFuture}, + crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue}, + futures_core::future::Future, futures_util::future::{self, TryFutureExt}, }; @@ -224,7 +224,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { /// use [`Scope::create_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 + /// will no longer have a `TypeId` associated with it, because `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 @@ -460,16 +460,11 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { // We know the destructor has not run yet because we hold a reference to the callback. ffi::lua_getupvalue(state, -1, 1); - let ud1 = take_userdata::(state); + let ud = take_userdata::(state); ffi::lua_pushnil(state); ffi::lua_setupvalue(state, -2, 1); - ffi::lua_getupvalue(state, -1, 2); - let ud2 = take_userdata::(state); - ffi::lua_pushnil(state); - ffi::lua_setupvalue(state, -2, 2); - - vec![Box::new(ud1), Box::new(ud2)] + vec![Box::new(ud)] }); self.destructors .borrow_mut() @@ -510,32 +505,21 @@ impl<'lua, 'scope> Scope<'lua, 'scope> { // Destroy all upvalues ffi::lua_getupvalue(state, -1, 1); - let ud1 = take_userdata::(state); + let upvalue1 = take_userdata::(state); ffi::lua_pushnil(state); ffi::lua_setupvalue(state, -2, 1); - ffi::lua_getupvalue(state, -1, 2); - let ud2 = take_userdata::(state); - ffi::lua_pushnil(state); - ffi::lua_setupvalue(state, -2, 2); - ffi::lua_pop(state, 1); - let mut data: Vec> = vec![Box::new(ud1), Box::new(ud2)]; + let mut data: Vec> = vec![Box::new(upvalue1)]; // Finally, get polled future and destroy it f.lua.push_ref(&poll_str.0); if ffi::lua_rawget(state, -2) == ffi::LUA_TFUNCTION { ffi::lua_getupvalue(state, -1, 1); - let ud3 = take_userdata::>>(state); + let upvalue2 = take_userdata::(state); ffi::lua_pushnil(state); ffi::lua_setupvalue(state, -2, 1); - data.push(Box::new(ud3)); - - ffi::lua_getupvalue(state, -1, 2); - let ud4 = take_userdata::(state); - ffi::lua_pushnil(state); - ffi::lua_setupvalue(state, -2, 2); - data.push(Box::new(ud4)); + data.push(Box::new(upvalue2)); } data diff --git a/src/types.rs b/src/types.rs index 6e21af1..40c9f01 100644 --- a/src/types.rs +++ b/src/types.rs @@ -26,10 +26,27 @@ pub struct LightUserData(pub *mut c_void); pub(crate) type Callback<'lua, 'a> = Box) -> Result> + 'a>; +pub(crate) struct CallbackUpvalue<'lua> { + pub(crate) lua: Lua, + pub(crate) func: Callback<'lua, 'static>, +} + #[cfg(feature = "async")] pub(crate) type AsyncCallback<'lua, 'a> = Box) -> LocalBoxFuture<'lua, Result>> + 'a>; +#[cfg(feature = "async")] +pub(crate) struct AsyncCallbackUpvalue<'lua> { + pub(crate) lua: Lua, + pub(crate) func: AsyncCallback<'lua, 'static>, +} + +#[cfg(feature = "async")] +pub(crate) struct AsyncPollUpvalue<'lua> { + pub(crate) lua: Lua, + pub(crate) fut: LocalBoxFuture<'lua, Result>>, +} + pub(crate) type HookCallback = Arc Result<()>>>; #[cfg(feature = "send")]