#[derive(Handler)]
{
// Attributes available to this derive:
#[reply]
}
Expand description
Derive a custom handler trait for given an enum containing tuple
structs. The handler trait defines a method corresponding
to each of the enum’s variants, and a handle
function
that dispatches messages to the correct method. The macro
supports two messaging patterns: “call” and “oneway”. A call is a
request-response message; a [hyperactor::mailbox::OncePortRef
] or
[hyperactor::mailbox::OncePortHandle
] in the last position is used
to send the return value.
The macro also derives a client trait that can be automatically implemented
by specifying HandleClient
for ActorHandle<Actor>
and RefClient
for ActorRef<Actor>
accordingly. We require two implementations because
not ActorRef
s require that its message type is serializable.
The associated [hyperactor_macros::handler
] macro can be used to add
a dispatching handler directly to an [hyperactor::Actor
].
§Example
The following example creates a “shopping list” actor responsible for maintaining a shopping list.
use std::collections::HashSet;
use std::time::Duration;
use async_trait::async_trait;
use hyperactor::Actor;
use hyperactor::HandleClient;
use hyperactor::Handler;
use hyperactor::Instance;
use hyperactor::Named;
use hyperactor::OncePortRef;
use hyperactor::RefClient;
use hyperactor::proc::Proc;
use serde::Deserialize;
use serde::Serialize;
#[derive(Handler, HandleClient, RefClient, Debug, Serialize, Deserialize, Named)]
enum ShoppingList {
// Oneway messages dispatch messages asynchronously, with no reply.
Add(String),
Remove(String),
// Call messages dispatch a request, expecting a reply to the
// provided port, which must be in the last position.
Exists(String, #[reply] OncePortRef<bool>),
List(#[reply] OncePortRef<Vec<String>>),
}
// Define an actor.
#[derive(Debug)]
#[hyperactor::export(
spawn = true,
handlers = [
ShoppingList,
],
)]
struct ShoppingListActor(HashSet<String>);
#[async_trait]
impl Actor for ShoppingListActor {
type Params = ();
async fn new(_params: ()) -> Result<Self, anyhow::Error> {
Ok(Self(HashSet::new()))
}
}
// ShoppingListHandler is the trait generated by derive(Handler) above.
// We implement the trait here for the actor, defining a handler for
// each ShoppingList message.
//
// The `forward` attribute installs a handler that forwards messages
// to the `ShoppingListHandler` implementation directly. This can also
// be done manually:
//
// ```ignore
//<ShoppingListActor as ShoppingListHandler>
// ::handle(self, comm, message).await
// ```
#[async_trait]
#[hyperactor::forward(ShoppingList)]
impl ShoppingListHandler for ShoppingListActor {
async fn add(&mut self, _cx: &Context<Self>, item: String) -> Result<(), anyhow::Error> {
eprintln!("insert {}", item);
self.0.insert(item);
Ok(())
}
async fn remove(&mut self, _cx: &Context<Self>, item: String) -> Result<(), anyhow::Error> {
eprintln!("remove {}", item);
self.0.remove(&item);
Ok(())
}
async fn exists(
&mut self,
_cx: &Context<Self>,
item: String,
) -> Result<bool, anyhow::Error> {
Ok(self.0.contains(&item))
}
async fn list(&mut self, _cx: &Context<Self>) -> Result<Vec<String>, anyhow::Error> {
Ok(self.0.iter().cloned().collect())
}
}
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let mut proc = Proc::local();
// Spawn our actor, and get a handle for rank 0.
let shopping_list_actor: hyperactor::ActorHandle<ShoppingListActor> =
proc.spawn("shopping", ()).await?;
// We join the system, so that we can send messages to actors.
let client = proc.attach("client").unwrap();
// todo: consider making this a macro to remove the magic names
// Derive(Handler) generates client methods, which call the
// remote handler provided an instance (send + open capability),
// the destination actor, and the method arguments.
shopping_list_actor.add(&client, "milk".into()).await?;
shopping_list_actor.add(&client, "eggs".into()).await?;
println!(
"got milk? {}",
shopping_list_actor.exists(&client, "milk".into()).await?
);
println!(
"got yoghurt? {}",
shopping_list_actor
.exists(&client, "yoghurt".into())
.await?
);
shopping_list_actor.remove(&client, "milk".into()).await?;
println!(
"got milk now? {}",
shopping_list_actor.exists(&client, "milk".into()).await?
);
println!(
"shopping list: {:?}",
shopping_list_actor.list(&client).await?
);
let _ = proc.destroy_and_wait(Duration::from_secs(1), None).await?;
Ok(())
}