use std::cell::{Cell, RefCell};
use std::rc::Rc;
use crate::value::{AsyncPromise, Channel, Value};
use crate::{EvalContext, SemaError};
#[derive(Debug, Clone)]
pub enum YieldReason {
AwaitPromise(Rc<AsyncPromise>),
ChannelRecv(Rc<Channel>),
ChannelSend(Rc<Channel>, Value),
Sleep(u64),
}
#[derive(Clone)]
pub enum SchedulerTarget {
All,
One(Rc<AsyncPromise>),
AllOf(Vec<Rc<AsyncPromise>>),
AnyOf(Vec<Rc<AsyncPromise>>),
Timeout(Rc<AsyncPromise>, u64),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SchedulerRunResult {
Complete,
TimedOut,
}
thread_local! {
static YIELD_SIGNAL: RefCell<Option<YieldReason>> = const { RefCell::new(None) };
static RESUME_VALUE: RefCell<Option<Value>> = const { RefCell::new(None) };
static IN_ASYNC_CONTEXT: Cell<bool> = const { Cell::new(false) };
}
pub fn set_yield_signal(reason: YieldReason) {
YIELD_SIGNAL.with(|s| *s.borrow_mut() = Some(reason));
}
pub fn take_yield_signal() -> Option<YieldReason> {
YIELD_SIGNAL.with(|s| s.borrow_mut().take())
}
pub fn set_resume_value(val: Value) {
RESUME_VALUE.with(|r| *r.borrow_mut() = Some(val));
}
pub fn take_resume_value() -> Option<Value> {
RESUME_VALUE.with(|r| r.borrow_mut().take())
}
pub fn in_async_context() -> bool {
IN_ASYNC_CONTEXT.with(|c| c.get())
}
pub fn set_async_context(val: bool) {
IN_ASYNC_CONTEXT.with(|c| c.set(val));
}
pub type SpawnCallbackFn = fn(&EvalContext, Value) -> Result<Value, SemaError>;
thread_local! {
static SPAWN_CALLBACK: Cell<Option<SpawnCallbackFn>> = const { Cell::new(None) };
}
pub fn set_spawn_callback(f: SpawnCallbackFn) {
SPAWN_CALLBACK.with(|cb| cb.set(Some(f)));
}
pub fn call_spawn_callback(ctx: &EvalContext, thunk: Value) -> Result<Value, SemaError> {
let f = SPAWN_CALLBACK.with(|cb| cb.get()).ok_or_else(|| {
SemaError::eval(
"async/spawn: no async scheduler registered (async requires the VM backend)"
.to_string(),
)
})?;
f(ctx, thunk)
}
pub type RunSchedulerCallbackFn =
fn(&EvalContext, SchedulerTarget) -> Result<SchedulerRunResult, SemaError>;
thread_local! {
static RUN_SCHEDULER_CALLBACK: Cell<Option<RunSchedulerCallbackFn>> = const { Cell::new(None) };
}
pub fn set_run_scheduler_callback(f: RunSchedulerCallbackFn) {
RUN_SCHEDULER_CALLBACK.with(|cb| cb.set(Some(f)));
}
pub type CancelCallbackFn = fn(u64) -> Result<bool, SemaError>;
thread_local! {
static CANCEL_CALLBACK: Cell<Option<CancelCallbackFn>> = const { Cell::new(None) };
}
pub fn set_cancel_callback(f: CancelCallbackFn) {
CANCEL_CALLBACK.with(|cb| cb.set(Some(f)));
}
pub fn call_cancel_callback(task_id: u64) -> Result<bool, SemaError> {
let f = CANCEL_CALLBACK.with(|cb| cb.get()).ok_or_else(|| {
SemaError::eval("async/cancel: no async scheduler registered".to_string())
})?;
f(task_id)
}
pub fn call_run_scheduler(
ctx: &EvalContext,
target: Option<Rc<AsyncPromise>>,
) -> Result<(), SemaError> {
let f = RUN_SCHEDULER_CALLBACK.with(|cb| cb.get()).ok_or_else(|| {
SemaError::eval(
"async: no async scheduler registered (async requires the VM backend)".to_string(),
)
})?;
let target = match target {
Some(promise) => SchedulerTarget::One(promise),
None => SchedulerTarget::All,
};
f(ctx, target).map(|_| ())
}
pub fn call_run_scheduler_all_of(
ctx: &EvalContext,
targets: Vec<Rc<AsyncPromise>>,
) -> Result<(), SemaError> {
let f = RUN_SCHEDULER_CALLBACK.with(|cb| cb.get()).ok_or_else(|| {
SemaError::eval(
"async: no async scheduler registered (async requires the VM backend)".to_string(),
)
})?;
f(ctx, SchedulerTarget::AllOf(targets)).map(|_| ())
}
pub fn call_run_scheduler_any_of(
ctx: &EvalContext,
targets: Vec<Rc<AsyncPromise>>,
) -> Result<(), SemaError> {
let f = RUN_SCHEDULER_CALLBACK.with(|cb| cb.get()).ok_or_else(|| {
SemaError::eval(
"async: no async scheduler registered (async requires the VM backend)".to_string(),
)
})?;
f(ctx, SchedulerTarget::AnyOf(targets)).map(|_| ())
}
pub fn call_run_scheduler_timeout(
ctx: &EvalContext,
target: Rc<AsyncPromise>,
timeout_ms: u64,
) -> Result<SchedulerRunResult, SemaError> {
let f = RUN_SCHEDULER_CALLBACK.with(|cb| cb.get()).ok_or_else(|| {
SemaError::eval(
"async: no async scheduler registered (async requires the VM backend)".to_string(),
)
})?;
f(ctx, SchedulerTarget::Timeout(target, timeout_ms))
}