use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll, Waker};
use wasmtime::{AsContextMut, Caller, Linker, bail};
use crate::host::Host;
pub(crate) struct Job(pub(crate) *mut (dyn FnMut(&mut Host) + 'static));
unsafe impl Send for Job {}
pub(crate) enum Cmd {
None,
Resume,
Job(Job),
Exit,
}
pub(crate) struct Exchange {
pub(crate) cmd: Mutex<Cmd>,
pub(crate) deadline: u64,
}
impl Exchange {
pub(crate) fn new(deadline: u64) -> Arc<Exchange> {
Arc::new(Exchange { cmd: Mutex::new(Cmd::None), deadline })
}
pub(crate) fn set(&self, cmd: Cmd) {
*self.cmd.lock().unwrap() = cmd;
}
fn take(&self) -> Cmd {
std::mem::replace(&mut *self.cmd.lock().unwrap(), Cmd::None)
}
}
#[derive(Debug)]
pub(crate) struct Quit;
impl std::fmt::Display for Quit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("the player quit")
}
}
impl std::error::Error for Quit {}
struct YieldOnce(bool);
impl Future for YieldOnce {
type Output = ();
fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<()> {
if self.0 {
Poll::Ready(())
} else {
self.0 = true;
Poll::Pending
}
}
}
pub(crate) fn add_yield_async(linker: &mut Linker<Host>) -> wasmtime::Result<()> {
linker.func_wrap_async("gasm", "yield_frame", |mut caller: Caller<'_, Host>, (): ()| {
Box::new(async move {
let Some(ex) = caller.data().run.clone() else {
bail!("gasm.yield_frame: only inside gasm_run");
};
caller.data_mut().frame_index += 1; loop {
YieldOnce(false).await;
match ex.take() {
Cmd::Resume => break,
Cmd::Job(job) => unsafe { (*job.0)(caller.data_mut()) },
Cmd::Exit => {
if let Some(f) = caller.get_export("gasm_exit").and_then(|e| e.into_func()) {
caller.as_context_mut().set_epoch_deadline(ex.deadline);
if let Err(e) = f.typed::<(), ()>(&caller)?.call_async(&mut caller, ()).await {
if e.downcast_ref::<crate::wasi::Exit>().is_none() {
eprintln!("[gasm] gasm_exit trapped: {e:?}");
}
}
}
return Err(wasmtime::Error::new(Quit));
}
Cmd::None => {}
}
}
caller.as_context_mut().set_epoch_deadline(ex.deadline);
Ok(())
})
})?;
Ok(())
}
pub(crate) fn add_yield_sync(linker: &mut Linker<Host>) -> wasmtime::Result<()> {
linker.func_wrap("gasm", "yield_frame", || -> wasmtime::Result<()> { bail!("gasm.yield_frame: only inside gasm_run") })?;
Ok(())
}
pub(crate) fn poll_once<F: Future + ?Sized>(f: Pin<&mut F>) -> Poll<F::Output> {
f.poll(&mut Context::from_waker(Waker::noop()))
}
pub(crate) fn block_on<F: Future>(f: F) -> wasmtime::Result<F::Output> {
let mut f = std::pin::pin!(f);
match poll_once(f.as_mut()) {
Poll::Ready(v) => Ok(v),
Poll::Pending => bail!("gasm.yield_frame: only inside gasm_run"),
}
}