abcgen - Actor's Boilerplate Code Generator
abcgen helps you to build Actor object by producing all the boilerplate code needed by this patter, meaning that all the code involved in defining/sending/raceiving/unwrapping messages and managing lifetime of the actor is hidden from user. The user should only focus on the logic of the service that the actor is going to provide.
abcgen produces Actor objects that are based on the async
/await
syntax and the tokio library.
The actor objects generated do not require any scheduler o manager to run, they are standalone and can be used in any (tokio) context.
The user should provide:
- a struct or enum definition marked with
actor
attribute
- implement start(...) and shutdown(...) methods for the
actor
- implement, for the
actor
, a set of methods marked with message_handler
attribute; these are going to handle the messages that the actor
can receive.
- optionally, an enum definition marked with
events
attribute to define the events that the actor
can signal
The procedural macro will generate:
- implementation of
run(self)
method for the actor
which will return an ActorProxy
- implementation of message handling logic for the
actor
:
- calling the
start(...)
method before entering the actor
's loop
- calling the
shutdown(&mut self)
method after exiting the actor
's loop
- handling of stop signal
- handling of messages (support replies)
- handling of tasks (functions that can be enqueued to be invoked in the
actor
's loop so the can access &mut Actor
)
- an ActorProxy object that implements all of the methods that were marked with
message_handler
attribute
- a message enum that contains all the messages that the
actor
can receive (which is not meant to be used directly by the user)
More details can be found in the example below.
Example
You can have a look at generated code for this example to see what abcgen produces.
More examples can be found in the examples directory of the repository.
#[abcgen::actor_module(channels_size = 123, events_chan_size = 15)]
#[allow(unused)]
mod hello_world_actor {
use abcgen::*;
#[events] #[derive(Debug, Clone)]
pub enum HelloWorldActorEvent {
SomeoneAskedMyName(String),
Message(String),
}
#[derive(thiserror::Error, Debug)]
pub enum HelloWorldError {
#[error("Actor already stopped")]
AlreadyStopped,
#[error("HelloWorldErrors 1")]
Error1,
#[error("HelloWorldErrors 2")]
Error2,
}
impl From<AbcgenError> for HelloWorldError {
fn from(_: abcgen::AbcgenError) -> Self {
HelloWorldError::AlreadyStopped
}
}
#[derive(thiserror::Error, Debug)]
pub enum SomeOtherError {
#[error("SomeOtherErrors 1")]
Error1,
#[error("SomeOtherErrors 2")]
Error2,
}
#[actor] pub struct HelloWorldActor {
pub event_sender: Option<EventSender>,
}
impl HelloWorldActor {
async fn start(
&mut self,
task_sender: TaskSender, event_sender: EventSender, ) {
self.event_sender = Some(event_sender);
println!("Hello, World!");
tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
send_task!(task_sender(this) => {
this.still_here().await;
});
});
}
async fn shutdown(&mut self) {
println!("Goodbye, World!");
}
#[message_handler]
async fn tell_me_your_name(&mut self, caller: String) -> Result<String, HelloWorldError> {
self.event_sender
.as_ref()
.unwrap()
.send(HelloWorldActorEvent::SomeoneAskedMyName(caller.clone()))
.unwrap();
println!("Hello {}, I am HelloWorldActor", caller);
Ok("HelloWorldActor".to_string())
}
#[message_handler]
async fn do_that(&mut self) -> Result<(), SomeOtherError> {
println!("do_that called");
Ok(())
}
fn still_here(&mut self) -> PinnedFuture<()> {
Box::pin(async {
self.event_sender
.as_ref()
.unwrap()
.send(HelloWorldActorEvent::Message(
"Hello world again, I'm still here.".to_string(),
))
.unwrap();
})
}
}
}
use abcgen::AbcgenError;
use hello_world_actor::{HelloWorldActor, HelloWorldActorEvent, SomeOtherError};
#[tokio::main]
async fn main() {
let actor = HelloWorldActor { event_sender: None };
let proxy = actor.run();
let mut events_rx = proxy.get_events().resubscribe();
tokio::spawn(async move {
while let Ok(event) = events_rx.recv().await {
match event {
HelloWorldActorEvent::SomeoneAskedMyName(name) => {
println!("{} asked my name", name);
}
HelloWorldActorEvent::Message(msg) => {
println!("Actor said: \"{}\"", msg);
}
}
}
});
let do_that_res: Result<Result<(), SomeOtherError>, AbcgenError> = proxy.do_that().await;
let thename = proxy.tell_me_your_name("Alice".to_string()).await.unwrap();
println!("The actor replied with name: \"{}\"", thename);
match do_that_res {
Ok(Ok(_)) => println!("do_that succeeded"),
Ok(Err(e)) => println!("do_that failed: {:?}", e),
Err(e) => println!("do_that failed: {:?}", e),
}
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
}