use super::{complete, completion, receive, succeeded};
use crate::channel::mpsc;
use emscripten_rs_sys as ffi;
use std::{ffi::c_void, time::Duration};
pub async fn sleep(duration: Duration) {
let millis = i32::try_from(duration.as_millis()).expect("sleep duration exceeds i32::MAX ms");
let (arg, receiver) = completion::<()>();
unsafe { ffi::emscripten_async_call(Some(succeeded), arg, millis) };
receive(receiver).await.expect("sleep callback failed");
}
pub async fn yield_now() {
let (arg, receiver) = completion::<()>();
unsafe { ffi::emscripten_set_immediate(Some(succeeded), arg) };
receive(receiver).await.expect("immediate callback failed");
}
pub async fn timeout(duration: Duration) {
let millis = i32::try_from(duration.as_millis()).expect("timeout exceeds i32::MAX ms");
let (arg, receiver) = completion::<()>();
unsafe { ffi::emscripten_set_timeout(Some(succeeded), millis as f64, arg) };
receive(receiver).await.expect("timeout callback failed");
}
pub async fn animation_frame() -> Duration {
unsafe extern "C" fn frame(time: f64, arg: *mut c_void) -> bool {
unsafe { complete(arg, Ok(time)) };
false
}
let (arg, receiver) = completion::<f64>();
unsafe { ffi::emscripten_request_animation_frame(Some(frame), arg) };
let millis = receive(receiver)
.await
.expect("animation frame callback failed");
Duration::from_secs_f64(millis / 1000.0)
}
pub async fn main_loop_blocker(counted: bool) {
let (arg, receiver) = completion::<()>();
unsafe {
if counted {
ffi::_emscripten_push_main_loop_blocker(Some(succeeded), arg, c"Rust task".as_ptr());
} else {
ffi::_emscripten_push_uncounted_main_loop_blocker(
Some(succeeded),
arg,
c"Rust task".as_ptr(),
);
}
}
receive(receiver)
.await
.expect("main-loop blocker callback failed");
}
pub type Ticks = mpsc::Receiver<Duration>;
type TickSender = mpsc::Sender<Duration>;
unsafe extern "C" fn loop_tick(time: f64, arg: *mut c_void) -> bool {
let sender = unsafe { Box::from_raw(arg.cast::<TickSender>()) };
let keep_running = match sender.send(Duration::from_secs_f64(time / 1000.0)) {
Ok(()) => true,
Err(error) => error.is_full(),
};
if keep_running {
let _ = Box::into_raw(sender);
}
keep_running
}
unsafe extern "C" fn immediate_tick(arg: *mut c_void) -> bool {
unsafe { loop_tick(ffi::emscripten_get_now(), arg) }
}
fn tick_channel() -> (*mut c_void, Ticks) {
let (sender, receiver) = mpsc::bounded(2);
(Box::into_raw(Box::new(sender)).cast(), receiver)
}
pub fn timeout_loop(period: Duration) -> Ticks {
let millis = i32::try_from(period.as_millis()).expect("period exceeds i32::MAX ms");
let (arg, ticks) = tick_channel();
unsafe { ffi::emscripten_set_timeout_loop(Some(loop_tick), millis as f64, arg) };
ticks
}
pub fn immediate_loop() -> Ticks {
let (arg, ticks) = tick_channel();
unsafe { ffi::emscripten_set_immediate_loop(Some(immediate_tick), arg) };
ticks
}
pub fn animation_frames() -> Ticks {
let (arg, ticks) = tick_channel();
unsafe { ffi::emscripten_request_animation_frame_loop(Some(loop_tick), arg) };
ticks
}
pub fn interval(period: Duration) -> Ticks {
struct Interval {
sender: TickSender,
id: i32,
}
unsafe extern "C" fn tick(arg: *mut c_void) {
let interval_ptr = arg.cast::<Interval>();
let state = unsafe { &*interval_ptr };
let time = Duration::from_secs_f64(unsafe { ffi::emscripten_get_now() } / 1000.0);
match state.sender.send(time) {
Err(error) if !error.is_full() => {
unsafe { ffi::emscripten_clear_interval(state.id) };
let _ = state;
drop(unsafe { Box::from_raw(interval_ptr) });
}
_ => {}
}
}
let millis = i32::try_from(period.as_millis()).expect("period exceeds i32::MAX ms");
let (sender, receiver) = mpsc::bounded(2);
let state = Box::into_raw(Box::new(Interval { sender, id: 0 }));
unsafe {
(*state).id = ffi::emscripten_set_interval(Some(tick), millis as f64, state.cast());
}
receiver
}