From d21b3723f59664f5132b55167434ee11fde7d4a2 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Fri, 19 Jan 2024 23:23:56 +0100 Subject: [PATCH] Improve ergonomics for callbacks struct, improve examples, docs, naming --- examples/basic_sleep.rs | 2 +- examples/basic_spawn.rs | 2 +- examples/callbacks.rs | 21 ++++++++++++------- examples/main.rs | 38 ++++++++++++++++++---------------- examples/scheduler_ordering.rs | 2 +- lib/callbacks.rs | 37 ++++++++++++++++++++++++++++++--- lib/runtime.rs | 21 ++++++++++++++++--- 7 files changed, 89 insertions(+), 34 deletions(-) diff --git a/examples/basic_sleep.rs b/examples/basic_sleep.rs index bc14ac9..dc27d3b 100644 --- a/examples/basic_sleep.rs +++ b/examples/basic_sleep.rs @@ -23,7 +23,7 @@ pub fn main() -> LuaResult<()> { // Load the main script into a runtime and run it until completion let rt = Runtime::new(&lua)?; let main = lua.load(MAIN_SCRIPT); - rt.push_main(&lua, main, ()); + rt.push_thread(&lua, main, ()); rt.run_blocking(&lua); Ok(()) diff --git a/examples/basic_spawn.rs b/examples/basic_spawn.rs index ca0ad0d..66f1f8e 100644 --- a/examples/basic_spawn.rs +++ b/examples/basic_spawn.rs @@ -29,7 +29,7 @@ pub fn main() -> LuaResult<()> { // Load the main script into a runtime and run it until completion let rt = Runtime::new(&lua)?; let main = lua.load(MAIN_SCRIPT); - rt.push_main(&lua, main, ()); + rt.push_thread(&lua, main, ()); rt.run_blocking(&lua); Ok(()) diff --git a/examples/callbacks.rs b/examples/callbacks.rs index 7cc074e..ba0d473 100644 --- a/examples/callbacks.rs +++ b/examples/callbacks.rs @@ -9,15 +9,22 @@ pub fn main() -> LuaResult<()> { // Set up persistent lua environment let lua = Lua::new(); - // Load the main script into a runtime + // Create a new runtime with custom callbacks let rt = Runtime::new(&lua)?; + rt.set_callbacks( + &lua, + Callbacks::default().on_error(|_, _, e| { + println!( + "Captured error from Lua!\n{}\n{e}\n{}", + "-".repeat(15), + "-".repeat(15) + ); + }), + ); + + // Load and run the main script until completion let main = lua.load(MAIN_SCRIPT); - - // Inject default value & error callbacks - this will print lua errors to stderr - Callbacks::default().inject(&lua); - - // Run the main script until completion - rt.push_main(&lua, main, ()); + rt.push_thread(&lua, main, ()); rt.run_blocking(&lua); Ok(()) diff --git a/examples/main.rs b/examples/main.rs index f1ed537..0181e15 100644 --- a/examples/main.rs +++ b/examples/main.rs @@ -45,28 +45,30 @@ pub fn main() -> LuaResult<()> { fn run<'lua>(lua: &'lua Lua, main: impl IntoLuaThread<'lua>) -> LuaResult { // Set up runtime (thread queue / async executors) let rt = Runtime::new(lua)?; - let thread = rt.push_main(lua, main, ()); + let thread = rt.push_thread(lua, main, ()); lua.set_named_registry_value("mainThread", thread)?; - // Add callbacks to capture resulting value/error of main thread, - // we need to do some tricks to get around lifetime issues with 'lua - // being different inside the callback vs outside the callback for LuaValue + // Create callbacks to capture resulting value/error of main thread, + // we need to do some tricks to get around the lifetime issues with 'lua + // being different inside the callback vs. outside the callback, for LuaValue let captured_error = Rc::new(Mutex::new(None)); let captured_error_inner = Rc::clone(&captured_error); - Callbacks::new() - .on_value(|lua, thread, val| { - let main: LuaThread = lua.named_registry_value("mainThread").unwrap(); - if main == thread { - lua.set_named_registry_value("mainValue", val).unwrap(); - } - }) - .on_error(move |lua, thread, err| { - let main: LuaThread = lua.named_registry_value("mainThread").unwrap(); - if main == thread { - captured_error_inner.lock_blocking().replace(err); - } - }) - .inject(lua); + rt.set_callbacks( + lua, + Callbacks::new() + .on_value(|lua, thread, val| { + let main: LuaThread = lua.named_registry_value("mainThread").unwrap(); + if main == thread { + lua.set_named_registry_value("mainValue", val).unwrap(); + } + }) + .on_error(move |lua, thread, err| { + let main: LuaThread = lua.named_registry_value("mainThread").unwrap(); + if main == thread { + captured_error_inner.lock_blocking().replace(err); + } + }), + ); // Run until end rt.run_blocking(lua); diff --git a/examples/scheduler_ordering.rs b/examples/scheduler_ordering.rs index 78f2786..3e663fd 100644 --- a/examples/scheduler_ordering.rs +++ b/examples/scheduler_ordering.rs @@ -24,7 +24,7 @@ pub fn main() -> LuaResult<()> { // Load the main script into a runtime and run it until completion let rt = Runtime::new(&lua)?; let main = lua.load(MAIN_SCRIPT); - rt.push_main(&lua, main, ()); + rt.push_thread(&lua, main, ()); rt.run_blocking(&lua); Ok(()) diff --git a/lib/callbacks.rs b/lib/callbacks.rs index 1f85048..7b86de0 100644 --- a/lib/callbacks.rs +++ b/lib/callbacks.rs @@ -6,16 +6,34 @@ type ErrorCallback = Box Fn(&'lua Lua, LuaThread<'lua>, LuaError) const FORWARD_VALUE_KEY: &str = "__runtime__forwardValue"; const FORWARD_ERROR_KEY: &str = "__runtime__forwardError"; +/** + A set of callbacks for thread values and errors. + + These callbacks are used to forward values and errors from + Lua threads back to Rust. By default, the runtime will print + any errors to stderr and not do any operations with values. + + You can set your own callbacks using the `on_value` and `on_error` builder methods. +*/ pub struct Callbacks { on_value: Option, on_error: Option, } impl Callbacks { - pub fn new() -> Callbacks { - Default::default() + /** + Creates a new set of callbacks with no callbacks set. + */ + pub fn new() -> Self { + Self { + on_value: None, + on_error: None, + } } + /** + Sets the callback for thread values being yielded / returned. + */ pub fn on_value(mut self, f: F) -> Self where F: Fn(&Lua, LuaThread, LuaValue) + 'static, @@ -24,6 +42,9 @@ impl Callbacks { self } + /** + Sets the callback for thread errors. + */ pub fn on_error(mut self, f: F) -> Self where F: Fn(&Lua, LuaThread, LuaError) + 'static, @@ -32,17 +53,27 @@ impl Callbacks { self } + /** + Removes any current thread value callback. + */ pub fn without_value_callback(mut self) -> Self { self.on_value.take(); self } + /** + Removes any current thread error callback. + */ pub fn without_error_callback(mut self) -> Self { self.on_error.take(); self } - pub fn inject(self, lua: &Lua) { + pub(crate) fn inject(self, lua: &Lua) { + // Remove any previously injected callbacks + lua.unset_named_registry_value(FORWARD_VALUE_KEY).ok(); + lua.unset_named_registry_value(FORWARD_ERROR_KEY).ok(); + // Create functions to forward values & errors if let Some(f) = self.on_value { lua.set_named_registry_value( diff --git a/lib/runtime.rs b/lib/runtime.rs index 3d1f4aa..be8bde2 100644 --- a/lib/runtime.rs +++ b/lib/runtime.rs @@ -29,7 +29,8 @@ impl Runtime { /** Creates a new runtime for the given Lua state. - This will inject some functions to interact with the scheduler / executor. + This will inject some functions to interact with the scheduler / executor, + as well as the default [`Callbacks`] for thread values and errors. */ pub fn new(lua: &Lua) -> LuaResult { let queue_status = Rc::new(Cell::new(false)); @@ -106,6 +107,9 @@ impl Runtime { lua.globals().set(GLOBAL_NAME_SPAWN, fn_spawn)?; lua.globals().set(GLOBAL_NAME_DEFER, fn_defer)?; + // Finally, inject default callbacks + Callbacks::default().inject(lua); + Ok(Runtime { queue_status, queue_spawn, @@ -116,9 +120,20 @@ impl Runtime { } /** - Pushes a chunk / function / thread to the front of the runtime. + Sets the callbacks for this runtime. + + This will overwrite any previously set callbacks, including default ones. */ - pub fn push_main<'lua>( + pub fn set_callbacks(&self, lua: &Lua, callbacks: Callbacks) { + callbacks.inject(lua); + } + + /** + Pushes a chunk / function / thread to the runtime queue. + + Threads are guaranteed to be resumed in the order that they were pushed to the queue. + */ + pub fn push_thread<'lua>( &self, lua: &'lua Lua, thread: impl IntoLuaThread<'lua>,