odin_actor
The odin_actor
crate provides an implementation of a typed actor model
that serves as the common basis for ODIN applications.
Actors are objects that execute concurrently and only communicate through asynchronous Messages. Actors do not share their internal State and are only represented to the outside by ActorHandles. The only operation supported by ActorHandles is to send messages to the actor, which are then queued in an (actor internal) Mailbox and processed by the actor in the order in which they were received. In reaction to received messages actors can send messages or mutate their internal state:
╭──────╮
─────▶︎│Handle│─────x:X──╮ Message
┌─┴──────┴──────────│───┐
│ Actor State ┌─▼─┐ │
│ ▲ ├─:─┤ MailBox
│ │ └───┘ │
│ ▼ │ │
│ receive(m) ◀︎────╯ │
│ match m │
│ X => process_x │
│ ... ───────────▶︎ send messages to other actors
└───────────────────────┘
From a Rust perspective this is a library that implements actors as async tasks that process input received through actor-owned channels and encapsulate actor specific state that is not visible to the outside. It is an architectural abstraction layer on top of async runtimes (such as tokio).
In odin_actor
we map the message interface of an actor to an enum
containing variants for all message types
understood by this actor (variants can be anything that satisfies Rust's Send
trait). The actor state is a user
defined struct
containint the data that is owned by this actor. Actor behavior defined as a trait
impl that
consists of a single receive
function that matches the variants of the actor message enum to user defined expressions.
Please refer to the respective chapter in the odin_book
for more details.
The odin_actor
crate mostly provides a set of macros that implement a DSL for defining and instantiating these
actor components, namely
- [
define_actor_msg_set
] to define an enum for all messages understood by an actor - [
impl_actor
] to define the actor as a 3-tuple of actor state, actor message set and areceive
function that provides the (possibly state dependent) behavior for each input message (such as sending messages to other actors) - [
spawn_actor
] to instantiate actors and start their message receiver tasks
Here is the "hello world" example of odin_actor
, consisting of a single Greeter actor:
use tokio; use odin_actor::prelude::*; // define actor message set ① #[derive(Debug)] pub struct Greet(&'static str); define_actor_msg_set! { pub GreeterMsg = Greet } // define actor state ② pub struct Greeter { name: &'static str } // define the actor tuple (incl. behavior) ③ impl_actor! { match msg for Actor<Greeter,GreeterMsg> as Greet => term! { println!("{} sends greetings to {}", self.name, msg.0); } } // instantiate and run the actor system ④ #[tokio::main] async fn main() ->Result<()> { let mut actor_system = ActorSystem::new("greeter_app"); let actor_handle = spawn_actor!( actor_system, "greeter", Greeter{name: "me"})?; actor_handle.send_msg( Greet("world")).await?; actor_system.process_requests().await?; Ok(()) }
This breaks down into the following four parts: