# 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. [generated code]: https://github.com/frabul/abcgen/blob/main/examples/hello_world_expanded.rs [examples directory]: https://github.com/frabul/abcgen/blob/main/examples/ ```rust // below there is the attribute that is actually emitting the code by calling a procedural macro #[abcgen::actor_module(channels_size = 123, events_chan_size = 15)] #[allow(unused)] mod hello_world_actor { use abcgen::*; // Events are optional, if they are not found the start method will not receive the event_sender argument #[events] // this attribute is used to mark the enum defining the events that can be signaled by the actor #[derive(Debug, Clone)] pub enum HelloWorldActorEvent { SomeoneAskedMyName(String), Message(String), } /// Some errors that we want to return in our actor's handler methods #[derive(thiserror::Error, Debug)] pub enum HelloWorldError { #[error("Actor already stopped")] AlreadyStopped, #[error("HelloWorldErrors 1")] Error1, #[error("HelloWorldErrors 2")] Error2, } /// It is useful to implement the From for the errors /// that can be returned by the actor's handler methods so that /// the proxy can return a flat Result instead /// of a nested Result, AbcgenError> impl From 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] // this attribute is used to mark the struct defining the actor pub struct HelloWorldActor { pub event_sender: Option, } impl HelloWorldActor { /// The following function *must* be implemented by the user and is called by the run function async fn start( &mut self, task_sender: TaskSender, // this can be used to send a function to be invoked in the actor's loop event_sender: EventSender, // this argument should be removed if there are no events ) { self.event_sender = Some(event_sender); println!("Hello, World!"); tokio::spawn(async move { tokio::time::sleep(std::time::Duration::from_secs(1)).await; // the following call will send the function Self::still_here to be invoked by the actor's loop send_task!(task_sender(this) => { // ⚠️ Do not put any sleep in the closure or Self::still_here, it will block the actor's loop this.still_here().await; }); }); } /// The following function must be implemented by the user and is called befor termination async fn shutdown(&mut self) { println!("Goodbye, World!"); } /// following function is meant to handle a message because it is marked with `#[message_handler]` /// abcgen generates a message for it /// that should have the following signature: /// ``` /// HelloWorldActorMessage::TellMeYourName({caller: String, respond_to: tokio::sync::oneshot::Sender>}) /// ``` /// A specular function is generated on the proxy that can be called to send the message and receive the response. /// In this case the fuction of the proxy will return the same `Result` because there is /// a conversion `From` for HelloWorldError otherwise it would return a nested `Result, AbcgenError>` /// #[message_handler] async fn tell_me_your_name(&mut self, caller: String) -> Result { // ⚠️ Do not put any sleep in this function, it will block the actor's task self.event_sender .as_ref() .unwrap() .send(HelloWorldActorEvent::SomeoneAskedMyName(caller.clone())) .unwrap(); println!("Hello {}, I am HelloWorldActor", caller); Ok("HelloWorldActor".to_string()) } /// The following function is meant to handle a message because it is marked with `#[message_handler]` /// In this case the fuction generated on the proxy will return a nested `Result, AbcgenError>` #[message_handler] async fn do_that(&mut self) -> Result<(), SomeOtherError> { println!("do_that called"); Ok(()) } /// The following function can be enqueued as a task to executed in the actor's task fn still_here(&mut self) -> PinnedFuture<()> { // ⚠️ Do not put any sleep in this function, it will block the actor's task Box::pin(async { self.event_sender .as_ref() .unwrap() .send(HelloWorldActorEvent::Message( "Hello world again, I'm still here.".to_string(), )) .unwrap(); // don't do this tokio::time::sleep(std::time::Duration::from_secs(1)).await; }) } } } use abcgen::AbcgenError; // ---- main.rs ---- use hello_world_actor::{HelloWorldActor, HelloWorldActorEvent, SomeOtherError}; #[tokio::main] async fn main() { let actor = HelloWorldActor { event_sender: None }; // the following call will spawn a tokio task that will handle the messages received by the actor // it consumes the actor and returns a proxy that can be used to send and receive messages let proxy = actor.run(); // handle events sent by the actor 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); } } } }); // in the case of SomeOtherError there is no conversion to AbcgenError so the method returns // a nested Result let do_that_res: Result, AbcgenError> = proxy.do_that().await; // in the case of HelloWorldError there is a conversion to AbcgenError so the method returns // a flat result because the eventual AbcgenError is converted to HelloWorldError // thanks to the From implementation 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), } // -- wait for events tokio::time::sleep(std::time::Duration::from_secs(2)).await; } ```