2023-02-13 14:28:18 +00:00
|
|
|
use std::{collections::HashSet, process::ExitCode};
|
2023-01-22 20:23:56 +00:00
|
|
|
|
2023-02-13 14:28:18 +00:00
|
|
|
use lua::task::TaskScheduler;
|
2023-01-23 02:14:13 +00:00
|
|
|
use mlua::prelude::*;
|
2023-02-13 14:28:18 +00:00
|
|
|
use tokio::task::LocalSet;
|
2023-01-21 03:01:02 +00:00
|
|
|
|
2023-02-06 03:32:10 +00:00
|
|
|
pub(crate) mod globals;
|
2023-02-11 21:40:14 +00:00
|
|
|
pub(crate) mod lua;
|
2023-02-06 03:32:10 +00:00
|
|
|
pub(crate) mod utils;
|
2023-01-21 03:01:02 +00:00
|
|
|
|
2023-02-10 11:14:28 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests;
|
2023-01-21 03:01:02 +00:00
|
|
|
|
2023-02-13 14:28:18 +00:00
|
|
|
use crate::utils::formatting::pretty_format_luau_error;
|
2023-01-22 20:23:56 +00:00
|
|
|
|
2023-02-10 11:14:28 +00:00
|
|
|
pub use globals::LuneGlobal;
|
2023-01-22 20:23:56 +00:00
|
|
|
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
|
|
pub struct Lune {
|
2023-02-10 11:14:28 +00:00
|
|
|
includes: HashSet<LuneGlobal>,
|
|
|
|
excludes: HashSet<LuneGlobal>,
|
2023-01-22 20:23:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Lune {
|
2023-02-10 11:14:28 +00:00
|
|
|
/**
|
|
|
|
Creates a new Lune script runner.
|
|
|
|
*/
|
2023-01-22 20:23:56 +00:00
|
|
|
pub fn new() -> Self {
|
|
|
|
Self::default()
|
2023-01-21 03:01:02 +00:00
|
|
|
}
|
2023-01-22 20:23:56 +00:00
|
|
|
|
2023-02-10 11:14:28 +00:00
|
|
|
/**
|
|
|
|
Include a global in the lua environment created for running a Lune script.
|
|
|
|
*/
|
|
|
|
pub fn with_global(mut self, global: LuneGlobal) -> Self {
|
|
|
|
self.includes.insert(global);
|
2023-01-22 20:23:56 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2023-02-10 11:14:28 +00:00
|
|
|
/**
|
|
|
|
Include all globals in the lua environment created for running a Lune script.
|
|
|
|
*/
|
|
|
|
pub fn with_all_globals(mut self) -> Self {
|
|
|
|
for global in LuneGlobal::all::<String>(&[]) {
|
|
|
|
self.includes.insert(global);
|
|
|
|
}
|
2023-01-22 20:23:56 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2023-02-10 11:14:28 +00:00
|
|
|
/**
|
|
|
|
Include all globals in the lua environment created for running a
|
|
|
|
Lune script, as well as supplying args for [`LuneGlobal::Process`].
|
|
|
|
*/
|
|
|
|
pub fn with_all_globals_and_args(mut self, args: Vec<String>) -> Self {
|
|
|
|
for global in LuneGlobal::all(&args) {
|
|
|
|
self.includes.insert(global);
|
2023-01-22 20:23:56 +00:00
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2023-02-10 11:14:28 +00:00
|
|
|
/**
|
|
|
|
Exclude a global from the lua environment created for running a Lune script.
|
|
|
|
|
|
|
|
This should be preferred over manually iterating and filtering
|
|
|
|
which Lune globals to add to the global environment.
|
|
|
|
*/
|
|
|
|
pub fn without_global(mut self, global: LuneGlobal) -> Self {
|
|
|
|
self.excludes.insert(global);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Runs a Lune script.
|
|
|
|
|
|
|
|
This will create a new sandboxed Luau environment with the configured
|
|
|
|
globals and arguments, running inside of a [`tokio::task::LocalSet`].
|
|
|
|
|
2023-02-13 14:28:18 +00:00
|
|
|
Some Lune globals such as [`LuneGlobal::Process`] and [`LuneGlobal::Net`]
|
|
|
|
may spawn separate tokio tasks on other threads, but the Luau environment
|
2023-02-10 11:14:28 +00:00
|
|
|
itself is guaranteed to run on a single thread in the local set.
|
2023-02-11 11:39:39 +00:00
|
|
|
|
2023-02-13 14:28:18 +00:00
|
|
|
Note that this will create a static Lua instance and task scheduler which both
|
|
|
|
will live for the remainer of the program, and that this leaks memory using
|
2023-02-11 11:39:39 +00:00
|
|
|
[`Box::leak`] that will then get deallocated when the program exits.
|
2023-02-10 11:14:28 +00:00
|
|
|
*/
|
|
|
|
pub async fn run(
|
|
|
|
&self,
|
|
|
|
script_name: &str,
|
|
|
|
script_contents: &str,
|
|
|
|
) -> Result<ExitCode, LuaError> {
|
2023-02-13 14:28:18 +00:00
|
|
|
let set = LocalSet::new();
|
2023-02-11 11:39:39 +00:00
|
|
|
let lua = Lua::new().into_static();
|
2023-02-13 14:28:18 +00:00
|
|
|
let sched = TaskScheduler::new(lua)?.into_static();
|
|
|
|
lua.set_app_data(sched);
|
|
|
|
// 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")?)?;
|
|
|
|
let debug: LuaTable = lua.globals().raw_get("debug")?;
|
|
|
|
lua.set_named_registry_value("dbg.info", debug.get::<_, LuaFunction>("info")?)?;
|
2023-01-23 07:38:32 +00:00
|
|
|
// Add in wanted lune globals
|
2023-02-10 11:14:28 +00:00
|
|
|
for global in self.includes.clone() {
|
|
|
|
if !self.excludes.contains(&global) {
|
2023-02-13 14:28:18 +00:00
|
|
|
global.inject(lua, sched)?;
|
2023-01-23 02:14:13 +00:00
|
|
|
}
|
2023-01-23 07:38:32 +00:00
|
|
|
}
|
2023-02-13 14:28:18 +00:00
|
|
|
// Schedule the main thread on the task scheduler
|
|
|
|
sched.schedule_instant(
|
|
|
|
LuaValue::Function(
|
|
|
|
lua.load(script_contents)
|
|
|
|
.set_name(script_name)
|
|
|
|
.unwrap()
|
|
|
|
.into_function()
|
|
|
|
.unwrap(),
|
|
|
|
),
|
|
|
|
LuaValue::Nil.to_lua_multi(lua)?,
|
|
|
|
)?;
|
|
|
|
// Keep running the scheduler until there are either no tasks
|
|
|
|
// left to run, or until some task requests to exit the process
|
|
|
|
let exit_code = set
|
2023-02-03 19:15:20 +00:00
|
|
|
.run_until(async {
|
2023-01-24 16:31:27 +00:00
|
|
|
let mut got_error = false;
|
2023-02-13 14:28:18 +00:00
|
|
|
while let Some(result) = sched.resume_queue().await {
|
|
|
|
match result {
|
|
|
|
Err(e) => {
|
2023-01-24 16:31:27 +00:00
|
|
|
eprintln!("{}", pretty_format_luau_error(&e));
|
|
|
|
got_error = true;
|
|
|
|
}
|
2023-02-13 14:28:18 +00:00
|
|
|
Ok(status) => {
|
|
|
|
if let Some(exit_code) = status.exit_code {
|
|
|
|
return exit_code;
|
|
|
|
} else if status.num_total == 0 {
|
|
|
|
return ExitCode::SUCCESS;
|
|
|
|
}
|
|
|
|
}
|
2023-01-22 21:26:45 +00:00
|
|
|
}
|
2023-01-23 18:51:32 +00:00
|
|
|
}
|
2023-02-13 14:28:18 +00:00
|
|
|
if got_error {
|
|
|
|
ExitCode::FAILURE
|
|
|
|
} else {
|
|
|
|
ExitCode::SUCCESS
|
|
|
|
}
|
2023-01-24 16:31:27 +00:00
|
|
|
})
|
2023-02-13 14:28:18 +00:00
|
|
|
.await;
|
|
|
|
Ok(exit_code)
|
2023-01-21 03:01:02 +00:00
|
|
|
}
|
|
|
|
}
|