use super::{complete, completion, receive, succeeded};
use emscripten_functions_sys::{emscripten as ffi, html5};
use futures::channel::oneshot;
use std::{ffi::c_void, time::Duration};
pub async fn sleep(duration: Duration) {
unsafe extern "C" fn complete(arg: *mut c_void) {
let sender = unsafe { Box::from_raw(arg.cast::<oneshot::Sender<()>>()) };
let _ = sender.send(());
}
let millis = i32::try_from(duration.as_millis()).expect("sleep duration exceeds i32::MAX ms");
let (sender, receiver) = oneshot::channel::<()>();
let arg = Box::into_raw(Box::new(sender)).cast::<c_void>();
unsafe { ffi::emscripten_async_call(Some(complete), arg, millis) };
receiver.await.expect("sleep callback dropped its sender");
}
pub async fn yield_now() {
let (arg, receiver) = completion::<()>();
unsafe { html5::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 { html5::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 { html5::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 struct Ticks {
receiver: futures::channel::mpsc::Receiver<f64>,
interval: Option<i32>,
_state: Option<std::rc::Rc<std::cell::RefCell<futures::channel::mpsc::Sender<f64>>>>,
}
impl futures::Stream for Ticks {
type Item = Duration;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Duration>> {
std::pin::Pin::new(&mut self.receiver)
.poll_next(cx)
.map(|tick| tick.map(|millis| Duration::from_secs_f64(millis / 1000.0)))
}
}
impl Drop for Ticks {
fn drop(&mut self) {
if let Some(id) = self.interval {
unsafe { html5::emscripten_clear_interval(id) };
}
}
}
type TickSender = futures::channel::mpsc::Sender<f64>;
unsafe extern "C" fn loop_tick(time: f64, arg: *mut c_void) -> bool {
let mut sender = unsafe { Box::from_raw(arg.cast::<TickSender>()) };
let keep_running = match sender.try_send(time) {
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) = futures::channel::mpsc::channel(1);
(
Box::into_raw(Box::new(sender)).cast(),
Ticks {
receiver,
interval: None,
_state: None,
},
)
}
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 { html5::emscripten_set_timeout_loop(Some(loop_tick), millis as f64, arg) };
ticks
}
pub fn immediate_loop() -> Ticks {
let (arg, ticks) = tick_channel();
unsafe { html5::emscripten_set_immediate_loop(Some(immediate_tick), arg) };
ticks
}
pub fn animation_frames() -> Ticks {
let (arg, ticks) = tick_channel();
unsafe { html5::emscripten_request_animation_frame_loop(Some(loop_tick), arg) };
ticks
}
pub fn interval(period: Duration) -> Ticks {
unsafe extern "C" fn tick(arg: *mut c_void) {
let ptr = arg.cast::<std::cell::RefCell<TickSender>>();
unsafe { std::rc::Rc::increment_strong_count(ptr) };
let state = unsafe { std::rc::Rc::from_raw(ptr) };
let _ = state
.borrow_mut()
.try_send(unsafe { ffi::emscripten_get_now() });
}
let millis = i32::try_from(period.as_millis()).expect("period exceeds i32::MAX ms");
let (sender, receiver) = futures::channel::mpsc::channel(1);
let state = std::rc::Rc::new(std::cell::RefCell::new(sender));
let arg = std::rc::Rc::as_ptr(&state).cast_mut().cast();
let id = unsafe { html5::emscripten_set_interval(Some(tick), millis as f64, arg) };
Ticks {
receiver,
interval: Some(id),
_state: Some(state),
}
}