Clean up error handling & chunk loading

This commit is contained in:
Filip Tibell 2023-02-20 13:02:22 +01:00
parent 4cc983dbe6
commit 801da61c0f
No known key found for this signature in database
6 changed files with 58 additions and 85 deletions

View file

@ -8,6 +8,25 @@ use mlua::prelude::*;
use crate::utils::table::TableBuilder; use crate::utils::table::TableBuilder;
const REQUIRE_IMPL_LUA: &str = r#"
local source = info(1, "s")
if source == '[string "require"]' then
source = info(2, "s")
end
local absolute, relative = paths(source, ...)
if loaded[absolute] ~= true then
local first, second = load(absolute, relative)
if first == nil or second ~= nil then
error("Module did not return exactly one value")
end
loaded[absolute] = true
cache[absolute] = first
return first
else
return cache[absolute]
end
"#;
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> { pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
// Preserve original require behavior if we have a special env var set, // Preserve original require behavior if we have a special env var set,
// returning an empty table since there are no globals to overwrite // returning an empty table since there are no globals to overwrite
@ -89,26 +108,7 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
.with_value("load", require_get_loaded_file)? .with_value("load", require_get_loaded_file)?
.build_readonly()?; .build_readonly()?;
let require_fn_lua = lua let require_fn_lua = lua
.load( .load(REQUIRE_IMPL_LUA)
r#"
local source = info(1, "s")
if source == '[string "require"]' then
source = info(2, "s")
end
local absolute, relative = paths(source, ...)
if loaded[absolute] ~= true then
local first, second = load(absolute, relative)
if first == nil or second ~= nil then
error("Module did not return exactly one value")
end
loaded[absolute] = true
cache[absolute] = first
return first
else
return cache[absolute]
end
"#,
)
.set_name("require")? .set_name("require")?
.set_environment(require_env)? .set_environment(require_env)?
.into_function()?; .into_function()?;

View file

@ -11,6 +11,13 @@ use crate::{
utils::table::TableBuilder, utils::table::TableBuilder,
}; };
const SPAWN_IMPL_LUA: &str = r#"
scheduleNext(thread())
local task = scheduleNext(...)
yield()
return task
"#;
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable<'static>> { pub fn create(lua: &'static Lua) -> LuaResult<LuaTable<'static>> {
lua.app_data_ref::<&TaskScheduler>() lua.app_data_ref::<&TaskScheduler>()
.expect("Missing task scheduler in app data"); .expect("Missing task scheduler in app data");
@ -27,14 +34,7 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable<'static>> {
*/ */
let task_spawn_env_yield: LuaFunction = lua.named_registry_value("co.yield")?; let task_spawn_env_yield: LuaFunction = lua.named_registry_value("co.yield")?;
let task_spawn = lua let task_spawn = lua
.load( .load(SPAWN_IMPL_LUA)
"
scheduleNext(thread())
local task = scheduleNext(...)
yield()
return task
",
)
.set_name("task.spawn")? .set_name("task.spawn")?
.set_environment( .set_environment(
TableBuilder::new(lua)? TableBuilder::new(lua)?

View file

@ -6,6 +6,16 @@ use crate::{lua::task::TaskScheduler, utils::table::TableBuilder};
use super::task::TaskSchedulerAsyncExt; use super::task::TaskSchedulerAsyncExt;
const ASYNC_IMPL_LUA: &str = r#"
resumeAsync(thread(), ...)
return yield()
"#;
const WAIT_IMPL_LUA: &str = r#"
resumeAfter(...)
return yield()
"#;
#[async_trait(?Send)] #[async_trait(?Send)]
pub trait LuaAsyncExt { pub trait LuaAsyncExt {
fn create_async_function<'lua, A, R, F, FR>(self, func: F) -> LuaResult<LuaFunction<'lua>> fn create_async_function<'lua, A, R, F, FR>(self, func: F) -> LuaResult<LuaFunction<'lua>>
@ -30,18 +40,8 @@ impl LuaAsyncExt for &'static Lua {
F: 'static + Fn(&'static Lua, A) -> FR, F: 'static + Fn(&'static Lua, A) -> FR,
FR: 'static + Future<Output = LuaResult<R>>, FR: 'static + Future<Output = LuaResult<R>>,
{ {
let async_env_make_err: LuaFunction = self.named_registry_value("dbg.makeerr")?;
let async_env_is_err: LuaFunction = self.named_registry_value("dbg.iserr")?;
let async_env_trace: LuaFunction = self.named_registry_value("dbg.trace")?;
let async_env_error: LuaFunction = self.named_registry_value("error")?;
let async_env_unpack: LuaFunction = self.named_registry_value("tab.unpack")?;
let async_env_yield: LuaFunction = self.named_registry_value("co.yield")?; let async_env_yield: LuaFunction = self.named_registry_value("co.yield")?;
let async_env = TableBuilder::new(self)? let async_env = TableBuilder::new(self)?
.with_value("makeError", async_env_make_err)?
.with_value("isError", async_env_is_err)?
.with_value("trace", async_env_trace)?
.with_value("error", async_env_error)?
.with_value("unpack", async_env_unpack)?
.with_value("yield", async_env_yield)? .with_value("yield", async_env_yield)?
.with_function("thread", |lua, _: ()| Ok(lua.current_thread()))? .with_function("thread", |lua, _: ()| Ok(lua.current_thread()))?
.with_function( .with_function(
@ -60,17 +60,7 @@ impl LuaAsyncExt for &'static Lua {
)? )?
.build_readonly()?; .build_readonly()?;
let async_func = self let async_func = self
.load( .load(ASYNC_IMPL_LUA)
"
resumeAsync(thread(), ...)
local results = { yield() }
if isError(results[1]) then
error(makeError(results[1], trace()))
else
return unpack(results)
end
",
)
.set_name("async")? .set_name("async")?
.set_environment(async_env)? .set_environment(async_env)?
.into_function()?; .into_function()?;
@ -94,12 +84,7 @@ impl LuaAsyncExt for &'static Lua {
})? })?
.build_readonly()?; .build_readonly()?;
let async_func = self let async_func = self
.load( .load(WAIT_IMPL_LUA)
"
resumeAfter(...)
return yield()
",
)
.set_name("wait")? .set_name("wait")?
.set_environment(async_env)? .set_environment(async_env)?
.into_function()?; .into_function()?;

View file

@ -48,9 +48,6 @@ end
These globals can then be modified safely after constructing Lua using this function. These globals can then be modified safely after constructing Lua using this function.
---
* `"require"` -> `require`
* `"select"` -> `select`
--- ---
* `"print"` -> `print` * `"print"` -> `print`
* `"error"` -> `error` * `"error"` -> `error`
@ -82,8 +79,6 @@ pub fn create() -> LuaResult<&'static Lua> {
let coroutine: LuaTable = globals.get("coroutine")?; let coroutine: LuaTable = globals.get("coroutine")?;
// Store original lua global functions in the registry so we can use // Store original lua global functions in the registry so we can use
// them later without passing them around and dealing with lifetimes // them later without passing them around and dealing with lifetimes
lua.set_named_registry_value("require", globals.get::<_, LuaFunction>("require")?)?;
lua.set_named_registry_value("select", globals.get::<_, LuaFunction>("select")?)?;
lua.set_named_registry_value("print", globals.get::<_, LuaFunction>("print")?)?; lua.set_named_registry_value("print", globals.get::<_, LuaFunction>("print")?)?;
lua.set_named_registry_value("error", globals.get::<_, LuaFunction>("error")?)?; lua.set_named_registry_value("error", globals.get::<_, LuaFunction>("error")?)?;
lua.set_named_registry_value("type", globals.get::<_, LuaFunction>("type")?)?; lua.set_named_registry_value("type", globals.get::<_, LuaFunction>("type")?)?;
@ -98,18 +93,6 @@ pub fn create() -> LuaResult<&'static Lua> {
lua.set_named_registry_value("dbg.info", debug.get::<_, LuaFunction>("info")?)?; lua.set_named_registry_value("dbg.info", debug.get::<_, LuaFunction>("info")?)?;
lua.set_named_registry_value("tab.pack", table.get::<_, LuaFunction>("pack")?)?; lua.set_named_registry_value("tab.pack", table.get::<_, LuaFunction>("pack")?)?;
lua.set_named_registry_value("tab.unpack", table.get::<_, LuaFunction>("unpack")?)?; lua.set_named_registry_value("tab.unpack", table.get::<_, LuaFunction>("unpack")?)?;
// Create a function that can be called from lua to check if a value is a mlua error,
// this will be used in async environments for proper error handling and throwing, as
// well as a function that can be called to make a callback error with a traceback from lua
let dbg_is_err_fn =
lua.create_function(move |_, value: LuaValue| Ok(matches!(value, LuaValue::Error(_))))?;
let dbg_make_err_fn = lua.create_function(|_, (cause, traceback): (LuaError, String)| {
Ok(LuaError::CallbackError {
traceback,
cause: cause.into(),
})
})?;
// Create a trace function that can be called to obtain a full stack trace from // Create a trace function that can be called to obtain a full stack trace from
// lua, this is not possible to do from rust when using our manual scheduler // lua, this is not possible to do from rust when using our manual scheduler
let dbg_trace_env = lua.create_table_with_capacity(0, 1)?; let dbg_trace_env = lua.create_table_with_capacity(0, 1)?;
@ -123,8 +106,6 @@ pub fn create() -> LuaResult<&'static Lua> {
.set_environment(dbg_trace_env)? .set_environment(dbg_trace_env)?
.into_function()?; .into_function()?;
lua.set_named_registry_value("dbg.trace", dbg_trace_fn)?; lua.set_named_registry_value("dbg.trace", dbg_trace_fn)?;
lua.set_named_registry_value("dbg.iserr", dbg_is_err_fn)?;
lua.set_named_registry_value("dbg.makeerr", dbg_make_err_fn)?;
// All done // All done
Ok(lua) Ok(lua)
} }

