mirror of
https://github.com/CompeyDev/lune-packaging.git
synced 2025-01-09 12:19:09 +00:00
Implement traceback helper to use in scheduler
This commit is contained in:
parent
02e4f87a5a
commit
04a47babdd
8 changed files with 183 additions and 16 deletions
|
@ -98,8 +98,7 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable<'static>> {
|
|||
// We want the task scheduler to be transparent,
|
||||
// but it does not return real lua threads, so
|
||||
// we need to override some globals to fake it
|
||||
let globals = lua.globals();
|
||||
let type_original: LuaFunction = globals.get("type")?;
|
||||
let type_original: LuaFunction = lua.named_registry_value("type")?;
|
||||
let type_proxy = lua.create_function(move |_, value: LuaValue| {
|
||||
if let LuaValue::UserData(u) = &value {
|
||||
if u.is::<TaskReference>() {
|
||||
|
@ -108,7 +107,7 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable<'static>> {
|
|||
}
|
||||
type_original.call(value)
|
||||
})?;
|
||||
let typeof_original: LuaFunction = globals.get("typeof")?;
|
||||
let typeof_original: LuaFunction = lua.named_registry_value("typeof")?;
|
||||
let typeof_proxy = lua.create_function(move |_, value: LuaValue| {
|
||||
if let LuaValue::UserData(u) = &value {
|
||||
if u.is::<TaskReference>() {
|
||||
|
@ -117,6 +116,7 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable<'static>> {
|
|||
}
|
||||
typeof_original.call(value)
|
||||
})?;
|
||||
let globals = lua.globals();
|
||||
globals.set("type", type_proxy)?;
|
||||
globals.set("typeof", typeof_proxy)?;
|
||||
// Functions in the built-in coroutine library also need to be
|
||||
|
|
|
@ -14,6 +14,7 @@ mod tests;
|
|||
use crate::utils::formatting::pretty_format_luau_error;
|
||||
|
||||
pub use globals::LuneGlobal;
|
||||
pub use lua::create_lune_lua;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Lune {
|
||||
|
@ -88,18 +89,8 @@ impl Lune {
|
|||
script_name: &str,
|
||||
script_contents: &str,
|
||||
) -> Result<ExitCode, LuaError> {
|
||||
let lua = Lua::new().into_static();
|
||||
// Store original lua global functions in the registry so we can use
|
||||
// them later without passing them around and dealing with lifetimes
|
||||
lua.set_named_registry_value("require", lua.globals().get::<_, LuaFunction>("require")?)?;
|
||||
lua.set_named_registry_value("print", lua.globals().get::<_, LuaFunction>("print")?)?;
|
||||
lua.set_named_registry_value("error", lua.globals().get::<_, LuaFunction>("error")?)?;
|
||||
let coroutine: LuaTable = lua.globals().get("coroutine")?;
|
||||
lua.set_named_registry_value("co.thread", coroutine.get::<_, LuaFunction>("running")?)?;
|
||||
lua.set_named_registry_value("co.yield", coroutine.get::<_, LuaFunction>("yield")?)?;
|
||||
lua.set_named_registry_value("co.close", coroutine.get::<_, LuaFunction>("close")?)?;
|
||||
let debug: LuaTable = lua.globals().raw_get("debug")?;
|
||||
lua.set_named_registry_value("dbg.info", debug.get::<_, LuaFunction>("info")?)?;
|
||||
// Create our special lune-flavored Lua object with extra registry values
|
||||
let lua = create_lune_lua().expect("Failed to create Lua object");
|
||||
// Create our task scheduler and schedule the main thread on it
|
||||
let sched = TaskScheduler::new(lua)?.into_static();
|
||||
lua.set_app_data(sched);
|
||||
|
|
93
packages/lib/src/lua/create.rs
Normal file
93
packages/lib/src/lua/create.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
use mlua::prelude::*;
|
||||
|
||||
/*
|
||||
- Level 0 is the call to info
|
||||
- Level 1 is the load call in create() below where we load this into a function
|
||||
- Level 2 is the call to the scheduler, probably, but we can't know for sure so we start at 2
|
||||
*/
|
||||
const TRACE_IMPL_LUA: &str = r#"
|
||||
local lines = {}
|
||||
for level = 2, 2^8 do
|
||||
local source, line, name = info(level, "sln")
|
||||
if source then
|
||||
if line then
|
||||
if name and #name > 0 then
|
||||
push(lines, format(" Script '%s', Line %d - function %s", source, line, name))
|
||||
else
|
||||
push(lines, format(" Script '%s', Line %d", source, line))
|
||||
end
|
||||
elseif name and #name > 0 then
|
||||
push(lines, format(" Script '%s' - function %s", source, name))
|
||||
else
|
||||
push(lines, format(" Script '%s'", source))
|
||||
end
|
||||
elseif name then
|
||||
push(lines, format("[Lune] - function %s", source, name))
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
if #lines > 0 then
|
||||
push(lines, 1, "Stack Begin")
|
||||
push(lines, "Stack End")
|
||||
return concat(lines, "\n")
|
||||
else
|
||||
return nil
|
||||
end
|
||||
"#;
|
||||
|
||||
/**
|
||||
Creates a [`mlua::Lua`] object with certain globals stored in the Lua registry.
|
||||
|
||||
These globals can then be modified safely after constructing Lua using this function.
|
||||
|
||||
---
|
||||
* `"require"` -> `require`
|
||||
---
|
||||
* `"print"` -> `print`
|
||||
* `"error"` -> `error`
|
||||
---
|
||||
* `"type"` -> `type`
|
||||
* `"typeof"` -> `typeof`
|
||||
---
|
||||
* `"co.thread"` -> `coroutine.running`
|
||||
* `"co.yield"` -> `coroutine.yield`
|
||||
* `"co.close"` -> `coroutine.close`
|
||||
---
|
||||
* `"dbg.info"` -> `debug.info`
|
||||
* `"dbg.trace"` -> `debug.traceback`
|
||||
---
|
||||
*/
|
||||
pub fn create() -> LuaResult<&'static Lua> {
|
||||
let lua = Lua::new().into_static();
|
||||
let globals = &lua.globals();
|
||||
let debug: LuaTable = globals.raw_get("debug")?;
|
||||
let table: LuaTable = globals.raw_get("table")?;
|
||||
let string: LuaTable = globals.raw_get("string")?;
|
||||
let coroutine: LuaTable = globals.get("coroutine")?;
|
||||
// Store original lua global functions in the registry so we can use
|
||||
// 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("print", globals.get::<_, LuaFunction>("print")?)?;
|
||||
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("typeof", globals.get::<_, LuaFunction>("typeof")?)?;
|
||||
lua.set_named_registry_value("co.thread", coroutine.get::<_, LuaFunction>("running")?)?;
|
||||
lua.set_named_registry_value("co.yield", coroutine.get::<_, LuaFunction>("yield")?)?;
|
||||
lua.set_named_registry_value("co.close", coroutine.get::<_, LuaFunction>("close")?)?;
|
||||
lua.set_named_registry_value("dbg.info", debug.get::<_, LuaFunction>("info")?)?;
|
||||
// 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
|
||||
let trace_env = lua.create_table_with_capacity(0, 1)?;
|
||||
trace_env.set("info", debug.get::<_, LuaFunction>("info")?)?;
|
||||
trace_env.set("push", table.get::<_, LuaFunction>("insert")?)?;
|
||||
trace_env.set("concat", table.get::<_, LuaFunction>("concat")?)?;
|
||||
trace_env.set("format", string.get::<_, LuaFunction>("format")?)?;
|
||||
let trace_fn = lua
|
||||
.load(TRACE_IMPL_LUA)
|
||||
.set_environment(trace_env)?
|
||||
.into_function()?;
|
||||
lua.set_named_registry_value("dbg.trace", trace_fn)?;
|
||||
// All done
|
||||
Ok(lua)
|
||||
}
|
|
@ -1,3 +1,7 @@
|
|||
mod create;
|
||||
|
||||
pub mod net;
|
||||
pub mod stdio;
|
||||
pub mod task;
|
||||
|
||||
pub use create::create as create_lune_lua;
|
||||
|
|
|
@ -94,9 +94,12 @@ impl TaskSchedulerState {
|
|||
Returns `true` if the task scheduler is done,
|
||||
meaning it has no lua threads left to run, and
|
||||
no spawned tasks are running in the background.
|
||||
|
||||
Also returns `true` if a task has requested to exit the process.
|
||||
*/
|
||||
pub fn is_done(&self) -> bool {
|
||||
self.num_blocking == 0 && self.num_futures == 0 && self.num_background == 0
|
||||
self.exit_code.is_some()
|
||||
|| (self.num_blocking == 0 && self.num_futures == 0 && self.num_background == 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,26 @@ end)
|
|||
task.wait()
|
||||
assert(flag3 == 3, "Defer should run after spawned threads")
|
||||
|
||||
-- Delay should be able to be nested
|
||||
|
||||
local flag4: boolean = false
|
||||
task.delay(0.05, function()
|
||||
local function nested3()
|
||||
task.delay(0.05, function()
|
||||
flag4 = true
|
||||
end)
|
||||
end
|
||||
local function nested2()
|
||||
task.delay(0.05, nested3)
|
||||
end
|
||||
local function nested1()
|
||||
task.delay(0.05, nested2)
|
||||
end
|
||||
nested1()
|
||||
end)
|
||||
task.wait(0.25)
|
||||
assert(flag4, "Defer should work with nesting")
|
||||
|
||||
-- Varargs should get passed correctly
|
||||
|
||||
local fcheck = require("./fcheck")
|
||||
|
|
|
@ -26,6 +26,34 @@ assert(flag, "Delay should work with yielding (1)")
|
|||
task.wait(0.1)
|
||||
assert(not flag2, "Delay should work with yielding (2)")
|
||||
|
||||
-- Defer should be able to be nested
|
||||
|
||||
local flag4: boolean = false
|
||||
task.defer(function()
|
||||
local function nested3()
|
||||
task.defer(function()
|
||||
task.wait(0.05)
|
||||
flag4 = true
|
||||
end)
|
||||
end
|
||||
local function nested2()
|
||||
task.defer(function()
|
||||
task.wait(0.05)
|
||||
nested3()
|
||||
end)
|
||||
end
|
||||
local function nested1()
|
||||
task.defer(function()
|
||||
task.wait(0.05)
|
||||
nested2()
|
||||
end)
|
||||
end
|
||||
task.wait(0.05)
|
||||
nested1()
|
||||
end)
|
||||
task.wait(0.25)
|
||||
assert(flag4, "Defer should work with nesting")
|
||||
|
||||
-- Varargs should get passed correctly
|
||||
|
||||
local fcheck = require("./fcheck")
|
||||
|
|
|
@ -31,6 +31,34 @@ end)
|
|||
task.spawn(thread2)
|
||||
assert(flag3, "Spawn should run threads made from coroutine.create")
|
||||
|
||||
-- Spawn should be able to be nested
|
||||
|
||||
local flag4: boolean = false
|
||||
task.spawn(function()
|
||||
local function nested3()
|
||||
task.spawn(function()
|
||||
task.wait(0.05)
|
||||
flag4 = true
|
||||
end)
|
||||
end
|
||||
local function nested2()
|
||||
task.spawn(function()
|
||||
task.wait(0.05)
|
||||
nested3()
|
||||
end)
|
||||
end
|
||||
local function nested1()
|
||||
task.spawn(function()
|
||||
task.wait(0.05)
|
||||
nested2()
|
||||
end)
|
||||
end
|
||||
task.wait(0.05)
|
||||
nested1()
|
||||
end)
|
||||
task.wait(0.25)
|
||||
assert(flag4, "Spawn should work with nesting")
|
||||
|
||||
-- Varargs should get passed correctly
|
||||
|
||||
local fcheck = require("./fcheck")
|
||||
|
|
Loading…
Reference in a new issue