use std::{any::Any, cell::RefCell, collections::VecDeque, fmt::Debug, future::Future, pin::Pin};
use crate::{
object::{JsFunction, NativeObject},
realm::Realm,
vm::ActiveRunnable,
Context, JsResult, JsValue,
};
use boa_gc::{Finalize, Trace};
pub type FutureJob = Pin<Box<dyn Future<Output = NativeJob> + 'static>>;
pub struct NativeJob {
#[allow(clippy::type_complexity)]
f: Box<dyn FnOnce(&mut Context<'_>) -> JsResult<JsValue>>,
realm: Option<Realm>,
active_runnable: Option<ActiveRunnable>,
}
impl Debug for NativeJob {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NativeJob").field("f", &"Closure").finish()
}
}
impl NativeJob {
pub fn new<F>(f: F) -> Self
where
F: FnOnce(&mut Context<'_>) -> JsResult<JsValue> + 'static,
{
Self {
f: Box::new(f),
realm: None,
active_runnable: None,
}
}
pub fn with_realm<F>(f: F, realm: Realm, context: &mut Context<'_>) -> Self
where
F: FnOnce(&mut Context<'_>) -> JsResult<JsValue> + 'static,
{
Self {
f: Box::new(f),
realm: Some(realm),
active_runnable: context.vm.active_runnable.clone(),
}
}
pub const fn realm(&self) -> Option<&Realm> {
self.realm.as_ref()
}
pub fn call(mut self, context: &mut Context<'_>) -> JsResult<JsValue> {
if let Some(realm) = self.realm {
let old_realm = context.enter_realm(realm);
std::mem::swap(&mut context.vm.active_runnable, &mut self.active_runnable);
let result = (self.f)(context);
context.enter_realm(old_realm);
std::mem::swap(&mut context.vm.active_runnable, &mut self.active_runnable);
result
} else {
(self.f)(context)
}
}
}
#[derive(Trace, Finalize)]
pub struct JobCallback {
callback: JsFunction,
host_defined: Box<dyn NativeObject>,
}
impl Debug for JobCallback {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("JobCallback")
.field("callback", &self.callback)
.field("host_defined", &"dyn NativeObject")
.finish()
}
}
impl JobCallback {
pub fn new<T: Any + Trace>(callback: JsFunction, host_defined: T) -> Self {
Self {
callback,
host_defined: Box::new(host_defined),
}
}
pub const fn callback(&self) -> &JsFunction {
&self.callback
}
pub fn host_defined(&self) -> &dyn Any {
self.host_defined.as_any()
}
pub fn host_defined_mut(&mut self) -> &mut dyn Any {
self.host_defined.as_mut_any()
}
}
pub trait JobQueue {
fn enqueue_promise_job(&self, job: NativeJob, context: &mut Context<'_>);
fn run_jobs(&self, context: &mut Context<'_>);
fn enqueue_future_job(&self, future: FutureJob, context: &mut Context<'_>);
fn run_jobs_async<'a, 'ctx, 'host, 'fut>(
&'a self,
context: &'ctx mut Context<'host>,
) -> Pin<Box<dyn Future<Output = ()> + 'fut>>
where
'a: 'fut,
'ctx: 'fut,
'host: 'fut,
{
Box::pin(async { self.run_jobs(context) })
}
}
#[derive(Debug, Clone, Copy)]
pub struct IdleJobQueue;
impl JobQueue for IdleJobQueue {
fn enqueue_promise_job(&self, _: NativeJob, _: &mut Context<'_>) {}
fn run_jobs(&self, _: &mut Context<'_>) {}
fn enqueue_future_job(&self, _: FutureJob, _: &mut Context<'_>) {}
}
#[derive(Default)]
pub struct SimpleJobQueue(RefCell<VecDeque<NativeJob>>);
impl Debug for SimpleJobQueue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("SimpleQueue").field(&"..").finish()
}
}
impl SimpleJobQueue {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
impl JobQueue for SimpleJobQueue {
fn enqueue_promise_job(&self, job: NativeJob, _: &mut Context<'_>) {
self.0.borrow_mut().push_back(job);
}
fn run_jobs(&self, context: &mut Context<'_>) {
let mut next_job = self.0.borrow_mut().pop_front();
while let Some(job) = next_job {
if job.call(context).is_err() {
self.0.borrow_mut().clear();
return;
};
next_job = self.0.borrow_mut().pop_front();
}
}
fn enqueue_future_job(&self, future: FutureJob, context: &mut Context<'_>) {
let job = pollster::block_on(future);
self.enqueue_promise_job(job, context);
}
}