mod context;
mod runloop;
mod var;
use dyn_cache::local::SharedLocalCache;
use futures::{
future::LocalFutureObj,
task::{noop_waker, LocalSpawn, SpawnError},
};
use illicit::AsContext;
use std::{
fmt::{Debug, Formatter, Result as FmtResult},
rc::Rc,
task::Waker,
};
pub(crate) use context::Context;
pub use runloop::RunLoop;
pub(crate) use var::Var;
#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Revision(pub u64);
impl Revision {
pub fn current() -> Self {
if let Ok(r) = illicit::get::<Context>() {
r.revision()
} else {
Revision::default()
}
}
}
impl std::fmt::Debug for Revision {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_fmt(format_args!("r{}", self.0))
}
}
pub struct Runtime {
revision: Revision,
cache: SharedLocalCache,
spawner: Spawner,
wk: Waker,
}
impl Default for Runtime {
fn default() -> Runtime {
Runtime::new()
}
}
impl Runtime {
pub fn new() -> Self {
Self {
spawner: Spawner(Rc::new(JunkSpawner)),
revision: Revision(0),
cache: SharedLocalCache::default(),
wk: noop_waker(),
}
}
pub fn revision(&self) -> Revision {
self.revision
}
pub fn run_once<Out>(&mut self, op: impl FnOnce() -> Out) -> Out {
self.revision.0 += 1;
let ret = self.context_handle().offer(|| topo::call(op));
self.cache.gc();
ret
}
pub fn set_state_change_waker(&mut self, wk: Waker) {
self.wk = wk;
}
pub fn set_task_executor(&mut self, sp: impl LocalSpawn + 'static) {
self.spawner = Spawner(Rc::new(sp));
}
}
#[derive(Clone)]
struct Spawner(pub Rc<dyn LocalSpawn>);
impl Debug for Spawner {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
f.write_fmt(format_args!("{:p}", &self.0))
}
}
struct JunkSpawner;
impl LocalSpawn for JunkSpawner {
fn spawn_local_obj(&self, _: LocalFutureObj<'static, ()>) -> Result<(), SpawnError> {
Err(SpawnError::shutdown())
}
fn status_local(&self) -> Result<(), SpawnError> {
Err(SpawnError::shutdown())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn propagating_env_to_runtime() {
let first_byte = 0u8;
let mut runtime = RunLoop::new(|| {
let from_env: u8 = *illicit::expect();
assert_eq!(from_env, first_byte);
});
assert!(illicit::get::<u8>().is_err());
first_byte.offer(|| {
topo::call(|| runtime.run_once());
});
assert!(illicit::get::<u8>().is_err());
}
#[test]
fn tick_a_few_times() {
let mut rt = RunLoop::new(Revision::current);
assert_eq!(rt.run_once(), Revision(1));
assert_eq!(rt.run_once(), Revision(2));
assert_eq!(rt.run_once(), Revision(3));
assert_eq!(rt.run_once(), Revision(4));
assert_eq!(rt.run_once(), Revision(5));
}
}