An actor framework for Compio.
The interface is centered on a few types:
Actorowns state and lifecycle hooks.Handler<M>adds each message type an actor accepts.Clusterplaces actors on runtimes managed bycompio-dispatcher.Mailbox<A>casts anyMfor whichA: Handler<M>.Broker<M>is a send-only capability derived from an actor reference.ActorHandle<E>observes completion without cancelling the actor when dropped.
Actors, their state, and their futures stay on one worker and do not need to implement Send. Actor factories, messages, references, startup arguments, and errors can cross threads and therefore must be Send. Calling stop lets the current handler finish. The lifecycle order is pre_start, post_start, message handling, pre_stop, then post_stop.
An actor can implement multiple Handler<M: Message>. Different message types share one bounded FIFO mailbox. Messages and their worker-local handler futures are type-erased internally, so each handled message currently requires two small allocations.
use std::{convert::Infallible, io};
use compio_actor::{Actor, ActorExit, Broker, Cluster, Handler, Mailbox};
struct Counter;
#[derive(Debug)]
struct Add(usize);
#[derive(Debug)]
struct Stop;
impl Actor for Counter {
type State = usize;
type Arguments = usize;
type Error = Infallible;
async fn pre_start(
&self,
_myself: &Mailbox<Self>,
initial: usize,
) -> Result<usize, Infallible> {
Ok(initial)
}
}
impl Handler<Add> for Counter {
async fn handle(
&self,
_: &Mailbox<Self>,
Add(value): Add,
state: &mut usize,
) -> Result<(), Infallible> {
*state += value;
Ok(())
}
}
impl Handler<Stop> for Counter {
async fn handle(
&self,
myself: &Mailbox<Self>,
Stop: Stop,
state: &mut usize,
) -> Result<(), Infallible> {
assert_eq!(*state, 5);
myself.stop();
Ok(())
}
}
fn main() -> io::Result<()> {
compio_runtime::Runtime::new()?.block_on(async {
// `Cluster` will spin up a thread pool upon creating
let cluster = Cluster::new()?;
// Spawn an actor in the thread pool. Notice that actors doesn't need to be movable.
// They stay on the thread where they were created. Only messages and args are.
let (counter, handle) = cluster.spawn(|| Counter, 0).await.unwrap();
// Use broker to send a message without knowing what the underlying actor is
let add: Broker<Add> = counter.broker();
add.send(Add(2)).unwrap();
counter.send(Add(3)).unwrap();
counter.send(Stop).unwrap();
// Use handle to retrieve the final result
assert_eq!(handle.await.unwrap(), ActorExit::Stopped);
cluster.join().await
})
}