use core::{
any::{Any, TypeId, type_name},
marker::PhantomData,
};
use std::collections::HashMap;
use kameo::{
Reply,
actor::{ActorRef, WeakActorRef},
error::{BoxSendError, Infallible, SendError},
message::{Context, Message},
reply::{BoxReplySender, DelegatedReply, ForwardedReply, ReplyError},
};
use smol_str::SmolStr;
const CANONICAL: SmolStr = SmolStr::new_static("__canonical__");
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
struct Id {
actor_ty: TypeId,
name: SmolStr,
}
impl Id {
fn new<A>(name: Option<SmolStr>) -> Self
where
A: Any,
{
match name {
Some(name) => Self::named::<A>(name),
None => Self::canonical::<A>(),
}
}
const fn canonical<A>() -> Self
where
A: Any,
{
Self {
actor_ty: TypeId::of::<A>(),
name: CANONICAL,
}
}
const fn named<A>(name: SmolStr) -> Self
where
A: Any,
{
Self {
actor_ty: TypeId::of::<A>(),
name,
}
}
}
pub type ErasedWeakRef = Box<dyn Any + Send>;
#[derive(Default)]
pub struct Registry {
actors: HashMap<Id, ErasedWeakRef>,
pending_lookups: HashMap<Id, Vec<BoxReplySender>>,
}
impl kameo::Actor for Registry {
type Args = ();
type Error = Infallible;
async fn on_start(_args: Self::Args, _actor_ref: ActorRef<Self>) -> Result<Self, Self::Error> {
Ok(Self::default())
}
}
pub struct Register<A>
where
A: kameo::Actor,
{
id: Id,
aref: WeakActorRef<A>,
}
impl<A> Register<A>
where
A: kameo::Actor + Any,
{
pub fn new(name: Option<SmolStr>, aref: &ActorRef<A>) -> Self {
Self {
id: Id::new::<A>(name),
aref: aref.downgrade(),
}
}
}
impl<A> Message<Register<A>> for Registry
where
A: kameo::Actor + Any,
{
type Reply = Option<WeakActorRef<A>>;
async fn handle(
&mut self,
msg: Register<A>,
_ctx: &mut Context<Self, Self::Reply>,
) -> Option<WeakActorRef<A>> {
if let Some(pending) = self.pending_lookups.remove(&msg.id) {
for sender in pending {
drop(sender.send(Ok(Box::new(Some(msg.aref.clone())))));
}
}
let previous = self.actors.insert(msg.id, Box::new(msg.aref))?;
*previous.downcast().unwrap()
}
}
pub struct Unregister<A>(Id, PhantomData<A>);
impl<A> Unregister<A>
where
A: Any,
{
pub fn new(name: Option<SmolStr>) -> Self {
Self(Id::new::<A>(name), PhantomData)
}
}
impl<A> Message<Unregister<A>> for Registry
where
A: kameo::Actor,
{
type Reply = Option<WeakActorRef<A>>;
async fn handle(
&mut self,
msg: Unregister<A>,
_ctx: &mut Context<Self, Self::Reply>,
) -> Option<WeakActorRef<A>> {
let previous = self.actors.remove(&msg.0)?;
*previous.downcast().unwrap()
}
}
pub struct Lookup<A> {
id: Id,
wait: bool,
_phantom: PhantomData<A>,
}
impl<A> Lookup<A>
where
A: Any,
{
pub fn new(name: Option<SmolStr>) -> Self {
Self {
id: Id::new::<A>(name),
wait: false,
_phantom: PhantomData,
}
}
pub const fn wait(mut self, wait: bool) -> Self {
self.wait = wait;
self
}
}
impl<A> Message<Lookup<A>> for Registry
where
A: kameo::Actor,
{
type Reply = DelegatedReply<Option<WeakActorRef<A>>>;
async fn handle(
&mut self,
msg: Lookup<A>,
ctx: &mut Context<Self, Self::Reply>,
) -> Self::Reply {
let (deleg, sender) = ctx.reply_sender();
if let Some(sender) = sender {
let aref = self
.actors
.get(&msg.id)
.map(|x| x.downcast_ref::<WeakActorRef<A>>().unwrap())
.cloned();
match (&aref, msg.wait) {
(Some(_), _) | (_, false) => {
sender.send(aref);
}
(None, true) => {
self.pending_lookups
.entry(msg.id)
.or_default()
.push(sender.boxed());
}
}
};
deleg
}
}
pub struct Forward<A, M> {
id: Id,
message: M,
_phantom: PhantomData<A>,
}
impl<A, M> Forward<A, M>
where
A: Any,
{
pub fn new(name: Option<SmolStr>, m: M) -> Self {
Self {
id: Id::new::<A>(name),
message: m,
_phantom: PhantomData,
}
}
}
pub enum RegistryForward<M, R>
where
M: Send + 'static,
R: Reply,
{
Forwarded(ForwardedReply<M, R>),
ActorDead(M),
NotFound(M),
}
impl<M, R> Reply for RegistryForward<M, R>
where
M: Send + 'static,
R: Reply,
{
type Ok = R::Ok;
type Error = SendError<M, R::Error>;
type Value = Result<Self::Ok, Self::Error>;
fn to_result(self) -> Result<Self::Ok, Self::Error> {
match self {
Self::Forwarded(res) => res.to_result(),
Self::NotFound(m) => Err(SendError::ActorNotRunning(m)),
Self::ActorDead(m) => Err(SendError::ActorNotRunning(m)),
}
.inspect_err(|e| {
tracing::trace!(error = ?e, "forward error");
})
}
fn into_any_err(self) -> Option<Box<dyn ReplyError>> {
match self {
Self::Forwarded(res) => res.into_any_err(),
Self::ActorDead(m) => {
Some(Box::new(SendError::<M, R::Error>::ActorNotRunning(m)) as Box<dyn ReplyError>)
}
Self::NotFound(m) => {
Some(Box::new(SendError::<M, R::Error>::ActorNotRunning(m)) as Box<dyn ReplyError>)
}
}
}
fn into_value(self) -> Self::Value {
self.to_result()
}
fn downcast_ok(ok: Box<dyn Any>) -> Self::Ok {
*ok.downcast().unwrap()
}
fn downcast_err<N: 'static>(err: BoxSendError) -> SendError<N, Self::Error> {
err.try_downcast::<N, R::Error>()
.map(|err| err.map_err(SendError::HandlerError))
.unwrap_or_else(|err| {
err.downcast::<M, SendError<M, R::Error>>().map_msg(|_| {
unreachable!(
"forwarded reply is only an error if it failed to forward the message"
)
})
})
}
}
impl<A, M> Message<Forward<A, M>> for Registry
where
A: Message<M>,
M: Send + 'static,
{
type Reply = RegistryForward<M, A::Reply>;
#[tracing::instrument(skip_all, fields(msgty = type_name::<M>(), actor = type_name::<A>(), name = %msg.id.name))]
async fn handle(
&mut self,
msg: Forward<A, M>,
ctx: &mut Context<Self, Self::Reply>,
) -> Self::Reply {
let Some(aref) = self.actors.get(&msg.id) else {
tracing::trace!("actor not found");
return RegistryForward::NotFound(msg.message);
};
let Some(aref) = aref.downcast_ref::<WeakActorRef<A>>().unwrap().upgrade() else {
tracing::trace!("actor dead");
return RegistryForward::ActorDead(msg.message);
};
let result = ctx.try_forward(&aref, msg.message);
RegistryForward::Forwarded(result)
}
}