1use crate::{
20 agent::Agent, scheduler::Scheduler, space::Space, standard::StandardModel, store::AgentStore,
21 types::AgentId,
22};
23use rand::seq::SliceRandom;
24use thiserror::Error;
25
26#[derive(Debug, Error)]
28pub enum InteractionError<E: std::fmt::Debug + std::fmt::Display> {
29 #[error("duplicate agent id {0}")]
31 DuplicateId(AgentId),
32 #[error("agent not found: {0}")]
34 AgentNotFound(AgentId),
35 #[error("agent {0} is missing from the spatial index at its stored position")]
37 SpaceIndexMissing(AgentId),
38 #[error("agent {0} appears more than once in the spatial index at its stored position")]
40 SpaceIndexDuplicate(AgentId),
41 #[error("space error: {0}")]
43 Space(E),
44 #[error("space rollback failed during {operation}: original error: {source}; rollback error: {rollback}")]
46 RollbackFailed {
47 operation: &'static str,
49 source: E,
51 rollback: E,
53 },
54}
55
56pub trait PositionedAgent: Agent {
61 type Position: Clone;
63
64 fn position(&self) -> &Self::Position;
66
67 fn set_position(&mut self, position: Self::Position);
69}
70
71pub trait SpaceInteraction<A: PositionedAgent>: Space {
76 type Error: std::fmt::Debug + std::fmt::Display;
78
79 fn random_position<R: rand::RngCore>(&self, rng: &mut R) -> A::Position;
81
82 fn add_agent(&mut self, agent: &A) -> Result<(), Self::Error>;
84
85 fn remove_agent(&mut self, agent: &A) -> Result<(), Self::Error>;
87
88 fn nearby_ids(&self, position: &A::Position, radius: usize) -> Vec<AgentId>;
93}
94
95pub fn add_agent<S, A, Store, Props, R, Sch>(
100 model: &mut StandardModel<S, A, Store, Props, R, Sch>,
101 agent: A,
102) -> Result<(), InteractionError<S::Error>>
103where
104 A: PositionedAgent,
105 S: SpaceInteraction<A>,
106 Store: AgentStore<A>,
107 R: rand::RngCore,
108 Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
109{
110 let id = agent.id();
111 if model.agents.contains(id) {
112 return Err(InteractionError::DuplicateId(id));
113 }
114
115 model
118 .space
119 .add_agent(&agent)
120 .map_err(InteractionError::Space)?;
121
122 model.agents.insert(agent);
123
124 if id > model.max_id {
126 model.max_id = id;
127 }
128
129 Ok(())
130}
131
132pub fn validate_space_index<S, A, Store, Props, R, Sch>(
135 model: &StandardModel<S, A, Store, Props, R, Sch>,
136) -> Result<(), InteractionError<S::Error>>
137where
138 A: PositionedAgent,
139 S: SpaceInteraction<A>,
140 Store: AgentStore<A>,
141 R: rand::RngCore,
142 Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
143{
144 for id in model.agents.iter_ids() {
145 let Some(agent) = model.agents.get(id) else {
146 continue;
147 };
148 let matches = model
149 .space
150 .nearby_ids(agent.position(), 0)
151 .into_iter()
152 .filter(|candidate| *candidate == id)
153 .count();
154 match matches {
155 0 => return Err(InteractionError::SpaceIndexMissing(id)),
156 1 => {}
157 _ => return Err(InteractionError::SpaceIndexDuplicate(id)),
158 }
159 }
160 Ok(())
161}
162
163pub fn remove_agent<S, A, Store, Props, R, Sch>(
167 model: &mut StandardModel<S, A, Store, Props, R, Sch>,
168 id: AgentId,
169) -> Result<Option<A>, InteractionError<S::Error>>
170where
171 A: PositionedAgent,
172 S: SpaceInteraction<A>,
173 Store: AgentStore<A>,
174 R: rand::RngCore,
175 Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
176{
177 if let Some(agent_ref) = model.agents.get(id) {
179 model
180 .space
181 .remove_agent(&*agent_ref)
182 .map_err(InteractionError::Space)?;
183 } else {
184 return Ok(None);
185 }
186
187 Ok(model.agents.remove(id))
188}
189
190pub fn move_agent<S, A, Store, Props, R, Sch>(
195 model: &mut StandardModel<S, A, Store, Props, R, Sch>,
196 id: AgentId,
197 new_position: A::Position,
198) -> Result<(), InteractionError<S::Error>>
199where
200 A: PositionedAgent,
201 S: SpaceInteraction<A>,
202 Store: AgentStore<A>,
203 R: rand::RngCore,
204 Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
205{
206 let mut agent_ref = model
207 .agents
208 .get_mut(id)
209 .ok_or(InteractionError::AgentNotFound(id))?;
210
211 let old_position = agent_ref.position().clone();
212
213 model
214 .space
215 .remove_agent(&*agent_ref)
216 .map_err(InteractionError::Space)?;
217
218 agent_ref.set_position(new_position);
219
220 if let Err(source) = model.space.add_agent(&*agent_ref) {
221 agent_ref.set_position(old_position);
222 if let Err(rollback) = model.space.add_agent(&*agent_ref) {
223 return Err(InteractionError::RollbackFailed {
224 operation: "move_agent",
225 source,
226 rollback,
227 });
228 }
229 return Err(InteractionError::Space(source));
230 }
231
232 Ok(())
233}
234
235pub fn random_id<S, A, Store, Props, R, Sch>(
239 model: &mut StandardModel<S, A, Store, Props, R, Sch>,
240) -> Option<AgentId>
241where
242 A: Agent,
243 S: Space,
244 Store: AgentStore<A>,
245 R: rand::RngCore,
246 Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
247{
248 let ids: Vec<AgentId> = model.agents.iter_ids();
249 if ids.is_empty() {
250 return None;
251 }
252
253 let mut rng = model.rng_mut();
254 ids.choose(&mut *rng).copied()
255}
256
257pub fn all_ids<S, A, Store, Props, R, Sch>(
259 model: &StandardModel<S, A, Store, Props, R, Sch>,
260) -> Vec<AgentId>
261where
262 A: Agent,
263 S: Space,
264 Store: AgentStore<A>,
265 R: rand::RngCore,
266 Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
267{
268 model.agents.iter_ids()
269}
270
271pub fn nearby_ids<S, A, Store, Props, R, Sch>(
276 model: &StandardModel<S, A, Store, Props, R, Sch>,
277 position: &A::Position,
278 radius: usize,
279) -> Vec<AgentId>
280where
281 A: PositionedAgent,
282 S: SpaceInteraction<A>,
283 Store: AgentStore<A>,
284 R: rand::RngCore,
285 Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
286{
287 model.space.nearby_ids(position, radius)
288}
289
290pub fn nearby_agents<'a, S, A, Store, Props, R, Sch>(
295 model: &'a StandardModel<S, A, Store, Props, R, Sch>,
296 position: &A::Position,
297 radius: usize,
298) -> Vec<std::cell::Ref<'a, A>>
299where
300 A: PositionedAgent,
301 S: SpaceInteraction<A>,
302 Store: AgentStore<A>,
303 R: rand::RngCore,
304 Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
305{
306 model
307 .space
308 .nearby_ids(position, radius)
309 .into_iter()
310 .filter_map(|id| model.agents.get(id))
311 .collect()
312}
313
314pub fn nearby_ids_except<S, A, Store, Props, R, Sch>(
319 model: &StandardModel<S, A, Store, Props, R, Sch>,
320 position: &A::Position,
321 radius: usize,
322 exclude_id: AgentId,
323) -> Vec<AgentId>
324where
325 A: PositionedAgent,
326 S: SpaceInteraction<A>,
327 Store: AgentStore<A>,
328 R: rand::RngCore,
329 Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
330{
331 model
332 .space
333 .nearby_ids(position, radius)
334 .into_iter()
335 .filter(|&id| id != exclude_id)
336 .collect()
337}
338
339pub fn nearby_agents_except<'a, S, A, Store, Props, R, Sch>(
344 model: &'a StandardModel<S, A, Store, Props, R, Sch>,
345 position: &A::Position,
346 radius: usize,
347 exclude_id: AgentId,
348) -> Vec<std::cell::Ref<'a, A>>
349where
350 A: PositionedAgent,
351 S: SpaceInteraction<A>,
352 Store: AgentStore<A>,
353 R: rand::RngCore,
354 Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
355{
356 model
357 .space
358 .nearby_ids(position, radius)
359 .into_iter()
360 .filter(|&id| id != exclude_id)
361 .filter_map(|id| model.agents.get(id))
362 .collect()
363}
364
365pub fn random_agent<'a, S, A, Store, Props, R, Sch>(
369 model: &'a mut StandardModel<S, A, Store, Props, R, Sch>,
370) -> Option<std::cell::Ref<'a, A>>
371where
372 A: Agent,
373 S: Space,
374 Store: AgentStore<A>,
375 R: rand::RngCore,
376 Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
377{
378 let ids = model.agents.iter_ids();
379 if ids.is_empty() {
380 return None;
381 }
382
383 let mut rng = model.rng_mut();
384 let id = *ids.choose(&mut *rng)?;
385 drop(rng);
386
387 model.agents.get(id)
388}
389
390pub fn add_agent_random<S, A, Store, Props, R, Sch>(
395 model: &mut StandardModel<S, A, Store, Props, R, Sch>,
396 mut agent: A,
397) -> Result<(), InteractionError<S::Error>>
398where
399 A: PositionedAgent,
400 S: SpaceInteraction<A>,
401 Store: AgentStore<A>,
402 R: rand::RngCore,
403 Sch: Scheduler<StandardModel<S, A, Store, Props, R, Sch>>,
404{
405 let mut rng = model.rng_mut();
406 let position = model.space.random_position(&mut *rng);
407 drop(rng);
408
409 agent.set_position(position);
410 add_agent(model, agent)
411}