rustsim_core/store.rs
1//! Agent containers - storage backends for the agent population.
2//!
3//! Two implementations are provided:
4//!
5//! - [`HashMapStore`] - backed by `hashbrown::HashMap`. General-purpose; supports
6//! arbitrary agent addition and removal. Best when agents are frequently
7//! created or destroyed.
8//!
9//! - [`VecStore`] - backed by a `Vec<Option<RefCell<A>>>` indexed by agent ID.
10//! Faster iteration for dense, contiguous ID ranges. Best when agents are
11//! never (or rarely) removed.
12//!
13//! Both stores use [`RefCell`] for interior mutability, allowing agent step
14//! functions to borrow one agent mutably while reading others immutably.
15//!
16//! # Determinism note
17//!
18//! Store iteration order is part of simulation reproducibility whenever a
19//! scheduler or caller relies on `iter_ids()` / `iter_ids_into()` without an
20//! additional ordering step.
21//!
22//! - [`VecStore`] iterates in ascending ID order.
23//! - [`HashMapStore`] iteration order is not guaranteed and must not be treated
24//! as a reproducible activation order.
25//!
26//! For deterministic activation order across runs, prefer [`crate::scheduler::ById`]
27//! or another scheduler that imposes an explicit order.
28//!
29//! [`RefCell`]: std::cell::RefCell
30
31use hashbrown::HashMap;
32use std::cell::{Ref, RefCell, RefMut};
33
34use crate::{agent::Agent, types::AgentId};
35
36/// Trait for agent containers.
37///
38/// Provides CRUD operations and ID iteration. All access methods take `&self`
39/// (not `&mut self`) for reads, using [`RefCell`] internally to support
40/// concurrent borrows of different agents.
41///
42/// [`RefCell`]: std::cell::RefCell
43pub trait AgentStore<A: Agent> {
44 /// Borrow an agent immutably by ID.
45 fn get(&self, id: AgentId) -> Option<Ref<'_, A>>;
46
47 /// Borrow an agent mutably by ID.
48 ///
49 /// # Panics
50 ///
51 /// Panics at runtime if the agent is already borrowed (see [`RefCell`] rules).
52 ///
53 /// [`RefCell`]: std::cell::RefCell
54 fn get_mut(&self, id: AgentId) -> Option<RefMut<'_, A>>;
55
56 /// Insert an agent into the store.
57 ///
58 /// If an agent with the same ID already exists, it is silently replaced.
59 fn insert(&mut self, agent: A);
60
61 /// Remove and return an agent by ID, or `None` if not found.
62 fn remove(&mut self, id: AgentId) -> Option<A>;
63
64 /// Check whether an agent with the given ID exists.
65 fn contains(&self, id: AgentId) -> bool {
66 self.get(id).is_some()
67 }
68
69 /// Collect all agent IDs into a new `Vec`.
70 ///
71 /// Prefer [`iter_ids_into`] when a reusable buffer is available.
72 ///
73 /// [`iter_ids_into`]: AgentStore::iter_ids_into
74 fn iter_ids(&self) -> Vec<AgentId>;
75
76 /// Append all agent IDs into `buf` without clearing it first.
77 ///
78 /// This avoids allocation when `buf` is reused across calls.
79 fn iter_ids_into(&self, buf: &mut Vec<AgentId>) {
80 buf.extend(self.iter_ids());
81 }
82
83 /// Number of agents in the store.
84 fn len(&self) -> usize;
85
86 /// Returns `true` if the store contains no agents.
87 fn is_empty(&self) -> bool {
88 self.len() == 0
89 }
90}
91
92/// Hash-map-based agent store.
93///
94/// Backed by [`hashbrown::HashMap`] for O(1) average-case insert, remove,
95/// and lookup. Iteration order is not guaranteed and must not be used as a
96/// reproducibility contract.
97///
98/// This is the default store for most simulations.
99#[derive(Debug, Default)]
100pub struct HashMapStore<A: Agent> {
101 agents: HashMap<AgentId, RefCell<A>>,
102}
103
104impl<A: Agent> HashMapStore<A> {
105 /// Create an empty `HashMapStore`.
106 pub fn new() -> Self {
107 Self {
108 agents: HashMap::new(),
109 }
110 }
111}
112
113impl<A: Agent> AgentStore<A> for HashMapStore<A> {
114 fn get(&self, id: AgentId) -> Option<Ref<'_, A>> {
115 self.agents.get(&id).map(|cell| cell.borrow())
116 }
117
118 fn get_mut(&self, id: AgentId) -> Option<RefMut<'_, A>> {
119 self.agents.get(&id).map(|cell| cell.borrow_mut())
120 }
121
122 fn insert(&mut self, agent: A) {
123 self.agents.insert(agent.id(), RefCell::new(agent));
124 }
125
126 fn remove(&mut self, id: AgentId) -> Option<A> {
127 self.agents.remove(&id).map(|cell| cell.into_inner())
128 }
129
130 fn iter_ids(&self) -> Vec<AgentId> {
131 self.agents.keys().copied().collect()
132 }
133
134 fn iter_ids_into(&self, buf: &mut Vec<AgentId>) {
135 buf.extend(self.agents.keys().copied());
136 }
137
138 fn len(&self) -> usize {
139 self.agents.len()
140 }
141}
142
143/// Dense vector-based agent store.
144///
145/// Uses agent IDs as indices into a `Vec<Option<RefCell<A>>>`. Provides
146/// faster iteration than [`HashMapStore`] when IDs form a dense, contiguous
147/// range starting near 0.
148///
149/// Iteration order is ascending by agent ID, which makes it suitable for
150/// deterministic workflows when paired with deterministic stepping logic.
151///
152/// Inserting an agent with a large ID will grow the internal vector to
153/// accommodate it, potentially wasting memory for sparse ID spaces.
154#[derive(Debug, Default)]
155pub struct VecStore<A: Agent> {
156 agents: Vec<Option<RefCell<A>>>,
157 count: usize,
158}
159
160impl<A: Agent> VecStore<A> {
161 /// Create an empty `VecStore`.
162 pub fn new() -> Self {
163 Self {
164 agents: Vec::new(),
165 count: 0,
166 }
167 }
168
169 fn ensure_slot(&mut self, id: AgentId) {
170 let index = id as usize;
171 if index >= self.agents.len() {
172 self.agents.resize_with(index + 1, || None);
173 }
174 }
175}
176
177impl<A: Agent> AgentStore<A> for VecStore<A> {
178 fn get(&self, id: AgentId) -> Option<Ref<'_, A>> {
179 self.agents
180 .get(id as usize)
181 .and_then(|slot| slot.as_ref().map(|cell| cell.borrow()))
182 }
183
184 fn get_mut(&self, id: AgentId) -> Option<RefMut<'_, A>> {
185 self.agents
186 .get(id as usize)
187 .and_then(|slot| slot.as_ref().map(|cell| cell.borrow_mut()))
188 }
189
190 fn insert(&mut self, agent: A) {
191 let id = agent.id();
192 self.ensure_slot(id);
193 let slot = &mut self.agents[id as usize];
194 if slot.is_none() {
195 self.count += 1;
196 }
197 *slot = Some(RefCell::new(agent));
198 }
199
200 fn remove(&mut self, id: AgentId) -> Option<A> {
201 if (id as usize) < self.agents.len() {
202 let taken = self.agents[id as usize]
203 .take()
204 .map(|cell| cell.into_inner());
205 if taken.is_some() {
206 self.count -= 1;
207 }
208 taken
209 } else {
210 None
211 }
212 }
213
214 fn iter_ids(&self) -> Vec<AgentId> {
215 self.agents
216 .iter()
217 .enumerate()
218 .filter_map(|(idx, slot)| slot.as_ref().map(|_| idx as AgentId))
219 .collect()
220 }
221
222 fn iter_ids_into(&self, buf: &mut Vec<AgentId>) {
223 for (idx, slot) in self.agents.iter().enumerate() {
224 if slot.is_some() {
225 buf.push(idx as AgentId);
226 }
227 }
228 }
229
230 fn len(&self) -> usize {
231 self.count
232 }
233}