From 0757d6f29364aa29d8df9f0acc22a7dbbdb0b7be Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sat, 19 Aug 2023 18:42:28 -0500 Subject: [PATCH] Initial implementation of builtin libraries, task library --- src/lune/builtins/mod.rs | 44 ++++++++++++++ src/lune/builtins/task/mod.rs | 93 +++++++++++++++++++++++++++++ src/lune/builtins/task/tof.rs | 30 ++++++++++ src/lune/globals/require/builtin.rs | 9 ++- src/lune/globals/require/context.rs | 62 ++++++++++++++++++- src/lune/mod.rs | 1 + src/lune/util/table_builder.rs | 3 +- 7 files changed, 234 insertions(+), 8 deletions(-) create mode 100644 src/lune/builtins/mod.rs create mode 100644 src/lune/builtins/task/mod.rs create mode 100644 src/lune/builtins/task/tof.rs diff --git a/src/lune/builtins/mod.rs b/src/lune/builtins/mod.rs new file mode 100644 index 0000000..666f71d --- /dev/null +++ b/src/lune/builtins/mod.rs @@ -0,0 +1,44 @@ +use std::str::FromStr; + +use mlua::prelude::*; + +mod task; + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub enum LuneBuiltin { + Task, +} + +impl<'lua> LuneBuiltin +where + 'lua: 'static, // FIXME: Remove static lifetime bound here when builtin libraries no longer need it +{ + pub fn name(&self) -> &'static str { + match self { + Self::Task => "task", + } + } + + pub fn create(&self, lua: &'lua Lua) -> LuaResult> { + let res = match self { + Self::Task => task::create(lua), + }; + match res { + Ok(v) => Ok(v.into_lua_multi(lua)?), + Err(e) => Err(e.context(format!( + "Failed to create builtin library '{}'", + self.name() + ))), + } + } +} + +impl FromStr for LuneBuiltin { + type Err = String; + fn from_str(s: &str) -> Result { + match s.trim().to_ascii_lowercase().as_str() { + "task" => Ok(Self::Task), + _ => Err(format!("Unknown builtin library '{s}'")), + } + } +} diff --git a/src/lune/builtins/task/mod.rs b/src/lune/builtins/task/mod.rs new file mode 100644 index 0000000..1c2ff96 --- /dev/null +++ b/src/lune/builtins/task/mod.rs @@ -0,0 +1,93 @@ +use std::time::Duration; + +use mlua::prelude::*; + +use tokio::time::{self, Instant}; + +use crate::lune::{scheduler::Scheduler, util::TableBuilder}; + +mod tof; +use tof::LuaThreadOrFunction; + +pub fn create(lua: &'static Lua) -> LuaResult> { + TableBuilder::new(lua)? + .with_function("cancel", task_cancel)? + .with_function("defer", task_defer)? + .with_function("delay", task_delay)? + .with_function("spawn", task_spawn)? + .with_async_function("wait", task_wait)? + .build_readonly() +} + +fn task_cancel(lua: &Lua, thread: LuaThread) -> LuaResult<()> { + let close = lua + .globals() + .get::<_, LuaTable>("coroutine")? + .get::<_, LuaFunction>("close")?; + match close.call(thread) { + Err(LuaError::CoroutineInactive) => Ok(()), + Err(e) => Err(e), + Ok(()) => Ok(()), + } +} + +fn task_defer<'lua>( + lua: &'lua Lua, + (tof, args): (LuaThreadOrFunction<'lua>, LuaMultiValue<'_>), +) -> LuaResult> { + let thread = tof.into_thread(lua)?; + let sched = lua + .app_data_ref::<&Scheduler>() + .expect("Lua struct is missing scheduler"); + sched.push_back(thread.clone(), args)?; + Ok(thread) +} + +// FIXME: `self` escapes outside of method because we are borrowing `tof` and +// `args` when we call `schedule_future_thread` in the lua function body below +// For now we solve this by using the 'static lifetime bound in the impl +fn task_delay<'lua>( + lua: &'lua Lua, + (secs, tof, args): (f64, LuaThreadOrFunction<'lua>, LuaMultiValue<'lua>), +) -> LuaResult> +where + 'lua: 'static, +{ + let thread = tof.into_thread(lua)?; + let sched = lua + .app_data_ref::<&Scheduler>() + .expect("Lua struct is missing scheduler"); + + let thread2 = thread.clone(); + sched.schedule_future_thread(thread.clone(), async move { + let duration = Duration::from_secs_f64(secs); + time::sleep(duration).await; + sched.push_back(thread2, args)?; + Ok(()) + })?; + + Ok(thread) +} + +fn task_spawn<'lua>( + lua: &'lua Lua, + (tof, args): (LuaThreadOrFunction<'lua>, LuaMultiValue<'_>), +) -> LuaResult> { + let thread = tof.into_thread(lua)?; + let resume = lua + .globals() + .get::<_, LuaTable>("coroutine")? + .get::<_, LuaFunction>("resume")?; + resume.call((thread.clone(), args))?; + Ok(thread) +} + +async fn task_wait(_: &Lua, secs: Option) -> LuaResult { + let duration = Duration::from_secs_f64(secs.unwrap_or_default()); + + let before = Instant::now(); + time::sleep(duration).await; + let after = Instant::now(); + + Ok((after - before).as_secs_f64()) +} diff --git a/src/lune/builtins/task/tof.rs b/src/lune/builtins/task/tof.rs new file mode 100644 index 0000000..e63cd2b --- /dev/null +++ b/src/lune/builtins/task/tof.rs @@ -0,0 +1,30 @@ +use mlua::prelude::*; + +#[derive(Clone)] +pub(super) enum LuaThreadOrFunction<'lua> { + Thread(LuaThread<'lua>), + Function(LuaFunction<'lua>), +} + +impl<'lua> LuaThreadOrFunction<'lua> { + pub(super) fn into_thread(self, lua: &'lua Lua) -> LuaResult> { + match self { + Self::Thread(t) => Ok(t), + Self::Function(f) => lua.create_thread(f), + } + } +} + +impl<'lua> FromLua<'lua> for LuaThreadOrFunction<'lua> { + fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { + match value { + LuaValue::Thread(t) => Ok(Self::Thread(t)), + LuaValue::Function(f) => Ok(Self::Function(f)), + value => Err(LuaError::FromLuaConversionError { + from: value.type_name(), + to: "LuaThreadOrFunction", + message: Some("Expected thread or function".to_string()), + }), + } + } +} diff --git a/src/lune/globals/require/builtin.rs b/src/lune/globals/require/builtin.rs index e7a5171..914d1c1 100644 --- a/src/lune/globals/require/builtin.rs +++ b/src/lune/globals/require/builtin.rs @@ -3,14 +3,13 @@ use mlua::prelude::*; use super::context::*; pub(super) async fn require<'lua, 'ctx>( - _lua: &'lua Lua, - _ctx: &'ctx RequireContext, + lua: &'lua Lua, + ctx: &'ctx RequireContext, name: &str, ) -> LuaResult> where 'lua: 'ctx, + 'lua: 'static, // FIXME: Remove static lifetime bound here when builtin libraries no longer need it { - Err(LuaError::runtime(format!( - "TODO: Support require for built-in libraries (tried to require '{name}')" - ))) + ctx.load_builtin(lua, name) } diff --git a/src/lune/globals/require/context.rs b/src/lune/globals/require/context.rs index acf2c1c..ceba8f2 100644 --- a/src/lune/globals/require/context.rs +++ b/src/lune/globals/require/context.rs @@ -3,7 +3,10 @@ use std::{collections::HashMap, env, path::PathBuf, sync::Arc}; use mlua::prelude::*; use tokio::{fs, sync::Mutex as AsyncMutex}; -use crate::lune::scheduler::{IntoLuaOwnedThread, Scheduler, SchedulerThreadId}; +use crate::lune::{ + builtins::LuneBuiltin, + scheduler::{IntoLuaOwnedThread, Scheduler, SchedulerThreadId}, +}; const REGISTRY_KEY: &str = "RequireContext"; @@ -11,6 +14,7 @@ const REGISTRY_KEY: &str = "RequireContext"; pub(super) struct RequireContext { use_absolute_paths: bool, working_directory: PathBuf, + cache_builtins: Arc>>>, cache_results: Arc>>>, cache_pending: Arc>>, } @@ -24,11 +28,13 @@ impl RequireContext { than one context may lead to undefined require-behavior. */ pub fn new() -> Self { + let cwd = env::current_dir().expect("Failed to get current working directory"); Self { // TODO: Set to false by default, load some kind of config // or env var to check if we should be using absolute paths use_absolute_paths: true, - working_directory: env::current_dir().expect("Failed to get current working directory"), + working_directory: cwd, + cache_builtins: Arc::new(AsyncMutex::new(HashMap::new())), cache_results: Arc::new(AsyncMutex::new(HashMap::new())), cache_pending: Arc::new(AsyncMutex::new(HashMap::new())), } @@ -224,6 +230,58 @@ impl RequireContext { thread_res } + + /** + Loads (requires) the builtin with the given name. + */ + pub fn load_builtin<'lua>( + &self, + lua: &'lua Lua, + name: impl AsRef, + ) -> LuaResult> + where + 'lua: 'static, // FIXME: Remove static lifetime bound here when builtin libraries no longer need it + { + let builtin: LuneBuiltin = match name.as_ref().parse() { + Err(e) => return Err(LuaError::runtime(e)), + Ok(b) => b, + }; + + let mut cache = self + .cache_builtins + .try_lock() + .expect("RequireContext may not be used from multiple threads"); + + if let Some(res) = cache.get(&builtin) { + return match res { + Err(e) => return Err(e.clone()), + Ok(key) => { + let multi_vec = lua + .registry_value::>(key) + .expect("Missing builtin result in lua registry"); + Ok(LuaMultiValue::from_vec(multi_vec)) + } + }; + }; + + let result = builtin.create(lua); + + cache.insert( + builtin, + match result.clone() { + Err(e) => Err(e), + Ok(multi) => { + let multi_vec = multi.into_vec(); + let multi_key = lua + .create_registry_value(multi_vec) + .expect("Failed to store require result in registry"); + Ok(multi_key) + } + }, + ); + + result + } } impl LuaUserData for RequireContext {} diff --git a/src/lune/mod.rs b/src/lune/mod.rs index 1b5a602..8ac8a88 100644 --- a/src/lune/mod.rs +++ b/src/lune/mod.rs @@ -1,5 +1,6 @@ use std::process::ExitCode; +mod builtins; mod error; mod globals; mod scheduler; diff --git a/src/lune/util/table_builder.rs b/src/lune/util/table_builder.rs index 7db4910..9f022d4 100644 --- a/src/lune/util/table_builder.rs +++ b/src/lune/util/table_builder.rs @@ -52,7 +52,8 @@ impl<'lua> TableBuilder<'lua> { } } -// FIXME: Remove static lifetime bound here when possible and move into above impl +// FIXME: Remove static lifetime bound here when `create_async_function` +// no longer needs it to compile, then move this into the above impl impl<'lua> TableBuilder<'lua> where 'lua: 'static,