use emscripten_rs_sys::em_asm::{SignatureBuilder, emscripten_asm_const_int, js_asm};
use futures::{FutureExt, future::LocalBoxFuture};
use send_wrapper::SendWrapper;
use std::{
cell::Cell,
ffi::c_void,
sync::Arc,
task::{Context, Poll, Wake, Waker},
};
use crate::task::spawn::keepalive::EmscriptenKeepalive;
#[cfg(test)]
mod tests;
mod keepalive {
use emscripten_rs_sys::{emscripten_runtime_keepalive_pop, emscripten_runtime_keepalive_push};
pub struct EmscriptenKeepalive {}
impl Default for EmscriptenKeepalive {
fn default() -> Self {
unsafe { emscripten_runtime_keepalive_push() };
Self {}
}
}
impl Drop for EmscriptenKeepalive {
fn drop(&mut self) {
unsafe { emscripten_runtime_keepalive_pop() };
}
}
}
struct SpawnedTask {
future: LocalBoxFuture<'static, ()>,
mwaker: Arc<QueueMicrotaskWaker>,
waker: Waker,
_keepalive: EmscriptenKeepalive,
}
struct QueueMicrotaskWaker {
state: SendWrapper<MicrotaskWakerState>,
}
struct MicrotaskWakerState {
task: Cell<*mut SpawnedTask>,
notified: Cell<bool>,
is_sleeping: Cell<bool>,
}
impl Wake for QueueMicrotaskWaker {
fn wake(self: Arc<Self>) {
let state = &*self.state;
if !state.notified.replace(true) && state.is_sleeping.get() {
schedule(state.task.get());
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn emscripten_futures_poll_task(task: *mut c_void) {
let task_ptr = task.cast::<SpawnedTask>();
let mut task = unsafe { Box::from_raw(task_ptr) };
let state = &*task.mwaker.state;
state.is_sleeping.set(false);
state.notified.set(false);
let result = task
.future
.poll_unpin(&mut Context::from_waker(&task.waker));
match result {
Poll::Ready(()) => {
drop(task);
}
Poll::Pending => {
state.is_sleeping.set(true);
let notified = state.notified.get();
let task_ptr = Box::into_raw(task);
if notified {
schedule(task_ptr);
}
}
}
}
fn schedule(raw_task: *mut SpawnedTask) {
js_asm! {
|raw_task| {
queueMicrotask(() => {
_emscripten_futures_poll_task(raw_task);
});
}
}
}
pub fn spawn_local(f: impl Future<Output = ()> + 'static) {
spawn_local_boxed(f.boxed_local());
}
fn spawn_local_boxed(f: LocalBoxFuture<'static, ()>) {
let waker_state = SendWrapper::new(MicrotaskWakerState {
task: Cell::default(),
notified: Cell::new(true),
is_sleeping: Cell::new(true),
});
let mwaker = Arc::new(QueueMicrotaskWaker { state: waker_state });
let waker = Waker::from(mwaker.clone());
let mut spawned_task: Box<SpawnedTask> = Box::new(SpawnedTask {
future: f,
mwaker,
waker,
_keepalive: Default::default(),
});
let task_ptr = Box::as_mut_ptr(&mut spawned_task);
spawned_task.mwaker.state.task.set(task_ptr);
schedule(Box::into_raw(spawned_task));
}