pub mod error; /// Public export as the oneshot channel is in the `Actor` trait signature pub use tokio::sync::oneshot; use tokio::{sync::mpsc, task::JoinHandle}; use tokio_util::sync::CancellationToken; use crate::error::{Result, convert_err}; const DEFAULT_CHANNEL_SIZE: usize = 100; type ActorRequest = ( ::Message, oneshot::Sender<::Response>, ); /// Wrapper defining the transmission end of a Request/Response channel with an `Actor` pub struct ActorRequestSender(mpsc::Sender>); impl ActorRequestSender { pub async fn send(&self, request: A::Message) -> Result { let (tx, rx) = oneshot::channel::(); self.0.send((request, tx)).await.map_err(convert_err)?; rx.await.map_err(convert_err) } } impl From>> for ActorRequestSender { fn from(value: mpsc::Sender>) -> Self { Self(value) } } impl Clone for ActorRequestSender { fn clone(&self) -> Self { Self(self.0.clone()) } } /// Combined 'JoinHandle' to await the actor process and 'Sender' for communication pub struct Handle where A: Actor, { cancel_token: CancellationToken, tx: ActorRequestSender, /// the task drops when the `JoinHandle` does, so be careful with the `Handle` _handle: JoinHandle>, // to prevent accidental swaps, strongly type the handle _type: std::marker::PhantomData, } impl Handle { /// Send a message to the spawned `Actor` task and get a response corresponding to the `Actor::Response` type pub async fn send(&self, msg: A::Message) -> Result { self.tx.send(msg).await } /// Get a cloned sender for messaging the `Actor` this handle is for pub fn get_connection(&self) -> ActorRequestSender { self.tx.clone() } } impl Drop for Handle { fn drop(&mut self) { self.cancel_token.cancel(); } } /// Primary trait defining an `Actor` capable of receiving, processing, and transmitting messages pub trait Actor: Send + Sized + 'static { /// The type for messages received by this `Actor` type Message: Send; /// The type for responses given by this actor when called from `Handle::send(..)` type Response: Send; /// Inner method that defines actor behavior fn handle_message(&self, msg: Self::Message, tx: oneshot::Sender); /// Spawns the `Actor` utilizing the given runtime context /// Only `tokio` runtime is accepted for now fn spawn(self, ctx: &tokio::runtime::Runtime) -> Handle { let cancel_token = CancellationToken::new(); let cancel = cancel_token.clone(); let (tx, mut rx) = mpsc::channel::<(Self::Message, oneshot::Sender)>(DEFAULT_CHANNEL_SIZE); let handle = ctx.spawn(async move { let mut res = Ok(()); loop { tokio::select! { _ = cancel.cancelled() => { break; }, msg = rx.recv() => { match msg { Some(m) => { self.handle_message(m.0, m.1); }, None => {res = Err(format!("Sender handle was dropped without calling cancel!").into()); break; }, } } }; } res }); Handle { cancel_token, _handle: handle, tx: tx.into(), _type: std::marker::PhantomData::, } } }