agner-actors 0.4.1

An actor toolkit inspired by Erlang/OTP (actors)
Documentation
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::error::Error as StdError;
use std::time::Instant;

use tokio::sync::{mpsc, oneshot};

use crate::actor_id::ActorID;
use crate::actor_runner::sys_msg::SysMsg;
use crate::exit::Exit;

use super::actor_id_pool::ActorIDLease;

pub type Data = Box<dyn Any + Send + Sync + 'static>;

#[derive(Debug)]
pub struct ActorEntry(Entry);

#[derive(Debug)]
enum Entry {
    Vacant(Vacant),
    Occupied(Occupied),
}

#[derive(Debug)]
struct Occupied {
    actor_id_lease: ActorIDLease,
    messages_tx: Box<dyn Any + Send + Sync + 'static>,
    sys_msg_tx: mpsc::UnboundedSender<SysMsg>,
    watches: Vec<oneshot::Sender<Exit>>,
    data: HashMap<TypeId, Data>,
}

type Vacant = Option<Terminated>;

#[derive(Debug)]
struct Terminated {
    actor_id: ActorID,
    exit: Exit,
    #[allow(unused)]
    at: Instant,
}

impl Default for ActorEntry {
    fn default() -> Self {
        Self(Entry::Vacant(Default::default()))
    }
}

impl ActorEntry {
    pub fn running_actor_id(&self) -> Option<ActorID> {
        self.occupied().map(|oe| *oe.actor_id_lease)
    }

    pub fn running_or_terminated_actor_id(&self) -> Option<ActorID> {
        match &self.0 {
            Entry::Occupied(occupied) => Some(*occupied.actor_id_lease),
            Entry::Vacant(Some(terminated)) => Some(terminated.actor_id),
            Entry::Vacant(None) => None,
        }
    }

    pub fn messages_tx<M>(&self) -> Option<&mpsc::UnboundedSender<M>>
    where
        M: Send + 'static,
    {
        self.occupied().and_then(|oe| oe.messages_tx.downcast_ref())
    }
    pub fn sys_msg_tx(&self) -> Option<&mpsc::UnboundedSender<SysMsg>> {
        self.occupied().map(|oe| &oe.sys_msg_tx)
    }
}

impl ActorEntry {
    pub fn new<Message>(
        actor_id_lease: ActorIDLease,
        messages_tx: mpsc::UnboundedSender<Message>,
        sys_msg_tx: mpsc::UnboundedSender<SysMsg>,
    ) -> Self
    where
        Message: Send + 'static,
    {
        let occupied = Occupied {
            actor_id_lease,
            messages_tx: Box::new(messages_tx),
            sys_msg_tx,
            watches: Default::default(),
            data: Default::default(),
        };
        let entry = Entry::Occupied(occupied);
        Self(entry)
    }

    pub fn put_data<D: Any + Send + Sync + 'static>(&mut self, data: D) {
        if let Entry::Occupied(occupied) = &mut self.0 {
            let type_id = data.type_id();
            occupied.data.insert(type_id, Box::new(data));
        }
    }

    pub fn get_data<D: Any>(&self) -> Option<&D> {
        if let Entry::Occupied(occupied) = &self.0 {
            let type_id = TypeId::of::<D>();
            occupied.data.get(&type_id).and_then(|boxed| boxed.as_ref().downcast_ref())
        } else {
            None
        }
    }

    pub fn take_data<D: Any>(&mut self) -> Option<D> {
        if let Entry::Occupied(occupied) = &mut self.0 {
            let type_id = TypeId::of::<D>();
            occupied
                .data
                .remove(&type_id)
                .and_then(|boxed| boxed.downcast().map(|b| *b).ok())
        } else {
            None
        }
    }

    pub fn add_watch(&mut self, watch: oneshot::Sender<Exit>) {
        fn replace_or_append(
            actor_id: ActorID,
            watches: &mut Vec<oneshot::Sender<Exit>>,
            watch: oneshot::Sender<Exit>,
        ) {
            let maybe_replace = watches.iter_mut().enumerate().find(|(_idx, tx)| tx.is_closed());
            if let Some((idx, to_replace)) = maybe_replace {
                tracing::trace!("[{}] adding 'wait' [replace #{}]", actor_id, idx);
                *to_replace = watch;
            } else {
                tracing::trace!("[{}] adding 'wait' [append #{}]", actor_id, watches.len());
                watches.push(watch);
            }
        }
        match &mut self.0 {
            Entry::Vacant(None) => {
                tracing::error!("How did the control flow get here?");
                panic!("There is no way the control gets here before the entry is initialized");
            },
            Entry::Vacant(Some(Terminated { actor_id, exit, .. })) => {
                tracing::trace!(
                    "[{}|TERMINATED] replying immediately upon attempt to install a watch",
                    actor_id
                );
                let _ = watch.send(exit.to_owned());
            },
            Entry::Occupied(occupied) => {
                replace_or_append(*occupied.actor_id_lease, &mut occupied.watches, watch);
            },
        }
    }

    pub fn terminate(
        &mut self,
        actor_id: ActorID,
        exit_reason: Exit,
    ) -> Result<(), Box<dyn StdError + Send + Sync + 'static>> {
        if self.running_actor_id() != Some(actor_id) {
            Err("this entry does not have a running entry with the specified actor_id")?
        }

        let to_terminate = std::mem::replace(
            &mut self.0,
            Entry::Vacant(Some(Terminated {
                actor_id,
                exit: exit_reason.to_owned(),
                at: Instant::now(),
            })),
        );

        if let Entry::Occupied(Occupied { actor_id_lease, mut watches, .. }) = to_terminate {
            watches.drain(..).enumerate().for_each(|(idx, tx)| {
                tracing::trace!("[{}] notifying waiting chan #{}", *actor_id_lease, idx);
                let _ = tx.send(exit_reason.to_owned());
            });
        }
        Ok(())
    }
}

impl ActorEntry {
    fn occupied(&self) -> Option<&Occupied> {
        if let Entry::Occupied(occupied) = &self.0 {
            Some(occupied)
        } else {
            None
        }
    }
}