View file

@ -3,6 +3,7 @@ use std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
collections::{HashMap, VecDeque}, collections::{HashMap, VecDeque},
process::ExitCode, process::ExitCode,
sync::Arc,
}; };
use futures_util::{future::LocalBoxFuture, stream::FuturesUnordered, Future}; use futures_util::{future::LocalBoxFuture, stream::FuturesUnordered, Future};
@ -45,6 +46,7 @@ pub struct TaskScheduler<'fut> {
pub(super) tasks: RefCell<HashMap<TaskReference, Task>>, pub(super) tasks: RefCell<HashMap<TaskReference, Task>>,
pub(super) tasks_current: Cell<Option<TaskReference>>, pub(super) tasks_current: Cell<Option<TaskReference>>,
pub(super) tasks_queue_blocking: RefCell<VecDeque<TaskReference>>, pub(super) tasks_queue_blocking: RefCell<VecDeque<TaskReference>>,
pub(super) tasks_current_lua_error: Arc<RefCell<Option<LuaError>>>,
// Future tasks & objects for waking // Future tasks & objects for waking
pub(super) futures: AsyncMutex<FuturesUnordered<TaskFuture<'fut>>>, pub(super) futures: AsyncMutex<FuturesUnordered<TaskFuture<'fut>>>,
pub(super) futures_registered_count: Cell<usize>, pub(super) futures_registered_count: Cell<usize>,
@ -58,6 +60,12 @@ impl<'fut> TaskScheduler<'fut> {
*/ */
pub fn new(lua: &'static Lua) -> LuaResult<Self> { pub fn new(lua: &'static Lua) -> LuaResult<Self> {
let (tx, rx) = mpsc::unbounded_channel(); let (tx, rx) = mpsc::unbounded_channel();
let tasks_current_lua_error = Arc::new(RefCell::new(None));
let tasks_current_lua_error_inner = tasks_current_lua_error.clone();
lua.set_interrupt(move || match tasks_current_lua_error_inner.take() {
Some(err) => Err(err),
None => Ok(LuaVmState::Continue),
});
Ok(Self { Ok(Self {
lua, lua,
guid: Cell::new(0), guid: Cell::new(0),
@ -65,6 +73,7 @@ impl<'fut> TaskScheduler<'fut> {
tasks: RefCell::new(HashMap::new()), tasks: RefCell::new(HashMap::new()),
tasks_current: Cell::new(None), tasks_current: Cell::new(None),
tasks_queue_blocking: RefCell::new(VecDeque::new()), tasks_queue_blocking: RefCell::new(VecDeque::new()),
tasks_current_lua_error,
futures: AsyncMutex::new(FuturesUnordered::new()), futures: AsyncMutex::new(FuturesUnordered::new()),
futures_tx: tx, futures_tx: tx,
futures_rx: AsyncMutex::new(rx), futures_rx: AsyncMutex::new(rx),
@ -268,17 +277,13 @@ impl<'fut> TaskScheduler<'fut> {
self.tasks_current.set(Some(reference)); self.tasks_current.set(Some(reference));
let rets = match args_opt_res { let rets = match args_opt_res {
Some(args_res) => match args_res { Some(args_res) => match args_res {
/* Err(e) => {
HACK: Resuming with an error here only works because the Rust // NOTE: Setting this error here means that when the thread
functions that we register and that may return lua errors are // is resumed it will error instantly, so we don't need
also error-aware and wrapped in a special wrapper that checks // to call it with proper args, empty args is fine
if the returned value is a lua error userdata, then throws it *self.tasks_current_lua_error.borrow_mut() = Some(e);
thread.resume(())
Also note that this only happens for our custom async functions }
that may pass errors as arguments when resuming tasks, other
native mlua functions will handle this and dont need wrapping
*/
Err(e) => thread.resume(e),
Ok(args) => thread.resume(args), Ok(args) => thread.resume(args),
}, },
None => thread.resume(()), None => thread.resume(()),

View file

@ -426,6 +426,8 @@ fn fix_error_nitpicks(full_message: String) -> String {
.replace("'require', Line 5", "'[C]' - function require") .replace("'require', Line 5", "'[C]' - function require")
.replace("'require', Line 7", "'[C]' - function require") .replace("'require', Line 7", "'[C]' - function require")
.replace("'require', Line 8", "'[C]' - function require") .replace("'require', Line 8", "'[C]' - function require")
// Same thing here for our async script
.replace("'async', Line 3", "'[C]'")
// Fix error calls in custom script chunks coming through // Fix error calls in custom script chunks coming through
.replace( .replace(
"'[C]' - function error\n Script '[C]' - function require", "'[C]' - function error\n Script '[C]' - function require",