use emscripten_rs_sys::em_asm::{SignatureBuilder, emscripten_asm_const_int, js_asm};
use futures::{
FutureExt,
future::LocalBoxFuture,
task::{ArcWake, waker_ref},
};
use send_wrapper::SendWrapper;
use std::{
ffi::c_void,
panic::{AssertUnwindSafe, catch_unwind},
sync::Arc,
task::{Context, Poll},
};
use crate::task::spawn::{keepalive::EmscriptenKeepalive, microtask_waker::MicroTaskWaker};
#[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<MicroTaskWakerWrapper>,
_keepalive: EmscriptenKeepalive,
}
struct MicroTaskWakerWrapper {
wrapper: SendWrapper<microtask_waker::MicroTaskWaker>,
}
impl MicroTaskWakerWrapper {
#[inline(never)]
fn waker(&self) -> &MicroTaskWaker {
&self.wrapper
}
}
mod microtask_waker {
use crate::task::spawn::{SpawnedTask, schedule};
use bitflags::bitflags;
use std::cell::Cell;
bitflags! {
#[derive(Clone, Copy, PartialEq, Eq)]
struct TaskStateFlags : u8 {
const NOTIFIED = 1 << 0;
const SLEEPING = 1 << 1;
}
}
pub struct MicroTaskWaker {
task: Cell<*mut SpawnedTask>,
task_state: Cell<TaskStateFlags>,
}
impl Default for MicroTaskWaker {
fn default() -> Self {
Self {
task: Default::default(),
task_state: Cell::new(TaskStateFlags::all()),
}
}
}
impl MicroTaskWaker {
#[inline]
pub fn init(&self, task: *mut SpawnedTask) {
self.task.replace(task);
}
#[inline]
pub fn start_poll(&self) {
self.task_state.set(TaskStateFlags::empty());
}
#[inline]
pub fn try_wake(&self) {
self.update_state::<{ TaskStateFlags::NOTIFIED.bits() }, { TaskStateFlags::SLEEPING.bits() }>();
}
#[inline]
pub fn try_sleep(&self) {
self.update_state::<{ TaskStateFlags::SLEEPING.bits() }, { TaskStateFlags::NOTIFIED.bits() }>();
}
#[inline]
fn update_state<const SET: u8, const SCHEDULE_IF: u8>(&self) {
let current_state = self.task_state.get().bits();
self.task_state
.set(TaskStateFlags::from_bits_retain(current_state | SET));
if current_state == SCHEDULE_IF {
schedule(self.task.get());
}
}
}
}
impl ArcWake for MicroTaskWakerWrapper {
fn wake_by_ref(arc_self: &Arc<Self>) {
arc_self.waker().try_wake();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn emscripten_futures_poll_task(task: *mut c_void) {
if let Err(payload) = catch_unwind(AssertUnwindSafe(|| {
let task_ptr = task.cast::<SpawnedTask>();
let mut task = unsafe { Box::from_raw(task_ptr) };
let state = task.mwaker.waker();
state.start_poll();
let result = {
let waker = waker_ref(&task.mwaker);
let mut cx = Context::from_waker(&waker);
task.future.poll_unpin(&mut cx)
};
match result {
Poll::Ready(()) => {
drop(task);
}
Poll::Pending => {
state.try_sleep();
let _ = Box::into_raw(task);
}
}
})) {
drop_panic_payload(payload);
}
}
#[cold]
#[inline(never)]
fn drop_panic_payload(payload: Box<dyn std::any::Any + Send>) {
drop(payload);
}
#[inline(never)]
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(MicroTaskWaker::default());
let mwaker = Arc::new(MicroTaskWakerWrapper {
wrapper: waker_state,
});
let mut spawned_task: Box<SpawnedTask> = Box::new(SpawnedTask {
future: f,
mwaker,
_keepalive: Default::default(),
});
let task_ptr = Box::as_mut_ptr(&mut spawned_task);
spawned_task.mwaker.waker().init(task_ptr);
schedule(Box::into_raw(spawned_task));
}