use super::super::FeagiAsyncRuntime;
#[cfg(feature = "async-wasm")]
use core::future::Future;
#[cfg(feature = "async-wasm")]
use core::pin::Pin;
#[cfg(feature = "async-wasm")]
use core::task::{Context, Poll};
#[cfg(feature = "async-wasm")]
use core::time::Duration;
#[cfg(feature = "async-wasm")]
use futures_channel::oneshot;
#[cfg(feature = "async-wasm")]
use futures_util::future::select;
#[cfg(feature = "async-wasm")]
use futures_util::FutureExt;
#[cfg(feature = "async-wasm")]
use js_sys::Promise;
#[cfg(feature = "async-wasm")]
use wasm_bindgen::prelude::*;
#[cfg(feature = "async-wasm")]
use wasm_bindgen_futures::{spawn_local, JsFuture};
#[cfg(feature = "async-wasm")]
pub struct WasmRuntime;
#[cfg(feature = "async-wasm")]
impl WasmRuntime {
pub fn new() -> Self {
Self
}
}
#[cfg(feature = "async-wasm")]
impl Default for WasmRuntime {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "async-wasm")]
pub struct WasmTaskHandle<T>(oneshot::Receiver<T>);
#[cfg(feature = "async-wasm")]
impl<T> Future for WasmTaskHandle<T> {
type Output = T;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
match Pin::new(&mut self.0).poll(cx) {
Poll::Ready(Ok(value)) => Poll::Ready(value),
Poll::Ready(Err(_)) => panic!("Spawned task was cancelled (sender dropped)"),
Poll::Pending => Poll::Pending,
}
}
}
#[cfg(feature = "async-wasm")]
unsafe impl<T: Send> Send for WasmTaskHandle<T> {}
#[cfg(feature = "async-wasm")]
struct WasmDelayFuture {
inner: JsFuture,
}
#[cfg(feature = "async-wasm")]
impl Future for WasmDelayFuture {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match Pin::new(&mut self.inner).poll(cx) {
Poll::Ready(Ok(_)) => Poll::Ready(()),
Poll::Ready(Err(_)) => Poll::Ready(()), Poll::Pending => Poll::Pending,
}
}
}
#[cfg(feature = "async-wasm")]
unsafe impl Send for WasmDelayFuture {}
#[cfg(feature = "async-wasm")]
impl FeagiAsyncRuntime for WasmRuntime {
type TaskHandle<T: Send + 'static> = WasmTaskHandle<T>;
fn spawn<F, T>(&self, fut: F) -> Self::TaskHandle<T>
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
let (tx, rx) = oneshot::channel();
spawn_local(async move {
let result = fut.await;
let _ = tx.send(result);
});
WasmTaskHandle(rx)
}
fn delay(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>> {
let millis = duration.as_millis() as u32;
let promise = Promise::new(&mut |resolve, _reject| {
let window = web_sys::window().expect("window should be available");
let closure = Closure::once_into_js(move || {
resolve.call0(&JsValue::NULL).unwrap();
});
window
.set_timeout_with_callback_and_timeout_and_arguments_0(
closure.as_ref().unchecked_ref(),
millis as i32,
)
.expect("setTimeout should work");
});
let delay_future = WasmDelayFuture {
inner: JsFuture::from(promise),
};
Box::pin(delay_future)
}
fn try_block_on<F, T>(&self, _future: F) -> Result<T, super::super::BlockOnError>
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
Err(super::super::BlockOnError::not_supported(
"WASM does not support blocking operations. All operations must be async.",
))
}
fn with_timeout<F, T>(
&self,
future: F,
timeout: Duration,
) -> Pin<Box<dyn Future<Output = Result<T, super::super::TimeoutError>> + Send + 'static>>
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
let delay = self.delay(timeout);
Box::pin(async move {
match select(future.boxed(), delay).await {
futures_util::future::Either::Left((result, _)) => Ok(result),
futures_util::future::Either::Right((_, _)) => Err(super::super::TimeoutError),
}
})
}
}