use crate::observer::ObserverNotified;
use js_sys::Function;
use js_sys::Reflect;
use js_sys::wasm_bindgen;
use std::cell::Cell;
use std::convert::Infallible;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use wasm_bindgen::JsCast;
use wasm_bindgen::closure::Closure;
#[cfg(test)]
pub use wasm_safe_thread as thread;
thread_local! {
static RUNNING_TASKS: Cell<usize> = const { Cell::new(0) };
static INSTALLED_CLOSE_HANDLER: Cell<Option<Function>> = const { Cell::new(None) };
static CLOSE_IS_CALLED: Cell<bool> = const { Cell::new(false) };
}
struct WasmStaticTaskFuture<F, N, E>
where
F: Future + 'static,
N: ObserverNotified<F::Output>,
E: crate::SomeStaticExecutor,
F::Output: 'static + Unpin,
{
spawned: Pin<Box<crate::task::SpawnedStaticTask<F, N, E>>>,
}
impl<F, N, E> WasmStaticTaskFuture<F, N, E>
where
F: Future + 'static,
N: ObserverNotified<F::Output>,
E: crate::SomeStaticExecutor,
F::Output: 'static + Unpin,
{
fn new(spawned: crate::task::SpawnedStaticTask<F, N, E>) -> Self {
Self {
spawned: Box::pin(spawned),
}
}
}
impl<F, N, E> Future for WasmStaticTaskFuture<F, N, E>
where
F: Future + 'static,
N: ObserverNotified<F::Output>,
E: crate::SomeStaticExecutor,
F::Output: 'static + Unpin,
{
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.spawned.as_mut().poll::<Infallible>(cx, None, None)
}
}
impl<F, N, E> Drop for WasmStaticTaskFuture<F, N, E>
where
F: Future + 'static,
N: ObserverNotified<F::Output>,
E: crate::SomeStaticExecutor,
F::Output: 'static + Unpin,
{
fn drop(&mut self) {
RUNNING_TASKS.with(|running| {
running.set(running.get() - 1);
});
consider_closing();
}
}
#[allow(dead_code)]
pub(crate) fn run_local_task<F, N, E>(_spawned: crate::task::SpawnedLocalTask<F, N, E>)
where
F: Future,
N: ObserverNotified<F::Output>,
E: for<'a> crate::SomeLocalExecutor<'a>,
{
panic!(
"Local task spawning without a proper executor is no longer supported. Please configure a local executor before spawning local tasks."
);
}
fn patch_if_needed() {
INSTALLED_CLOSE_HANDLER.with(|installed| {
let t = installed.take();
if t.is_some() {
INSTALLED_CLOSE_HANDLER.set(t);
} else {
let global = js_sys::global();
let orig_close: Function = match Reflect::get(&global, &"close".into())
.expect("global.close should exist")
.dyn_into()
{
Ok(f) => f,
Err(_) => {
web_sys::console::error_1(
&"Global close function not found; might be node?".into(),
);
return;
}
};
let wrapper = Closure::wrap(Box::new(move || {
CLOSE_IS_CALLED.with(|c| c.set(true));
consider_closing();
}) as Box<dyn Fn()>);
Reflect::set(&global, &"close".into(), wrapper.as_ref().unchecked_ref())
.expect("failed to patch close");
wrapper.forget();
installed.set(Some(orig_close));
}
});
}
fn consider_closing() {
if !CLOSE_IS_CALLED.with(|c| c.get()) {
return;
}
RUNNING_TASKS.with(|running| {
let count = running.get();
if count > 0 {
return;
}
INSTALLED_CLOSE_HANDLER.with(|installed| {
if let Some(orig_close) = installed.take() {
orig_close
.call0(&js_sys::global())
.expect("Failed to call original close");
}
});
});
}
pub(crate) fn run_static_task<F, N, E>(spawned: crate::task::SpawnedStaticTask<F, N, E>)
where
F: Future + 'static,
N: ObserverNotified<F::Output>,
E: crate::SomeStaticExecutor,
F::Output: 'static + Unpin,
{
patch_if_needed();
let wasm_future = WasmStaticTaskFuture::new(spawned);
RUNNING_TASKS.with(|running| {
running.set(running.get() + 1);
});
wasm_bindgen_futures::spawn_local(wasm_future);
}