Skip to main content

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}