Initial implementation of builtin libraries, task library

This commit is contained in:
Filip Tibell 2023-08-19 18:42:28 -05:00
parent 7a63987cbe
commit 0757d6f293
7 changed files with 234 additions and 8 deletions

44
src/lune/builtins/mod.rs Normal file
View file

@ -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<LuaMultiValue<'lua>> {
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<Self, Self::Err> {
match s.trim().to_ascii_lowercase().as_str() {
"task" => Ok(Self::Task),
_ => Err(format!("Unknown builtin library '{s}'")),
}
}
}

View file

@ -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<impl IntoLuaMulti<'_>> {
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<LuaThread<'lua>> {
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<LuaThread<'lua>>
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<LuaThread<'lua>> {
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<f64>) -> LuaResult<f64> {
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())
}

View file

@ -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<LuaThread<'lua>> {
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<Self> {
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()),
}),
}
}
}

View file

@ -3,14 +3,13 @@ use mlua::prelude::*;
use super::context::*; use super::context::*;
pub(super) async fn require<'lua, 'ctx>( pub(super) async fn require<'lua, 'ctx>(
_lua: &'lua Lua, lua: &'lua Lua,
_ctx: &'ctx RequireContext, ctx: &'ctx RequireContext,
name: &str, name: &str,
) -> LuaResult<LuaMultiValue<'lua>> ) -> LuaResult<LuaMultiValue<'lua>>
where where
'lua: 'ctx, 'lua: 'ctx,
'lua: 'static, // FIXME: Remove static lifetime bound here when builtin libraries no longer need it
{ {
Err(LuaError::runtime(format!( ctx.load_builtin(lua, name)
"TODO: Support require for built-in libraries (tried to require '{name}')"
)))
} }

View file

@ -3,7 +3,10 @@ use std::{collections::HashMap, env, path::PathBuf, sync::Arc};
use mlua::prelude::*; use mlua::prelude::*;
use tokio::{fs, sync::Mutex as AsyncMutex}; 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"; const REGISTRY_KEY: &str = "RequireContext";
@ -11,6 +14,7 @@ const REGISTRY_KEY: &str = "RequireContext";
pub(super) struct RequireContext { pub(super) struct RequireContext {
use_absolute_paths: bool, use_absolute_paths: bool,
working_directory: PathBuf, working_directory: PathBuf,
cache_builtins: Arc<AsyncMutex<HashMap<LuneBuiltin, LuaResult<LuaRegistryKey>>>>,
cache_results: Arc<AsyncMutex<HashMap<PathBuf, LuaResult<LuaRegistryKey>>>>, cache_results: Arc<AsyncMutex<HashMap<PathBuf, LuaResult<LuaRegistryKey>>>>,
cache_pending: Arc<AsyncMutex<HashMap<PathBuf, SchedulerThreadId>>>, cache_pending: Arc<AsyncMutex<HashMap<PathBuf, SchedulerThreadId>>>,
} }
@ -24,11 +28,13 @@ impl RequireContext {
than one context may lead to undefined require-behavior. than one context may lead to undefined require-behavior.
*/ */
pub fn new() -> Self { pub fn new() -> Self {
let cwd = env::current_dir().expect("Failed to get current working directory");
Self { Self {
// TODO: Set to false by default, load some kind of config // TODO: Set to false by default, load some kind of config
// or env var to check if we should be using absolute paths // or env var to check if we should be using absolute paths
use_absolute_paths: true, 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_results: Arc::new(AsyncMutex::new(HashMap::new())),
cache_pending: Arc::new(AsyncMutex::new(HashMap::new())), cache_pending: Arc::new(AsyncMutex::new(HashMap::new())),
} }
@ -224,6 +230,58 @@ impl RequireContext {
thread_res thread_res
} }
/**
Loads (requires) the builtin with the given name.
*/
pub fn load_builtin<'lua>(
&self,
lua: &'lua Lua,
name: impl AsRef<str>,
) -> LuaResult<LuaMultiValue<'lua>>
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::<Vec<LuaValue>>(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 {} impl LuaUserData for RequireContext {}

View file

@ -1,5 +1,6 @@
use std::process::ExitCode; use std::process::ExitCode;
mod builtins;
mod error; mod error;
mod globals; mod globals;
mod scheduler; mod scheduler;

View file

@ -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> impl<'lua> TableBuilder<'lua>
where where
'lua: 'static, 'lua: 'static,