use std::cell::{OnceCell, RefCell, RefMut};
use crate::{Error, Runtime, RuntimeOptions};
pub struct StaticRuntimeLock<'a> {
lock: RefMut<'a, Result<Runtime, Error>>,
}
impl StaticRuntimeLock<'_> {
pub fn runtime(&mut self) -> &mut Runtime {
match self.lock.as_mut() {
Ok(rt) => rt,
Err(_) => unreachable!("Could not get runtime lock"),
}
}
}
pub struct StaticRuntime {
init_options: fn() -> RuntimeOptions,
cell: OnceCell<RefCell<Result<Runtime, Error>>>,
}
impl StaticRuntime {
pub const fn new(init_options: fn() -> RuntimeOptions) -> Self {
Self {
init_options,
cell: OnceCell::new(),
}
}
fn cell_ref(&self) -> &RefCell<Result<Runtime, Error>> {
self.cell
.get_or_init(|| RefCell::new(Runtime::new((self.init_options)())))
}
pub fn lock(&self) -> Result<StaticRuntimeLock<'_>, Error> {
let rt_mut = self.cell_ref();
if let Err(e) = rt_mut.borrow_mut().as_ref() {
return Err(Error::Runtime(format!(
"Could not initialize static runtime: {e}"
)));
}
Ok(StaticRuntimeLock {
lock: rt_mut.borrow_mut(),
})
}
pub fn with_runtime<T>(&self, mut callback: impl FnMut(&mut Runtime) -> T) -> Result<T, Error> {
let rt_mut = self.cell_ref();
match rt_mut.borrow_mut().as_mut() {
Ok(rt) => Ok(callback(rt)),
Err(e) => Err(Error::Runtime(format!(
"Could not initialize static runtime: {e}"
))),
}
}
}
#[macro_export]
macro_rules! static_runtime {
($name:ident, $options:block) => {
#[allow(non_snake_case)]
mod $name {
fn init_options() -> $crate::RuntimeOptions {
#[allow(unused_imports)]
use super::*;
$options
}
thread_local! {
static RUNTIME: $crate::static_runtime::StaticRuntime
= const { $crate::static_runtime::StaticRuntime::new(init_options) };
}
#[allow(dead_code)]
pub fn with<T, F>(callback: F) -> Result<T, $crate::Error>
where
F: FnMut(&mut $crate::Runtime) -> Result<T, $crate::Error>,
{
RUNTIME.with(|rt| rt.with_runtime(callback))?
}
}
};
($name:ident) => {
static_runtime!($name, { $crate::RuntimeOptions::default() });
};
}
#[cfg(test)]
mod test {
use super::*;
use std::time::Duration;
static_runtime!(MY_DEFAULT_RUNTIME);
static_runtime!(MY_CUSTOM_RUNTIME, {
RuntimeOptions {
timeout: Duration::from_secs(5),
..Default::default()
}
});
#[test]
fn test_static_runtime() {
MY_DEFAULT_RUNTIME::with(|runtime| runtime.eval::<()>("console.log('Hello, world!')"))
.unwrap();
MY_CUSTOM_RUNTIME::with(|runtime| runtime.eval::<()>("console.log('Hello, world!')"))
.unwrap();
}
}