use emscripten_rs_sys::em_asm::{SignatureBuilder, emscripten_asm_const_int, js_asm};
use futures::{FutureExt, future::LocalBoxFuture, task::waker};
use std::{
ffi::c_void,
panic::{AssertUnwindSafe, catch_unwind},
sync::Arc,
task::{Context, Poll, Waker},
};
use crate::{
send_wrapper::SendWrapper,
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, ()>,
inner_waker: Arc<SendWrapper<microtask_waker::MicroTaskWaker>>,
waker: Waker,
_keepalive: EmscriptenKeepalive,
}
mod microtask_waker {
use crate::{
send_wrapper::SendWrapper,
task::spawn::{SpawnedTask, schedule},
};
use futures::task::ArcWake;
use std::{cell::Cell, sync::Arc};
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Eq, Default)]
enum MicroTaskState {
#[default]
Sleeping,
Polled,
NeedsScheduling,
}
pub struct MicroTaskWaker {
task: *mut SpawnedTask,
task_state: Cell<MicroTaskState>,
}
impl MicroTaskWaker {
#[inline]
pub(super) fn new(task: *mut SpawnedTask) -> Self {
Self {
task,
task_state: Cell::default()
}
}
#[inline]
pub fn before_poll(&self) {
self.task_state.set(MicroTaskState::Polled);
}
#[inline]
pub fn try_wake(&self) {
if self.task_state.replace(MicroTaskState::NeedsScheduling) == MicroTaskState::Sleeping
{
schedule(self.task);
}
}
#[inline]
pub fn try_sleep(&self) {
if self.task_state.replace(MicroTaskState::Sleeping) == MicroTaskState::NeedsScheduling
{
schedule(self.task);
}
}
}
impl ArcWake for SendWrapper<MicroTaskWaker> {
fn wake_by_ref(arc_self: &Arc<Self>) {
arc_self.as_ref().as_ref().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 waker = task.inner_waker.as_ref().as_ref();
waker.before_poll();
let result = task
.future
.poll_unpin(&mut Context::from_waker(&task.waker));
match result {
Poll::Ready(()) => {
drop(task);
}
Poll::Pending => {
waker.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);
}
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 mut uninitialized_task = Box::new_uninit();
let microtask_waker = MicroTaskWaker::new(uninitialized_task.as_mut_ptr() as _);
let inner_waker = Arc::new(SendWrapper::new(microtask_waker));
let waker = waker(inner_waker.clone());
uninitialized_task.write(SpawnedTask {
future: f,
inner_waker,
waker,
_keepalive: Default::default(),
});
let task = unsafe { uninitialized_task.assume_init() };
schedule(Box::into_raw(task));
}