use std::future::Future;
use std::pin::Pin;
use rquickjs::{AsyncContext, Ctx};
use crate::error::ScriptError;
pub type VmJob = Box<dyn for<'js> FnOnce(Ctx<'js>) -> Pin<Box<dyn Future<Output = ()> + Send + 'js>> + Send>;
#[derive(Clone)]
pub struct VmHandle {
tx: tokio::sync::mpsc::UnboundedSender<VmJob>,
}
impl VmHandle {
pub async fn with<R, F>(&self, f: F) -> Result<R, ScriptError>
where
R: Send + 'static,
F: for<'js> FnOnce(Ctx<'js>) -> Pin<Box<dyn Future<Output = R> + Send + 'js>> + Send + 'static,
{
let (tx, rx) = tokio::sync::oneshot::channel::<R>();
let job: VmJob = Box::new(move |ctx| {
Box::pin(async move {
let r = f(ctx).await;
let _ = tx.send(r);
})
});
self
.tx
.send(job)
.map_err(|_| ScriptError::internal("session VM loop is gone".to_string()))?;
rx.await
.map_err(|_| ScriptError::internal("session VM loop dropped the job".to_string()))
}
}
pub struct VmShutdown {
_tx: tokio::sync::oneshot::Sender<()>,
}
pub fn spawn_vm_loop(ctx: &AsyncContext) -> (VmHandle, VmShutdown) {
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<VmJob>();
let (shutdown_tx, mut shutdown_rx) = tokio::sync::oneshot::channel::<()>();
let loop_ctx = ctx.clone();
tokio::spawn(async move {
loop_ctx
.async_with(async |ctx| {
loop {
tokio::select! {
job = rx.recv() => match job {
Some(job) => ctx.spawn(job(ctx.clone())),
None => break,
},
_ = &mut shutdown_rx => break,
}
}
})
.await;
});
(VmHandle { tx }, VmShutdown { _tx: shutdown_tx })
}
#[macro_export]
macro_rules! vm_with {
($vm:expr => |$ctx:ident| { $($t:tt)* }) => {
$vm.with(move |$ctx| {
#[allow(unsafe_code)]
unsafe fn uplift<'a, 'b, R>(
f: ::core::pin::Pin<::std::boxed::Box<dyn ::core::future::Future<Output = R> + 'a>>,
) -> ::core::pin::Pin<::std::boxed::Box<dyn ::core::future::Future<Output = R> + 'b + ::core::marker::Send>>
{
unsafe { ::core::mem::transmute(f) }
}
let fut = ::std::boxed::Box::pin(async move { $($t)* });
#[allow(unsafe_code)]
unsafe {
uplift(fut)
}
})
};
}