Crate abcgen

Source
Expand description

§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.

§Basic example

The following example is minimale and does not shocase all the features of the library. Check the README for more details.

#[abcgen::actor_module]
#[allow(unused)]
mod actor {
    use abcgen::{actor, message_handler, send_task, AbcgenError, PinnedFuture, Task};

    #[actor]
    pub struct MyActor {
        pub some_value: i32,
    }

    impl MyActor {
        pub async fn start(&mut self, task_sender: TaskSender) {
            println!("Starting");
            // here you can spawn some tasks using tokio::spawn
            // or enqueue some tasks into the actor's main loop by sending them to task_sender
            tokio::spawn(async move {
                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
                send_task!( task_sender(this) => { this.example_task().await; } );
            });
        }
        pub async fn shutdown(&mut self) {
            println!("Shutting down");
        }
        #[message_handler]
        async fn get_value(&mut self, name: &'static str) -> Result<i32, &'static str> {
            match name {
                "some_value" => Ok(self.some_value),
                _ => Err("Invalid name"),
            }
        }

        async fn example_task(&mut self) {
            println!("Example task executed");
        }
    }
}

#[tokio::main]
async fn main() {
    let actor: actor::MyActorProxy = actor::MyActor { some_value: 32 }.run();
    let the_value = actor.get_value("some_value").await.unwrap();
    assert!(matches!(the_value, Ok(32)));
    let the_value = actor.get_value("some_other_value").await.unwrap();
    assert!(matches!(the_value, Err("Invalid name")));
}

§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 README file.

Macros§

send_task
A macro to simplify sending tasks to the actor Following below example you need to provide a refence to task_sender and
the name for the reference to the actor (this in the example). The macro will create a closure that returx a PinnedFuture expressed with |this| Box::pin(async move { … }) and send it trough the task_sender. Example:

Enums§

AbcgenError
Error type for the code generated by the abcgen

Type Aliases§

PinnedFuture
Helper type for async tasks The tasks that are meant to be sent to the actor need to return this type
Task
The type of the tasks that are sent to the actor

Attribute Macros§

actor
This attribute is used to mark the struct or enum that is going to be the actor.
actor_module
The actor_module attribute is used to mark a module that contains the actor definition. It will generate the necessary code to implement the actor pattern, that is:
events
This attribute is used to mark the enum that defines the events that the actor can signal. It can be applied to
message_handler
This attribute is used to mark the methods that are going to handle the messages that the actor can receive.