use std::ffi::c_void;
use std::ptr;
use std::sync::atomic::{AtomicPtr, Ordering::SeqCst};
use std::sync::OnceLock;
use std::time::Duration;
type Promise = *mut c_void;
const EM_PROMISE_FULFILL: i32 = 0;
const MIN_EMSCRIPTEN_VERSION: (u32, u32, u32) = (6, 0, 10);
#[repr(C)]
struct UtsName {
sysname: [u8; 65],
nodename: [u8; 65],
release: [u8; 65],
version: [u8; 65],
machine: [u8; 65],
domainname: [u8; 65],
}
extern "C" {
fn emscripten_has_asyncify() -> i32;
fn uname(buf: *mut UtsName) -> i32;
fn emscripten_promise_create() -> Promise;
fn emscripten_promise_destroy(promise: Promise);
fn emscripten_promise_resolve(promise: Promise, result: i32, value: *mut c_void);
fn emscripten_set_timeout(
cb: extern "C" fn(*mut c_void),
msecs: f64,
user_data: *mut c_void,
) -> i32;
fn emscripten_clear_timeout(id: i32);
fn emscripten_set_immediate(cb: extern "C" fn(*mut c_void), user_data: *mut c_void) -> i32;
fn emscripten_clear_immediate(id: i32);
}
#[repr(C)]
struct SettledResult {
result: i32,
value: *mut c_void,
}
extern "C-unwind" {
fn emscripten_promise_await(promise: Promise) -> SettledResult;
}
#[derive(Copy, Clone)]
enum JspiState {
Disabled,
Enabled,
UnsupportedVersion(u32, u32, u32),
}
fn emscripten_version() -> Option<(u32, u32, u32)> {
let mut uts = UtsName {
sysname: [0; 65],
nodename: [0; 65],
release: [0; 65],
version: [0; 65],
machine: [0; 65],
domainname: [0; 65],
};
if unsafe { uname(&mut uts) } != 0 {
return None;
}
let len = uts
.release
.iter()
.position(|&b| b == 0)
.unwrap_or(uts.release.len());
let release = std::str::from_utf8(&uts.release[..len]).ok()?;
let mut parts = release.split('.');
let major = parts.next()?.parse().ok()?;
let minor = parts.next()?.parse().ok()?;
let tiny = parts.next()?.parse().ok()?;
if parts.next().is_some() {
return None;
}
Some((major, minor, tiny))
}
fn detect_jspi() -> JspiState {
if unsafe { emscripten_has_asyncify() } != 2 {
return JspiState::Disabled;
}
match emscripten_version() {
Some((major, minor, tiny)) if (major, minor, tiny) < MIN_EMSCRIPTEN_VERSION => {
JspiState::UnsupportedVersion(major, minor, tiny)
}
_ => JspiState::Enabled,
}
}
pub(crate) fn jspi_enabled() -> bool {
static STATE: OnceLock<JspiState> = OnceLock::new();
match *STATE.get_or_init(detect_jspi) {
JspiState::Disabled => false,
JspiState::Enabled => true,
JspiState::UnsupportedVersion(major, minor, tiny) => panic!(
"cannot use JSPI on wasm32-unknown-emscripten with Emscripten \
{major}.{minor}.{tiny}: Emscripten 6.0.10 or later is required"
),
}
}
#[derive(Debug)]
pub(crate) struct Slot(AtomicPtr<c_void>);
impl Slot {
pub(crate) const fn new() -> Self {
Self(AtomicPtr::new(ptr::null_mut()))
}
}
enum Timer {
Timeout(i32),
Immediate(i32),
}
impl Timer {
fn set(promise: Promise, dur: Duration) -> Self {
unsafe {
if dur.is_zero() {
Timer::Immediate(emscripten_set_immediate(resolve, promise))
} else {
let ms = (dur.as_secs_f64() * 1000.0).min(0x7fff_ffff as f64);
Timer::Timeout(emscripten_set_timeout(resolve, ms, promise))
}
}
}
}
impl Drop for Timer {
fn drop(&mut self) {
unsafe {
match *self {
Timer::Timeout(id) => emscripten_clear_timeout(id),
Timer::Immediate(id) => emscripten_clear_immediate(id),
}
}
}
}
extern "C" fn resolve(promise: *mut c_void) {
unsafe { emscripten_promise_resolve(promise, EM_PROMISE_FULFILL, ptr::null_mut()) }
}
struct Park<'a> {
slot: &'a Slot,
promise: Promise,
timer: Option<Timer>,
}
impl Drop for Park<'_> {
fn drop(&mut self) {
self.slot.0.store(ptr::null_mut(), SeqCst);
drop(self.timer.take());
unsafe { emscripten_promise_destroy(self.promise) }
}
}
pub(crate) fn park(slot: &Slot, dur: Option<Duration>) {
#[cfg(feature = "rt")]
let _scheduler = crate::runtime::context::clear_scheduler();
let promise = unsafe { emscripten_promise_create() };
let park = Park {
slot,
promise,
timer: dur.map(|dur| Timer::set(promise, dur)),
};
let prev = slot.0.swap(promise, SeqCst);
debug_assert!(prev.is_null(), "parker already parked");
let _ = unsafe { emscripten_promise_await(park.promise) };
}
pub(crate) fn unpark(slot: &Slot) {
let promise = slot.0.load(SeqCst);
if !promise.is_null() {
unsafe { emscripten_promise_resolve(promise, EM_PROMISE_FULFILL, ptr::null_mut()) }
}
}