Skip to content

Commit 15fb63b

Browse files
committed
Support thread create/resume/yield callbacks for all Lua/Luau versions
Introduce new `Lua::set_thread_event_callback` function to register callback for thread events. Thread collection callback is removed as unlikely very usefil and has too many limitations.
1 parent 0849d05 commit 15fb63b

8 files changed

Lines changed: 467 additions & 181 deletions

File tree

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ pub use crate::string::LuaString as String;
142142
#[doc(hidden)]
143143
pub use crate::table::{TablePairs, TableSequence};
144144
#[doc(hidden)]
145-
pub use crate::thread::ThreadStatus;
145+
pub use crate::thread::{ThreadEvent, ThreadStatus, ThreadTriggers};
146146
#[doc(hidden)]
147147
pub use crate::userdata::{
148148
MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataOwned, UserDataRef,

src/state.rs

Lines changed: 64 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::scope::Scope;
2222
use crate::stdlib::StdLib;
2323
use crate::string::LuaString;
2424
use crate::table::Table;
25-
use crate::thread::Thread;
25+
use crate::thread::{Thread, ThreadEvent, ThreadTriggers};
2626
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
2727
use crate::types::{
2828
AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LuaType, MaybeSend, MaybeSync, Number,
@@ -842,92 +842,87 @@ impl Lua {
842842
}
843843
}
844844

845-
/// Sets a thread creation callback that will be called when a thread is created.
846-
#[cfg(any(feature = "luau", doc))]
847-
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
848-
pub fn set_thread_creation_callback<F>(&self, callback: F)
845+
/// Sets a callback invoked when thread lifecycle events occur.
846+
///
847+
/// `triggers` controls which events trigger the callback, see [`ThreadTriggers`] for more
848+
/// details.
849+
///
850+
/// Only one callback can be registered at a time. Calling this again replaces the previous
851+
/// callback and its triggers.
852+
///
853+
/// # Example
854+
///
855+
/// Subscribe only to yield events:
856+
///
857+
/// ```
858+
/// # use mlua::{Lua, Result, ThreadTriggers, ThreadEvent};
859+
/// # fn main() -> Result<()> {
860+
/// let lua = Lua::new();
861+
/// lua.set_thread_event_callback(
862+
/// ThreadTriggers::ON_YIELD,
863+
/// |_lua, event| {
864+
/// if let ThreadEvent::Yield(thread) = event {
865+
/// println!("thread yielded");
866+
/// }
867+
/// Ok(())
868+
/// },
869+
/// );
870+
/// # Ok(())
871+
/// # }
872+
/// ```
873+
pub fn set_thread_event_callback<F>(&self, triggers: ThreadTriggers, callback: F)
849874
where
850-
F: Fn(&Lua, Thread) -> Result<()> + MaybeSend + 'static,
875+
F: Fn(&Lua, ThreadEvent) -> Result<()> + MaybeSend + 'static,
851876
{
852877
let lua = self.lock();
853878
unsafe {
854-
(*lua.extra.get()).thread_creation_callback = Some(XRc::new(callback));
855-
(*ffi::lua_callbacks(lua.main_state())).userthread = Some(Self::userthread_proc);
879+
(*lua.extra.get()).thread_triggers = triggers;
880+
(*lua.extra.get()).thread_event_callback = Some(XRc::new(callback));
881+
#[cfg(feature = "luau")]
882+
{
883+
let proc = Self::userthread_proc as _;
884+
(*ffi::lua_callbacks(lua.main_state())).userthread = triggers.on_create.then(|| proc);
885+
}
856886
}
857887
}
858888

859-
/// Sets a thread collection callback that will be called when a thread is destroyed.
889+
/// Removes the thread event callback previously set by [`Lua::set_thread_event_callback`].
860890
///
861-
/// Luau GC does not support exceptions during collection, so the callback must be
862-
/// non-panicking. If the callback panics, the program will be aborted.
863-
#[cfg(any(feature = "luau", doc))]
864-
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
865-
pub fn set_thread_collection_callback<F>(&self, callback: F)
866-
where
867-
F: Fn(crate::LightUserData) + MaybeSend + 'static,
868-
{
891+
/// This function has no effect if a callback was not previously set.
892+
pub fn remove_thread_event_callback(&self) {
869893
let lua = self.lock();
894+
let extra = lua.extra.get();
870895
unsafe {
871-
(*lua.extra.get()).thread_collection_callback = Some(XRc::new(callback));
872-
(*ffi::lua_callbacks(lua.main_state())).userthread = Some(Self::userthread_proc);
896+
(*extra).thread_triggers = ThreadTriggers::new();
897+
(*extra).thread_event_callback = None;
898+
#[cfg(feature = "luau")]
899+
{
900+
(*ffi::lua_callbacks(lua.main_state())).userthread = None;
901+
}
873902
}
874903
}
875904

876905
#[cfg(feature = "luau")]
877906
unsafe extern "C-unwind" fn userthread_proc(parent: *mut ffi::lua_State, child: *mut ffi::lua_State) {
878-
let extra = ExtraData::get(child);
879-
if !parent.is_null() {
880-
// Thread is created
881-
let callback = match (*extra).thread_creation_callback {
882-
Some(ref cb) => cb.clone(),
883-
None => return,
884-
};
885-
if XRc::strong_count(&callback) > 2 {
886-
return; // Don't allow recursion
887-
}
888-
ffi::lua_pushthread(child);
889-
ffi::lua_xmove(child, (*extra).ref_thread, 1);
890-
let value = Thread((*extra).raw_lua().pop_ref_thread(), child);
891-
callback_error_ext(parent, extra, false, move |extra, _| {
892-
callback((*extra).lua(), value)
893-
})
894-
} else {
895-
// Thread is about to be collected
896-
let callback = match (*extra).thread_collection_callback {
897-
Some(ref cb) => cb.clone(),
898-
None => return,
899-
};
900-
901-
// We need to wrap the callback call in non-unwind function as it's not safe to unwind when
902-
// Luau GC is running.
903-
// This will trigger `abort()` if the callback panics.
904-
unsafe extern "C" fn run_callback(
905-
callback: *const crate::types::ThreadCollectionCallback,
906-
value: *mut ffi::lua_State,
907-
) {
908-
(*callback)(crate::LightUserData(value as _));
909-
}
910-
911-
(*extra).running_gc = true;
912-
run_callback(&callback, child);
913-
(*extra).running_gc = false;
907+
// Only handle thread creation
908+
if parent.is_null() {
909+
return;
914910
}
915-
}
916911

917-
/// Removes any thread creation or collection callbacks previously set by
918-
/// [`Lua::set_thread_creation_callback`] or [`Lua::set_thread_collection_callback`].
919-
///
920-
/// This function has no effect if a thread callbacks were not previously set.
921-
#[cfg(any(feature = "luau", doc))]
922-
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
923-
pub fn remove_thread_callbacks(&self) {
924-
let lua = self.lock();
925-
unsafe {
926-
let extra = lua.extra.get();
927-
(*extra).thread_creation_callback = None;
928-
(*extra).thread_collection_callback = None;
929-
(*ffi::lua_callbacks(lua.main_state())).userthread = None;
912+
let extra = ExtraData::get(child);
913+
if !(*extra).thread_triggers.on_create {
914+
return;
930915
}
916+
let callback = match &(*extra).thread_event_callback {
917+
Some(cb) if XRc::strong_count(cb) == 1 => cb.clone(),
918+
_ => return,
919+
};
920+
ffi::lua_pushthread(child);
921+
ffi::lua_xmove(child, (*extra).ref_thread, 1);
922+
let thread = Thread((*extra).raw_lua().pop_ref_thread(), child);
923+
callback_error_ext(parent, extra, false, move |extra, _| {
924+
callback((*extra).lua(), ThreadEvent::Create(thread))
925+
})
931926
}
932927

933928
/// Sets the warning function to be used by Lua to emit warnings.

src/state/extra.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use rustc_hash::FxHashMap;
1212
use crate::error::Result;
1313
use crate::state::RawLua;
1414
use crate::stdlib::StdLib;
15-
use crate::types::{AppData, ReentrantMutex, XRc};
15+
use crate::thread::ThreadTriggers;
16+
use crate::types::{AppData, ReentrantMutex, ThreadEventCallback, XRc};
1617
use crate::userdata::RawUserDataRegistry;
1718
use crate::util::{TypeKey, WrappedFailure, get_internal_metatable, push_internal_userdata};
1819

@@ -81,10 +82,8 @@ pub(crate) struct ExtraData {
8182
pub(super) warn_callback: Option<crate::types::WarnCallback>,
8283
#[cfg(feature = "luau")]
8384
pub(super) interrupt_callback: Option<crate::types::InterruptCallback>,
84-
#[cfg(feature = "luau")]
85-
pub(super) thread_creation_callback: Option<crate::types::ThreadCreationCallback>,
86-
#[cfg(feature = "luau")]
87-
pub(super) thread_collection_callback: Option<crate::types::ThreadCollectionCallback>,
85+
pub(super) thread_triggers: ThreadTriggers,
86+
pub(super) thread_event_callback: Option<ThreadEventCallback>,
8887

8988
#[cfg(feature = "luau")]
9089
pub(crate) running_gc: bool,
@@ -186,10 +185,8 @@ impl ExtraData {
186185
warn_callback: None,
187186
#[cfg(feature = "luau")]
188187
interrupt_callback: None,
189-
#[cfg(feature = "luau")]
190-
thread_creation_callback: None,
191-
#[cfg(feature = "luau")]
192-
thread_collection_callback: None,
188+
thread_triggers: ThreadTriggers::default(),
189+
thread_event_callback: None,
193190
#[cfg(feature = "luau")]
194191
sandboxed: false,
195192
#[cfg(feature = "luau")]

src/state/raw.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ use crate::state::util::callback_error_ext;
1515
use crate::stdlib::StdLib;
1616
use crate::string::LuaString;
1717
use crate::table::Table;
18-
use crate::thread::Thread;
18+
use crate::thread::{Thread, ThreadTriggers};
1919
use crate::traits::{FromLua, IntoLua};
2020
use crate::types::{
2121
AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData,
22-
LuaType, MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc,
22+
LuaType, MaybeSend, ReentrantMutex, RegistryKey, ThreadEventCallback, ValueRef, XRc,
2323
};
2424
use crate::userdata::{
2525
AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, UserDataStorage,
@@ -643,7 +643,7 @@ impl RawLua {
643643

644644
let protect = !self.unlikely_memory_error();
645645
#[cfg(feature = "luau")]
646-
let protect = protect || (*self.extra.get()).thread_creation_callback.is_some();
646+
let protect = protect || self.thread_event_triggers().on_create;
647647

648648
let thread_state = if !protect {
649649
ffi::lua_newthread(state)
@@ -656,6 +656,19 @@ impl RawLua {
656656
self.set_thread_hook(thread_state, HookKind::Global)?;
657657

658658
let thread = Thread(self.pop_ref(), thread_state);
659+
660+
// Exec creation callback for non-Luau (Luau handles this via `userthread_proc`)
661+
#[cfg(not(feature = "luau"))]
662+
if self.thread_event_triggers().on_create {
663+
let extra = self.extra.get();
664+
if let Some(ref cb) = (*extra).thread_event_callback
665+
&& XRc::strong_count(cb) == 1
666+
{
667+
let cb = cb.clone();
668+
cb((*extra).lua(), crate::thread::ThreadEvent::Create(thread.clone()))?;
669+
}
670+
}
671+
659672
ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index);
660673
Ok(thread)
661674
}
@@ -691,6 +704,16 @@ impl RawLua {
691704
}
692705
}
693706

707+
#[inline(always)]
708+
pub(crate) unsafe fn thread_event_triggers(&self) -> ThreadTriggers {
709+
(*self.extra.get()).thread_triggers
710+
}
711+
712+
#[inline(always)]
713+
pub(crate) unsafe fn thread_event_callback(&self) -> Option<ThreadEventCallback> {
714+
(*self.extra.get()).thread_event_callback.clone()
715+
}
716+
694717
/// Pushes a primitive type value onto the Lua stack.
695718
pub(crate) unsafe fn push_primitive_type<T: LuaType>(&self) -> bool {
696719
match T::TYPE_ID {

0 commit comments

Comments
 (0)