#![allow(clippy::type_complexity)]
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use rustsim_core::{
event_queue::{EventContext, EventQueueModel},
prelude::*,
};
mod support;
use support::NothingSpace;
#[derive(Debug, Clone)]
struct Counter {
id: AgentId,
count: u64,
}
impl Agent for Counter {
fn id(&self) -> AgentId {
self.id
}
}
type CounterModel = StandardModel<NothingSpace, Counter, VecStore<Counter>, (), StdRng, ById>;
fn increment_step(
agent: &mut Counter,
_ctx: &mut StepContext<'_, NothingSpace, Counter, (), StdRng, ById>,
) {
agent.count += 1;
}
#[test]
fn vec_store_iter_ids_matches_live_population_under_random_insert_remove() {
let mut rng = StdRng::seed_from_u64(42);
let mut store = VecStore::new();
let mut expected = std::collections::BTreeSet::new();
for _ in 0..1_000 {
let id = rng.gen_range(1..=200u64);
if rng.gen_bool(0.55) {
store.insert(Counter { id, count: id });
expected.insert(id);
} else {
let _ = store.remove(id);
expected.remove(&id);
}
let ids = store.iter_ids();
let actual: std::collections::BTreeSet<_> = ids.iter().copied().collect();
assert_eq!(actual, expected);
assert_eq!(store.len(), expected.len());
assert_eq!(store.is_empty(), expected.is_empty());
}
}
#[test]
fn standard_model_next_id_is_strictly_monotonic_after_random_insertions() {
let mut store = VecStore::new();
store.insert(Counter { id: 10, count: 0 });
store.insert(Counter { id: 25, count: 0 });
let mut model = CounterModel::new_base(
store,
NothingSpace,
ById::new(),
(),
StdRng::seed_from_u64(7),
)
.with_agent_step_ctx(increment_step);
let mut last = 25;
for _ in 0..200 {
let next = model.next_id();
assert!(next > last);
model.insert_agent(Counter { id: next, count: 0 }).unwrap();
last = next;
}
}
#[test]
fn by_id_scheduler_always_returns_sorted_unique_ids_under_random_population() {
let mut rng = StdRng::seed_from_u64(9);
for _ in 0..50 {
let mut store = VecStore::new();
let mut expected = Vec::new();
for id in 1..=100u64 {
if rng.gen_bool(0.6) {
store.insert(Counter { id, count: 0 });
expected.push(id);
}
}
expected.sort_unstable();
let model = CounterModel::new_base(
store,
NothingSpace,
ById::new(),
(),
StdRng::seed_from_u64(1),
);
let mut scheduler = ById::new();
let mut buf = Vec::new();
scheduler.schedule_into(&model, &mut buf);
assert_eq!(buf, expected);
assert!(buf.windows(2).all(|w| w[0] < w[1]));
}
}
#[derive(Debug, Clone)]
struct TimedAgent {
id: AgentId,
}
impl Agent for TimedAgent {
fn id(&self) -> AgentId {
self.id
}
}
type EventLog = Vec<(AgentId, u32)>;
type TimedModel = EventQueueModel<NothingSpace, TimedAgent, VecStore<TimedAgent>, EventLog, StdRng>;
fn log_action(
agent: &mut TimedAgent,
ctx: &mut EventContext<'_, NothingSpace, TimedAgent, EventLog, StdRng>,
) {
let time = ctx.time() as u32;
ctx.properties_mut().push((agent.id, time));
}
#[test]
fn event_queue_preserves_nondecreasing_time_under_random_event_sets() {
let mut rng = StdRng::seed_from_u64(99);
for _ in 0..25 {
let mut store = VecStore::new();
for id in 1..=8u64 {
store.insert(TimedAgent { id });
}
let actions: Vec<
fn(&mut TimedAgent, &mut EventContext<'_, NothingSpace, TimedAgent, EventLog, StdRng>),
> = vec![log_action];
let mut model = TimedModel::new(
store,
NothingSpace,
Vec::new(),
StdRng::seed_from_u64(5),
actions,
);
for _ in 0..100 {
let id = rng.gen_range(1..=8u64);
let dt = rng.gen_range(0..=20) as f64;
model.add_event(id, 0, dt);
}
model.run_events(100);
let times: Vec<u32> = model.properties().iter().map(|(_, t)| *t).collect();
assert!(times.windows(2).all(|w| w[0] <= w[1]));
}
}