#![allow(clippy::type_complexity)]
use rand::rngs::StdRng;
use rand::SeedableRng;
use rustsim_core::{
event_queue::{EventContext, EventQueueModel},
interaction::{PositionedAgent, SpaceInteraction},
prelude::{Agent, AgentStore, HashMapStore},
types::AgentId,
};
mod support;
use support::{Grid2D, GridPos2, NothingSpace};
#[derive(Debug, Clone)]
struct TimedAgent {
id: AgentId,
log: Vec<(usize, u64)>,
}
impl Agent for TimedAgent {
fn id(&self) -> AgentId {
self.id
}
}
type TimedModel = EventQueueModel<NothingSpace, TimedAgent, HashMapStore<TimedAgent>, (), StdRng>;
#[derive(Debug, Clone)]
struct TimedGridAgent {
id: AgentId,
pos: GridPos2,
}
impl Agent for TimedGridAgent {
fn id(&self) -> AgentId {
self.id
}
}
impl PositionedAgent for TimedGridAgent {
type Position = GridPos2;
fn position(&self) -> &Self::Position {
&self.pos
}
fn set_position(&mut self, position: Self::Position) {
self.pos = position;
}
}
type TimedGridModel =
EventQueueModel<Grid2D, TimedGridAgent, HashMapStore<TimedGridAgent>, (), StdRng>;
fn action_a(
agent: &mut TimedAgent,
ctx: &mut EventContext<'_, NothingSpace, TimedAgent, (), StdRng>,
) {
agent.log.push((0, ctx.time() as u64));
}
fn action_b(
agent: &mut TimedAgent,
ctx: &mut EventContext<'_, NothingSpace, TimedAgent, (), StdRng>,
) {
agent.log.push((1, ctx.time() as u64));
}
fn spatial_action(
agent: &mut TimedGridAgent,
ctx: &mut EventContext<'_, Grid2D, TimedGridAgent, (), StdRng>,
) {
if agent.id == 1 {
ctx.defer_remove_agent(2);
ctx.defer_insert_agent(TimedGridAgent { id: 3, pos: (4, 4) });
}
}
#[test]
fn event_queue_processes_in_time_order() {
let mut store = HashMapStore::new();
store.insert(TimedAgent {
id: 1,
log: Vec::new(),
});
let actions: Vec<
fn(&mut TimedAgent, &mut EventContext<'_, NothingSpace, TimedAgent, (), StdRng>),
> = vec![action_a, action_b];
let mut model = TimedModel::new(store, NothingSpace, (), StdRng::seed_from_u64(42), actions);
model.add_event(1, 1, 5.0);
model.add_event(1, 0, 2.0);
model.add_event(1, 0, 8.0);
assert_eq!(model.queue_len(), 3);
model.step_event();
assert!((model.time_f64() - 2.0).abs() < 1e-10);
model.step_event();
assert!((model.time_f64() - 5.0).abs() < 1e-10);
model.step_event();
assert!((model.time_f64() - 8.0).abs() < 1e-10);
let agent = model.agent(1).unwrap();
assert_eq!(agent.log.len(), 3);
assert_eq!(agent.log[0], (0, 2));
assert_eq!(agent.log[1], (1, 5));
assert_eq!(agent.log[2], (0, 8));
}
#[test]
fn event_queue_step_until_stops_at_boundary() {
let mut store = HashMapStore::new();
store.insert(TimedAgent {
id: 1,
log: Vec::new(),
});
let actions: Vec<
fn(&mut TimedAgent, &mut EventContext<'_, NothingSpace, TimedAgent, (), StdRng>),
> = vec![action_a];
let mut model = TimedModel::new(store, NothingSpace, (), StdRng::seed_from_u64(0), actions);
model.add_event(1, 0, 1.0);
model.add_event(1, 0, 3.0);
model.add_event(1, 0, 7.0);
model.step_until(5.0);
assert!((model.time_f64() - 5.0).abs() < 1e-10);
let agent = model.agent(1).unwrap();
assert_eq!(agent.log.len(), 2);
assert_eq!(model.queue_len(), 1);
}
#[test]
fn event_queue_deterministic_tiebreak() {
let mut store = HashMapStore::new();
store.insert(TimedAgent {
id: 1,
log: Vec::new(),
});
let actions: Vec<
fn(&mut TimedAgent, &mut EventContext<'_, NothingSpace, TimedAgent, (), StdRng>),
> = vec![action_a, action_b];
let mut model = TimedModel::new(store, NothingSpace, (), StdRng::seed_from_u64(0), actions);
model.add_event(1, 0, 1.0);
model.add_event(1, 1, 1.0);
model.run_events(2);
let agent = model.agent(1).unwrap();
assert_eq!(agent.log.len(), 2);
assert_eq!(agent.log[0].0, 0);
assert_eq!(agent.log[1].0, 1);
}
#[test]
fn event_queue_skips_removed_agent() {
let mut store = HashMapStore::new();
store.insert(TimedAgent {
id: 1,
log: Vec::new(),
});
store.insert(TimedAgent {
id: 2,
log: Vec::new(),
});
let actions: Vec<
fn(&mut TimedAgent, &mut EventContext<'_, NothingSpace, TimedAgent, (), StdRng>),
> = vec![action_a];
let mut model = TimedModel::new(store, NothingSpace, (), StdRng::seed_from_u64(0), actions);
model.add_event(1, 0, 1.0);
model.add_event(2, 0, 2.0);
model.remove_agent(1);
model.step_event();
assert!((model.time_f64() - 1.0).abs() < 1e-10);
model.step_event();
assert!((model.time_f64() - 2.0).abs() < 1e-10);
let agent2 = model.agent(2).unwrap();
assert_eq!(agent2.log.len(), 1);
}
#[test]
fn event_queue_spatial_applies_deferred_actions_to_store_and_space() {
let store = HashMapStore::new();
let actions: Vec<
fn(&mut TimedGridAgent, &mut EventContext<'_, Grid2D, TimedGridAgent, (), StdRng>),
> = vec![spatial_action];
let mut model = TimedGridModel::new(
store,
Grid2D::new(10, 10, false),
(),
StdRng::seed_from_u64(0),
actions,
);
model
.insert_positioned_agent(TimedGridAgent { id: 1, pos: (1, 1) })
.unwrap();
model
.insert_positioned_agent(TimedGridAgent { id: 2, pos: (2, 2) })
.unwrap();
model.add_event(1, 0, 1.0);
assert!(model.step_event_spatial().unwrap());
model.validate_space_index().unwrap();
assert!(model.agent(1).is_some());
assert!(model.agent(2).is_none());
assert!(model.agent(3).is_some());
let old_ids =
<Grid2D as SpaceInteraction<TimedGridAgent>>::nearby_ids(model.space(), &(2, 2), 0);
assert!(!old_ids.contains(&2));
let new_ids =
<Grid2D as SpaceInteraction<TimedGridAgent>>::nearby_ids(model.space(), &(4, 4), 0);
assert_eq!(new_ids, vec![3]);
}