use crate::{experiment::ObjectiveScore, message::Message, DiscreteTime};
use dyn_clone::DynClone;
use rand_distr::Distribution;
use rand_distr::Poisson;
use std::collections::VecDeque;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Default)]
pub enum AgentMode {
Proactive,
#[default]
Reactive,
AsleepUntil(DiscreteTime),
Dead,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct AgentMetadata {
pub queue_depth_metrics: Vec<usize>,
pub asleep_cycle_count: DiscreteTime,
}
#[derive(Debug, Clone)]
pub struct SimulationAgent {
pub agent: Box<dyn Agent>,
pub(crate) metadata: AgentMetadata,
pub name: String,
pub state: AgentState,
}
#[derive(Debug, Clone)]
pub struct AgentState {
pub mode: AgentMode,
pub wake_mode: AgentMode,
pub queue: VecDeque<Message>,
pub consumed: Vec<Message>,
pub produced: Vec<Message>,
}
impl Default for AgentState {
fn default() -> Self {
Self {
mode: AgentMode::Dead,
wake_mode: AgentMode::Dead,
queue: VecDeque::new(),
consumed: vec![],
produced: vec![],
}
}
}
pub struct AgentCommand {
pub ty: AgentCommandType,
pub agent_handle: usize,
}
pub enum AgentCommandType {
SendMessage(Message),
Sleep(DiscreteTime),
HaltSimulation(String),
}
pub enum MessageProcessingStatus {
NoError,
InProgress,
}
pub struct AgentContext<'a> {
pub handle: usize,
pub name: &'a str,
pub time: DiscreteTime,
pub(crate) commands: &'a mut Vec<AgentCommandType>,
pub state: &'a AgentState,
pub message_processing_status: MessageProcessingStatus,
}
impl AgentContext<'_> {
pub fn send(&mut self, target: &str, payload: Option<Vec<u8>>) {
self.commands.push(AgentCommandType::SendMessage(Message {
source: self.name.to_string(),
destination: target.to_string(),
queued_time: self.time,
custom_payload: payload,
..Default::default()
}));
}
pub fn send_halt_interrupt(&mut self, reason: &str) {
self.commands
.push(AgentCommandType::HaltSimulation(reason.to_string()));
}
pub fn sleep_for(&mut self, ticks: DiscreteTime) {
self.commands.push(AgentCommandType::Sleep(ticks));
}
pub fn set_processing_status(&mut self, status: MessageProcessingStatus) {
self.message_processing_status = status;
}
}
#[derive(Debug, Clone, Default)]
pub struct AgentOptions {
pub initial_mode: AgentMode,
pub wake_mode: AgentMode,
pub initial_queue: VecDeque<Message>,
pub name: String,
}
impl AgentOptions {
#[must_use]
pub fn defaults_with_name(name: String) -> Self {
Self {
name,
..Default::default()
}
}
}
#[derive(Debug, Clone)]
pub struct AgentInitializer {
pub agent: Box<dyn Agent>,
pub options: AgentOptions,
}
pub trait Agent: std::fmt::Debug + DynClone {
fn on_message(&mut self, ctx: &mut AgentContext, msg: &Message);
#[allow(unused_variables)]
fn on_tick(&mut self, ctx: &mut AgentContext) {}
fn cost(&self) -> ObjectiveScore {
0f64
}
}
dyn_clone::clone_trait_object!(Agent);
pub fn poisson_distributed_consumer<T>(name: T, dist: Poisson<f64>) -> AgentInitializer
where
T: Into<String>,
{
#[derive(Debug, Clone)]
struct PoissonAgent {
period: Poisson<f64>,
}
impl Agent for PoissonAgent {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
fn on_message(&mut self, ctx: &mut AgentContext, _msg: &Message) {
let cooldown_period = self.period.sample(&mut rand::rng()) as u64;
ctx.sleep_for(cooldown_period);
}
}
AgentInitializer {
agent: Box::new(PoissonAgent { period: dist }),
options: AgentOptions::defaults_with_name(name.into()),
}
}
pub fn poisson_distributed_producer<T>(name: T, dist: Poisson<f64>, target: T) -> AgentInitializer
where
T: Into<String>,
{
#[derive(Clone, Debug)]
struct PoissonAgent {
period: Poisson<f64>,
target: String,
}
impl Agent for PoissonAgent {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn on_message(&mut self, ctx: &mut AgentContext, _msg: &Message) {
let cooldown_period = self.period.sample(&mut rand::rng()) as u64;
ctx.sleep_for(cooldown_period);
ctx.send(&self.target, None);
}
}
AgentInitializer {
agent: Box::new(PoissonAgent {
period: dist,
target: target.into(),
}),
options: AgentOptions::defaults_with_name(name.into()),
}
}
pub fn periodic_producer<T>(name: T, period: DiscreteTime, target: T) -> AgentInitializer
where
T: Into<String>,
{
#[derive(Clone, Debug)]
struct PeriodicProducer {
period: DiscreteTime,
target: String,
}
impl Agent for PeriodicProducer {
fn cost(&self) -> ObjectiveScore {
-(self.period as ObjectiveScore)
}
#[allow(unused_variables)]
fn on_message(&mut self, ctx: &mut AgentContext, msg: &Message) {
}
fn on_tick(&mut self, ctx: &mut AgentContext) {
ctx.sleep_for(self.period);
ctx.send(&self.target, None);
}
}
AgentInitializer {
agent: Box::new(PeriodicProducer {
period,
target: target.into(),
}),
options: AgentOptions {
initial_mode: AgentMode::Proactive,
wake_mode: AgentMode::Proactive,
name: name.into(),
..Default::default()
},
}
}
pub fn periodic_consumer<T>(name: T, period: DiscreteTime) -> AgentInitializer
where
T: Into<String>,
{
#[derive(Clone, Debug)]
struct PeriodicConsumer {
period: DiscreteTime,
}
impl Agent for PeriodicConsumer {
fn cost(&self) -> ObjectiveScore {
-(self.period as ObjectiveScore)
}
fn on_message(&mut self, ctx: &mut AgentContext, _msg: &Message) {
ctx.sleep_for(self.period);
}
}
AgentInitializer {
agent: Box::new(PeriodicConsumer { period }),
options: AgentOptions::defaults_with_name(name.into()),
}
}