aika 0.2.1

Multi-agent coordination framework in Rust with single and multi-threaded execution engines.
Documentation
//! Agent traits and execution contexts for both single-threaded and multi-threaded simulations.
//! Provides `Agent` trait for single-threaded clusters and `ThreadedAgent` for multi-threaded planets,
//! along with their respective context structures that manage state and inter-agent communication.
use bytemuck::{Pod, Zeroable};
use mesocarp::logging::journal::Journal;

use crate::{
    env::Environment,
    objects::{AntiMsg, Msg, SchedulingTask, Transfer},
    AikaError,
};

#[derive(Debug)]
/// Shared context local `ThreadedAgents` mutate within a `Planet` thread
pub struct Context<MessageType: Pod + Zeroable + Clone> {
    /// state of each `ThreadedAgent` on the `Planet`
    pub env: Box<dyn Environment>,
    /// current time
    pub time: u64,
    /// termination time of the cluster.
    pub terminal: u64,
    /// cluster ID in the inter-cluster messaging system.
    pub cluster_id: usize,
    /// connection indicator
    pub connected: bool,
    /// all anti-messages generated by this `Planet`
    pub anti_msgs: Journal,
    /// sends for this time stamp.
    pub(crate) outbox: Vec<Transfer<MessageType>>,
    pub(crate) counter: u16,
}

impl<MessageType: Pod + Zeroable + Clone> Context<MessageType> {
    /// Spawn a new context environment for a `Planet`.
    pub fn new(
        env: impl Environment + 'static,
        connected: bool,
        cluster_id: usize,
        terminal: u64,
    ) -> Self {
        let env = Box::new(env);
        Self {
            env,
            time: 0,
            cluster_id,
            connected,
            anti_msgs: Journal::init(8 * 1024),
            outbox: Vec::new(),
            terminal,
            counter: 0,
        }
    }

    /// Send a `Msg` to another `Planet`
    pub fn send_mail(
        &mut self,
        mut msg: Msg<MessageType>,
        to_cluster: usize,
    ) -> Result<(), AikaError> {
        if msg.recv > self.terminal {
            return Ok(());
        }
        if msg.recv < self.time {
            return Err(AikaError::TimeTravel);
        }

        let to_cluster = if self.connected {
            to_cluster
        } else {
            self.cluster_id
        };
        msg.to.0 = to_cluster;
        msg.from.0 = self.cluster_id;

        if self.connected {
            let anti = AntiMsg::new(msg.sent, msg.recv, msg.from, msg.to);
            self.anti_msgs.write(anti, self.time, None);
        }

        let outgoing = Transfer::Msg(msg);
        if self.counter < 1000 {
            // println!("self.connected: {:?}, out cluster ID: {:?}", self.connected, if self.connected {
            //     to_cluster
            // } else {
            //     self.cluster_id
            // });
            // println!("sending mail ({:?} {:?}, {:?} {:?})", outgoing.from(), outgoing.from_actor(), outgoing.to(), outgoing.to_actor());
            self.counter += 1;
        }
        self.outbox.push(outgoing);
        Ok(())
    }
}

/// An `Actor` is an independent logical process that belongs to a `Planet` and can schedule events.
pub trait Actor<MessageType: Clone + Pod + Zeroable> {
    fn step(
        &mut self,
        env: &mut Context<MessageType>,
        actor_id: usize,
    ) -> Result<SchedulingTask, AikaError>;
}

/// A `ConnectedActor` is an independent logical process that belongs to a `Planet` and can schedule events,
/// send messages, and interact with that `Planet`'s `Context`.
pub trait ConnectedActor<MessageType: Pod + Zeroable + Clone>: Actor<MessageType> {
    fn read_message(
        &mut self,
        env: &mut Context<MessageType>,
        msg: Msg<MessageType>,
        actor_id: usize,
    ) -> Result<(), AikaError>;
}

pub enum ActorType<MessageType: Pod + Zeroable + Clone> {
    Basic(Box<dyn Actor<MessageType>>),
    Connected(Box<dyn ConnectedActor<MessageType>>),
}

impl<MessageType: Pod + Zeroable + Clone> ActorType<MessageType> {
    pub fn step(
        &mut self,
        env: &mut Context<MessageType>,
        actor_id: usize,
    ) -> Result<SchedulingTask, AikaError> {
        match self {
            ActorType::Basic(actor) => actor.step(env, actor_id),
            ActorType::Connected(connected_actor) => connected_actor.step(env, actor_id),
        }
    }
}