Add Actor interface

This is the early idea. The error message is still not great if the
HandledMessages don't fit the Actor. (Aka forgot to impl Handle>

Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
Marcel Müller 2025-11-05 14:21:38 +01:00
parent bf3e65a6e2
commit d38b04396c
3 changed files with 162 additions and 0 deletions

View file

@ -0,0 +1,139 @@
use std::any::Any;
use std::marker::PhantomData;
use std::pin::Pin;
use crate::InternalMessage;
use crate::IsContainedInBundle;
use crate::Message;
use crate::MessageBundle;
pub trait Actor: Any {
type HandledMessages: MessageBundle;
}
pub trait Handle<M: Message> {
fn handle(&mut self, message: M) -> impl Future<Output = anyhow::Result<M::Reply>>;
}
pub trait ActorHandler<MB>: Any {
fn handle_message(
&mut self,
message: InternalMessage,
) -> impl Future<Output = anyhow::Result<InternalMessage>>;
}
macro_rules! impl_actor_handle {
( $($ty:ident),* ) => {
impl<ACTOR, $($ty,)*> ActorHandler<($($ty,)*)> for ACTOR
where
ACTOR: Actor<HandledMessages = ($($ty,)*)>,
$( ACTOR: Handle<$ty>, )*
$( $ty: Message, )*
{
fn handle_message(
&mut self,
msg: InternalMessage,
) -> impl Future<Output = anyhow::Result<InternalMessage>> {
#[allow(unused_variables)]
async {
$(
let msg = match msg.into_inner::<$ty>() {
Ok(msg) => {
return self.handle(msg).await.map(InternalMessage::new);
}
Err(msg) => msg,
};
)*
Err(anyhow::anyhow!("Could not handle message"))
}
}
}
};
}
all_the_tuples!(impl_actor_handle);
pub struct ActorHandle<MB> {
actor: Box<dyn Any>,
#[allow(clippy::type_complexity)]
handle: for<'a> fn(
&'a mut dyn Any,
msg: InternalMessage,
) -> Pin<Box<dyn Future<Output = anyhow::Result<InternalMessage>> + 'a>>,
_pd: PhantomData<fn(MB)>,
}
impl<MB: MessageBundle + 'static> ActorHandle<MB> {
pub fn new<A: ActorHandler<MB>>(actor: A) -> ActorHandle<MB> {
ActorHandle {
actor: Box::new(actor),
handle: |actor, msg| {
let actor: &mut A = actor.downcast_mut().unwrap();
Box::pin(actor.handle_message(msg))
},
_pd: PhantomData,
}
}
pub async fn handle<M: Message + IsContainedInBundle<MB>>(
&mut self,
message: M,
) -> anyhow::Result<M::Reply> {
const {
let true = <M as IsContainedInBundle<MB>>::IS_CONTAINED else {
panic!("Message is not contained in MessageBundle",);
};
}
(self.handle)(self.actor.as_mut(), InternalMessage::new(message))
.await
.map(|msg| match msg.into_inner::<M::Reply>() {
Ok(t) => t,
Err(msg) => panic!("Could not process reply of type: {}", msg.type_name()),
})
}
}
#[cfg(test)]
mod tests {
use macro_rules_attribute::apply;
use smol_macros::test;
use super::*;
struct Foo;
impl Message for Foo {
type Reply = ();
}
struct Zap;
impl Message for Zap {
type Reply = ();
}
struct FActor;
impl Actor for FActor {
type HandledMessages = (Foo,);
}
impl Handle<Foo> for FActor {
async fn handle(&mut self, _message: Foo) -> anyhow::Result<<Foo as Message>::Reply> {
todo!()
}
}
#[apply(test!)]
async fn test_name() {
let mut actor = ActorHandle::new(FActor);
actor.handle(Zap).await.unwrap();
}
}

View file

@ -5,6 +5,7 @@
mod macros;
pub mod address;
pub mod message;
pub mod actor;
use std::any::TypeId;

View file

@ -1,3 +1,25 @@
#[rustfmt::skip]
macro_rules! all_the_tuples {
($name:ident) => {
$name!(M1);
$name!(M1, M2);
$name!(M1, M2, M3);
$name!(M1, M2, M3, M4);
$name!(M1, M2, M3, M4, M5);
$name!(M1, M2, M3, M4, M5, M6);
$name!(M1, M2, M3, M4, M5, M6, M7);
$name!(M1, M2, M3, M4, M5, M6, M7, M8);
$name!(M1, M2, M3, M4, M5, M6, M7, M8, M9);
$name!(M1, M2, M3, M4, M5, M6, M7, M8, M9, M10);
$name!(M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11);
$name!(M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12);
$name!(M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13);
$name!(M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14);
$name!(M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15);
$name!(M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15, M16);
};
}
#[rustfmt::skip]
macro_rules! all_the_tuples_special_first {
($name:ident) => {