use std::{
any::Any,
future::Future,
sync::{Arc, Mutex, OnceLock, Weak},
};
use thiserror::Error;
pub trait Process: Sized + Send + 'static {
type Message: Send + 'static;
type Output: std::fmt::Debug + Send + Sync + 'static;
const MAILBOX_CAP: usize = 1;
fn run(&mut self, mailbox: &mut Mailbox<Self>) -> impl Future<Output = Self::Output> + Send;
fn spawn(mut self, processor: impl Processor) -> Handle<Self> {
let (send, recv) = tachyonix::channel(Self::MAILBOX_CAP);
let output = Arc::new(OnceLock::new());
let death_event = Arc::new(async_event::Event::new());
let handle = Handle {
send,
output: output.clone(),
death_event: death_event.clone(),
_to_drop: Arc::new(Mutex::new(Box::new(0))),
};
let mut mailbox = Mailbox {
recv,
handle: handle.downgrade(),
};
let task = processor.spawn_future(async move {
let out = self.run(&mut mailbox).await;
output.set(out).unwrap();
death_event.notify_all();
drop(mailbox); });
*handle._to_drop.lock().unwrap() = Box::new(task);
handle
}
#[cfg(feature = "smolscale")]
fn spawn_smolscale(self) -> Handle<Self> {
self.spawn(SscaleProcessor)
}
}
pub struct Mailbox<P: Process> {
recv: tachyonix::Receiver<P::Message>,
handle: WeakHandle<P>,
}
impl<P: Process> Mailbox<P> {
pub async fn recv(&mut self) -> P::Message {
match self.recv.recv().await {
Ok(val) => val,
Err(_) => futures_util::future::pending().await,
}
}
pub fn handle(&self) -> WeakHandle<P> {
self.handle.clone()
}
}
pub struct Handle<P: Process> {
send: tachyonix::Sender<P::Message>,
output: Arc<OnceLock<P::Output>>,
death_event: Arc<async_event::Event>,
_to_drop: Arc<Mutex<Box<dyn Any + Send + Sync>>>,
}
impl<P: Process> Clone for Handle<P> {
fn clone(&self) -> Self {
Self {
send: self.send.clone(),
output: self.output.clone(),
death_event: self.death_event.clone(),
_to_drop: self._to_drop.clone(),
}
}
}
impl<P: Process> Handle<P> {
pub async fn send(&self, msg: P::Message) -> Result<(), SendError> {
match self.send.send(msg).await {
Ok(_) => Ok(()),
Err(_) => Err(SendError::ProcessStopped),
}
}
pub fn send_or_drop(&self, msg: P::Message) -> Result<(), SendError> {
match self.send.try_send(msg) {
Err(tachyonix::TrySendError::Closed(_)) => Err(SendError::ProcessStopped),
_ => Ok(()),
}
}
pub fn output(&self) -> Option<&P::Output> {
self.output.get()
}
pub async fn wait(&self) {
self.death_event
.wait_until(|| self.output.get().map(|_| ()))
.await;
}
pub fn downgrade(&self) -> WeakHandle<P> {
WeakHandle {
send: self.send.clone(),
output: self.output.clone(),
death_event: self.death_event.clone(),
_to_drop: Arc::downgrade(&self._to_drop),
}
}
}
pub struct WeakHandle<P: Process> {
send: tachyonix::Sender<P::Message>,
output: Arc<OnceLock<P::Output>>,
death_event: Arc<async_event::Event>,
_to_drop: Weak<Mutex<Box<dyn Any + Send + Sync>>>,
}
impl<P: Process> Clone for WeakHandle<P> {
fn clone(&self) -> Self {
Self {
send: self.send.clone(),
output: self.output.clone(),
death_event: self.death_event.clone(),
_to_drop: self._to_drop.clone(),
}
}
}
impl<P: Process> WeakHandle<P> {
pub async fn send(&self, msg: P::Message) -> Result<(), SendError> {
match self.send.send(msg).await {
Ok(_) => Ok(()),
Err(_) => Err(SendError::ProcessStopped),
}
}
pub fn send_or_drop(&self, msg: P::Message) -> Result<(), SendError> {
match self.send.try_send(msg) {
Err(tachyonix::TrySendError::Closed(_)) => Err(SendError::ProcessStopped),
_ => Ok(()),
}
}
pub fn output(&self) -> Option<&P::Output> {
self.output.get()
}
pub fn upgrade(&self) -> Option<Handle<P>> {
Some(Handle {
send: self.send.clone(),
output: self.output.clone(),
death_event: self.death_event.clone(),
_to_drop: self._to_drop.upgrade()?,
})
}
}
impl<P, T, U> Handle<P>
where
P: Process<Message = Request<T, U>>,
{
pub async fn request(&self, req: T) -> Result<U, RequestError> {
let (respond, recv_response) = oneshot::channel();
let req = Request {
inner: req,
respond,
};
self.send(req).await.map_err(RequestError::SendFailed)?;
match recv_response.await {
Ok(val) => Ok(val),
Err(_) => Err(RequestError::RequestRefused),
}
}
}
impl<P, T, U> WeakHandle<P>
where
P: Process<Message = Request<T, U>>,
{
pub async fn request(&self, req: T) -> Result<U, RequestError> {
let (respond, recv_response) = oneshot::channel();
let req = Request {
inner: req,
respond,
};
self.send(req).await.map_err(RequestError::SendFailed)?;
match recv_response.await {
Ok(val) => Ok(val),
Err(_) => Err(RequestError::RequestRefused),
}
}
}
pub struct Request<T, U> {
inner: T,
respond: oneshot::Sender<U>,
}
impl<T, U> Request<T, U> {
pub fn take(self) -> (T, impl FnOnce(U)) {
(self.inner, move |u| {
let _ = self.respond.send(u);
})
}
}
#[derive(Error, Debug, Clone)]
pub enum RequestError {
#[error("could not send to process: {0:?}")]
SendFailed(SendError),
#[error("process refused to respond")]
RequestRefused,
}
#[derive(Error, Debug, Clone)]
pub enum SendError {
#[error("process stopped")]
ProcessStopped,
#[error("process is in the process of stopping")]
ProcessStopping,
}
pub trait Processor {
fn spawn_future(
&self,
fut: impl Future<Output = ()> + Send + 'static,
) -> impl Any + Send + Sync;
}
impl<T: Processor> Processor for &T {
fn spawn_future(
&self,
fut: impl Future<Output = ()> + Send + 'static,
) -> impl Any + Send + Sync {
(*self).spawn_future(fut)
}
}
#[cfg(feature = "smolscale")]
pub struct SscaleProcessor;
#[cfg(feature = "smolscale")]
impl Processor for SscaleProcessor {
fn spawn_future(
&self,
fut: impl Future<Output = ()> + Send + 'static,
) -> impl Any + Send + Sync {
smolscale::spawn(fut)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
struct TestProcess;
impl Process for TestProcess {
type Message = String;
type Output = i32;
async fn run(&mut self, mailbox: &mut Mailbox<Self>) -> Self::Output {
let msg = mailbox.recv().await;
assert_eq!(msg, "Hello");
42
}
}
#[test]
#[cfg(feature = "smolscale")]
fn test_process_spawn_and_message() {
smolscale::block_on(async {
let handle = TestProcess.spawn(SscaleProcessor);
handle.send("Hello".to_string()).await.unwrap();
std::thread::sleep(Duration::from_millis(100));
assert_eq!(handle.output(), Some(&42));
});
}
}