desque/threadsafe/simulation.rs
1use super::events::EventQueue;
2use super::Event;
3use crate::{SimState, SimTime};
4use std::fmt::{Debug, Formatter};
5use std::ops::Add;
6
7/// Contains the event queue and other state belonging to a simulation.
8///
9/// This form of simulation behaves very similarly to the [`serial::Simulation`], but is easier to share across thread
10/// boundaries for the sake of enabling events to divide-and-conquer parts of their execution.
11///
12/// The expected workflow for a Simulation is:
13///
14/// 1. Initialize a struct that implements [`SimState`] and [`Sync`].
15/// 2. Pass this struct and the start time to `new()`.
16/// 3. Schedule at least one initial event.
17/// 4. Call [`run()`]. Handle any error it might return.
18/// 5. Use the [`state()`] or [`state_mut()`] accessors to finish processing the results.
19///
20/// A [`Simulation`] is [`Sync`], and will also be [`Send`] if and only if the [`SimState`] implementation is [`Send`].
21///
22/// > Note: in the implementation of [`Debug`], scheduled events will be printed in an arbitrary order and the number of
23/// > `total_events_scheduled` is over the entirety of the simulation run, as opposed to the number currently in queue.
24///
25/// [`serial::Simulation`]: crate::serial::Simulation
26/// [`run()`]: Simulation::run
27/// [`state()`]: Simulation::state
28/// [`state_mut()`]: Simulation::state_mut
29#[derive(Debug, Default)]
30pub struct Simulation<State, Time>
31where
32 State: SimState<Time> + Sync,
33 Time: SimTime + Send + Sync,
34{
35 /// A priority queue of events that have been scheduled to execute, ordered ascending by execution time.
36 event_queue: EventQueue<State, Time>,
37 /// The current shared state of the Simulation. Exclusive access will be granted to each event that executes.
38 state: State,
39 /// The current simulation time.
40 current_time: Time,
41}
42
43impl<State, Time> Simulation<State, Time>
44where
45 State: SimState<Time> + Sync,
46 Time: SimTime + Send + Sync,
47{
48 /// Initialize a Simulation instance with the provided starting state and an empty event queue, with clock set to
49 /// the provided starting time.
50 pub fn new(initial_state: State, start_time: Time) -> Self {
51 Self {
52 event_queue: EventQueue::new(),
53 state: initial_state,
54 current_time: start_time,
55 }
56 }
57
58 /// Execute events from the priority queue, one at a time, in ascending order by execution time.
59 ///
60 /// Follows this loop:
61 ///
62 /// 1. Does [`state.is_complete()`] return true? If so, return `Ok(())`.
63 /// 2. Attempt to pop the next event from the queue. If there isn't one, return `Ok(())`.
64 /// 3. Pass `&mut self` to [`event.execute()`]. If execution results in an error, forward it to the caller;
65 /// otherwise return to step 1.
66 ///
67 /// # Errors
68 ///
69 /// Errors may occur during execution of events, and if encountered here they will be passed back to the caller,
70 /// unchanged. The two variants directly supported are:
71 ///
72 /// 1. [`Error::BackInTime`] means that client code attempted to schedule an event at some point in the
73 /// simulation's past. This error is a likely indicator that client code contains a logical bug, as most
74 /// discrete-event simulations would never rewind their clocks.
75 /// 2. [`Error::BadExecution`] wraps a client-generated error in a way that is type-safe to feed back through this
76 /// method. To handle the underlying error, either unpack the [`BadExecution`] or call its [`source()`] method.
77 ///
78 /// # Panics
79 ///
80 /// This method requires the ability to lock the [`Mutex`] on the internal event queue to find the next event that
81 /// should be executed on each loop iteration. If that [`Mutex`] ever becomes poisoned, this method will panic.
82 ///
83 /// [`state.is_complete()`]: SimState::is_complete
84 /// [`event.execute()`]: Event::execute
85 /// [`Error::BackInTime`]: crate::Error::BackInTime
86 /// [`Error::BadExecution`]: crate::Error::BadExecution
87 /// [`BadExecution`]: crate::Error::BadExecution
88 /// [`source()`]: crate::Error#method.source
89 /// [`Mutex`]: std::sync::Mutex
90 pub fn run(&mut self) -> crate::Result {
91 loop {
92 if self.state.is_complete(self.current_time()) {
93 return Ok(());
94 }
95
96 let next_event = self.next_event();
97 if next_event.is_none() {
98 return Ok(());
99 }
100
101 let mut next_event = next_event.expect("next_event should not be None");
102 next_event.execute(self)?;
103 }
104 }
105
106 fn next_event(&mut self) -> Option<Box<dyn Event<State, Time>>> {
107 if let Some((event, time)) = self.event_queue.next() {
108 self.current_time = time;
109 Some(event)
110 } else {
111 None
112 }
113 }
114
115 /// Schedule the provided event at the specified time.
116 ///
117 /// # Errors
118 ///
119 /// If `time` is less than the current clock time on `self`, returns an [`Error::BackInTime`] to indicate the likely
120 /// presence of a logical bug at the call site, with no modifications to the queue.
121 ///
122 /// # Panics
123 ///
124 /// This method requires the ability to lock the [`Mutex`] on the internal event queue. If that [`Mutex`] ever
125 /// becomes poisoned, this method will panic.
126 ///
127 /// [`Error::BackInTime`]: crate::Error::BackInTime
128 /// [`Mutex`]: std::sync::Mutex
129 pub fn schedule<EventType>(&self, event: EventType, time: Time) -> crate::Result
130 where
131 EventType: Event<State, Time> + 'static,
132 {
133 if time < self.current_time {
134 return Err(crate::Error::BackInTime);
135 }
136
137 // SAFETY: we've just checked that the desired execution time is either
138 // Equal or Greater when compared to the current clock time, so it'll
139 // be fine to add to the queue
140 unsafe {
141 self.schedule_unchecked(event, time);
142 }
143 Ok(())
144 }
145
146 /// Schedule the provided event at the specified time. Assumes that the provided time is valid in the context of the
147 /// client's simulation.
148 ///
149 /// # Safety
150 ///
151 /// While this method cannot trigger undefined behaviors, scheduling an event for a time in the past is likely to be
152 /// a logical bug in client code. Generally, this method should only be invoked if the condition `time >= clock` is
153 /// already enforced at the call site through some other means. For example, adding a strictly positive offset to
154 /// the current clock time to get the `time` argument for the call.
155 ///
156 /// # Panics
157 ///
158 /// This method requires the ability to lock the [`Mutex`] on the internal event queue. If that [`Mutex`] ever
159 /// becomes poisoned, this method will panic.
160 ///
161 /// [`Mutex`]: std::sync::Mutex
162 pub unsafe fn schedule_unchecked<EventType>(&self, event: EventType, time: Time)
163 where
164 EventType: Event<State, Time> + 'static,
165 {
166 self.schedule_unchecked_from_boxed(Box::new(event), time);
167 }
168
169 /// Schedule the provided event at the specified time.
170 ///
171 /// # Errors
172 ///
173 /// If `time` is less than the current clock time on `self`, returns an [`Error::BackInTime`] to indicate the likely
174 /// presence of a logical bug at the call site, with no modifications to the queue.
175 ///
176 /// # Panics
177 ///
178 /// This method requires the ability to lock the [`Mutex`] on the internal event queue. If that [`Mutex`] ever
179 /// becomes poisoned, this method will panic.
180 ///
181 /// [`Error::BackInTime`]: crate::Error::BackInTime
182 /// [`Mutex`]: std::sync::Mutex
183 pub fn schedule_from_boxed(&self, event: Box<dyn Event<State, Time>>, time: Time) -> crate::Result {
184 if time < self.current_time {
185 return Err(crate::Error::BackInTime);
186 }
187
188 // SAFETY: we've just checked that the desired execution time is either
189 // Equal or Greater when compared to the current clock time, so it'll
190 // be fine to add to the queue
191 unsafe {
192 self.schedule_unchecked_from_boxed(event, time);
193 }
194 Ok(())
195 }
196
197 /// Schedule the provided event at the specified time. Assumes that the provided time is valid in the context of the
198 /// client's simulation.
199 ///
200 /// # Safety
201 ///
202 /// While this method cannot trigger undefined behaviors, scheduling an event for a time in the past is likely to be
203 /// a logical bug in client code. Generally, this method should only be invoked if the condition `time >= clock` is
204 /// already enforced at the call site through some other means. For example, adding a strictly positive offset to
205 /// the current clock time to get the `time` argument for the call.
206 ///
207 /// # Panics
208 ///
209 /// This method requires the ability to lock the [`Mutex`] on the internal event queue. If that [`Mutex`] ever
210 /// becomes poisoned, this method will panic.
211 ///
212 /// [`Mutex`]: std::sync::Mutex
213 pub unsafe fn schedule_unchecked_from_boxed(&self, event: Box<dyn Event<State, Time>>, time: Time) {
214 self.event_queue.schedule_event(event, time);
215 }
216
217 /// Get a shared reference to the simulation state.
218 pub fn state(&self) -> &State {
219 &self.state
220 }
221
222 /// Get an exclusive reference to the simulation state.
223 pub fn state_mut(&mut self) -> &mut State {
224 &mut self.state
225 }
226
227 /// Get a shared reference to the current simulation time.
228 pub fn current_time(&self) -> &Time {
229 &self.current_time
230 }
231}
232
233impl<State, Time> Simulation<State, Time>
234where
235 State: SimState<Time> + Sync,
236 Time: SimTime + Send + Sync + Clone,
237{
238 /// Schedule the provided event to execute at the current sim time. Events previously scheduled for "now" will still
239 /// execute before this event does.
240 ///
241 /// # Errors
242 ///
243 /// If the result of calling [`Clone::clone`] on the current sim time results in a new value that is somehow less
244 /// than the current sim time, this method will return an [`Error::BackInTime`]. Note that such behavior is not
245 /// expected from implementations of [`Clone::clone`] in most cases.
246 ///
247 /// # Panics
248 ///
249 /// This method requires the ability to lock the [`Mutex`] on the internal event queue. If that [`Mutex`] ever
250 /// becomes poisoned, this method will panic.
251 ///
252 /// [`Error::BackInTime`]: crate::Error::BackInTime
253 /// [`Mutex`]: std::sync::Mutex
254 pub fn schedule_now<EventType>(&self, event: EventType) -> crate::Result
255 where
256 EventType: Event<State, Time> + 'static,
257 {
258 let event_time = self.current_time.clone();
259 self.schedule(event, event_time)
260 }
261
262 /// Schedule the provided event to execute at the current sim time. Events previously scheduled for "now" will still
263 /// execute before this event does.
264 ///
265 /// # Safety
266 ///
267 /// This method cannot directly trigger undefined behaviors, but relies on client implementations of
268 /// [`Clone::clone`] producing new values of [`SimTime`] that are not less than the cloned receiver (i.e. the
269 /// current simulation time). If `my_sim_time.clone().cmp(my_sim_time) != Ordering::Less` is always true for your
270 /// chosen type, this method will be safe to call.
271 ///
272 /// # Panics
273 ///
274 /// This method requires the ability to lock the [`Mutex`] on the internal event queue. If that [`Mutex`] ever
275 /// becomes poisoned, this method will panic.
276 ///
277 /// [`Mutex`]: std::sync::Mutex
278 pub unsafe fn schedule_now_unchecked<EventType>(&self, event: EventType)
279 where
280 EventType: Event<State, Time> + 'static,
281 {
282 self.schedule_unchecked(event, self.current_time.clone());
283 }
284
285 /// Schedule the provided event to execute at the current sim time. Events previously scheduled for "now" will still
286 /// execute before this event does.
287 ///
288 /// # Errors
289 ///
290 /// If the result of calling [`Clone::clone`] on the current sim time results in a new value that is somehow less
291 /// than the current sim time, this method will return an [`Error::BackInTime`]. Note that such behavior is not
292 /// expected from implementations of [`Clone::clone`] in most cases.
293 ///
294 /// # Panics
295 ///
296 /// This method requires the ability to lock the [`Mutex`] on the internal event queue. If that [`Mutex`] ever
297 /// becomes poisoned, this method will panic.
298 ///
299 /// [`Error::BackInTime`]: crate::Error::BackInTime
300 /// [`Mutex`]: std::sync::Mutex
301 pub fn schedule_now_from_boxed(&self, event: Box<dyn Event<State, Time>>) -> crate::Result {
302 let event_time = self.current_time.clone();
303 self.schedule_from_boxed(event, event_time)
304 }
305
306 /// Schedule the provided event to execute at the current sim time. Events previously scheduled for "now" will still
307 /// execute before this event does.
308 ///
309 /// # Safety
310 ///
311 /// This method cannot directly trigger undefined behaviors, but relies on client implementations of
312 /// [`Clone::clone`] producing new values of [`SimTime`] that are not less than the cloned receiver (i.e. the
313 /// current simulation time). If `my_sim_time.clone().cmp(my_sim_time) != Ordering::Less` is always true for your
314 /// chosen type, this method will be safe to call.
315 ///
316 /// # Panics
317 ///
318 /// This method requires the ability to lock the [`Mutex`] on the internal event queue. If that [`Mutex`] ever
319 /// becomes poisoned, this method will panic.
320 ///
321 /// [`Mutex`]: std::sync::Mutex
322 pub unsafe fn schedule_now_unchecked_from_boxed(&self, event: Box<dyn Event<State, Time>>) {
323 self.schedule_unchecked_from_boxed(event, self.current_time.clone());
324 }
325}
326
327impl<State, Time> Simulation<State, Time>
328where
329 State: SimState<Time> + Sync,
330 Time: SimTime + Send + Sync + Clone + Add<Output = Time>,
331{
332 /// Schedule the provided event after the specified delay. The event's execution time will be equal to the result of
333 /// `self.current_time().clone() + delay`.
334 ///
335 /// # Errors
336 ///
337 /// If the calculated execution time is less than the current clock time on `self`, returns an [`Error::BackInTime`]
338 /// to indicate the likely presence of a logical bug at the call site, with no modifications to the queue.
339 ///
340 /// # Panics
341 ///
342 /// This method requires the ability to lock the [`Mutex`] on the internal event queue. If that [`Mutex`] ever
343 /// becomes poisoned, this method will panic.
344 ///
345 /// [`Error::BackInTime`]: crate::Error::BackInTime
346 /// [`Mutex`]: std::sync::Mutex
347 pub fn schedule_with_delay<EventType>(&self, event: EventType, delay: Time) -> crate::Result
348 where
349 EventType: Event<State, Time> + 'static,
350 {
351 let event_time = self.current_time.clone() + delay;
352 self.schedule(event, event_time)
353 }
354
355 /// Schedule the provided event after the specified delay. The event's execution time will be equal to the result of
356 /// `self.current_time().clone() + delay`.
357 ///
358 /// # Safety
359 ///
360 /// This method cannot directly trigger undefined behaviors, but relies on the provided `delay` being "nonnegative;"
361 /// in other words that `self.current_time().cmp(self.current_time().clone() + delay) != Ordering::Greater` should
362 /// always be true. If you are certain that is true for your type, this method will be safe to call. Alternatively,
363 /// you may call this method to intentionally schedule an event in the past if your use case truly calls for that.
364 ///
365 /// # Panics
366 ///
367 /// This method requires the ability to lock the [`Mutex`] on the internal event queue. If that [`Mutex`] ever
368 /// becomes poisoned, this method will panic.
369 ///
370 /// [`Mutex`]: std::sync::Mutex
371 pub unsafe fn schedule_with_delay_unchecked<EventType>(&self, event: EventType, delay: Time)
372 where
373 EventType: Event<State, Time> + 'static,
374 {
375 let event_time = self.current_time.clone() + delay;
376 self.schedule_unchecked(event, event_time);
377 }
378
379 /// Schedule the provided event after the specified delay. The event's execution time will be equal to the result of
380 /// `self.current_time().clone() + delay`.
381 ///
382 /// # Errors
383 ///
384 /// If the calculated execution time is less than the current clock time on `self`, returns an [`Error::BackInTime`]
385 /// to indicate the likely presence of a logical bug at the call site, with no modifications to the queue.
386 ///
387 /// # Panics
388 ///
389 /// This method requires the ability to lock the [`Mutex`] on the internal event queue. If that [`Mutex`] ever
390 /// becomes poisoned, this method will panic.
391 ///
392 /// [`Error::BackInTime`]: crate::Error::BackInTime
393 /// [`Mutex`]: std::sync::Mutex
394 pub fn schedule_with_delay_from_boxed(&self, event: Box<dyn Event<State, Time>>, delay: Time) -> crate::Result {
395 let event_time = self.current_time.clone() + delay;
396 self.schedule_from_boxed(event, event_time)
397 }
398
399 /// Schedule the provided event after the specified delay. The event's execution time will be equal to the result of
400 /// `self.current_time().clone() + delay`.
401 ///
402 /// # Safety
403 ///
404 /// This method cannot directly trigger undefined behaviors, but relies on the provided `delay` being "nonnegative;"
405 /// in other words that `self.current_time().cmp(self.current_time().clone() + delay) != Ordering::Greater` should
406 /// always be true. If you are certain that is true for your type, this method will be safe to call. Alternatively,
407 /// you may call this method to intentionally schedule an event in the past if your use case truly calls for that.
408 ///
409 /// # Panics
410 ///
411 /// This method requires the ability to lock the [`Mutex`] on the internal event queue. If that [`Mutex`] ever
412 /// becomes poisoned, this method will panic.
413 ///
414 /// [`Mutex`]: std::sync::Mutex
415 pub unsafe fn schedule_with_delay_unchecked_from_boxed(&self, event: Box<dyn Event<State, Time>>, delay: Time) {
416 let event_time = self.current_time.clone() + delay;
417 self.schedule_unchecked_from_boxed(event, event_time);
418 }
419}
420
421impl<State, Time> std::fmt::Display for Simulation<State, Time>
422where
423 State: SimState<Time> + Sync,
424 Time: SimTime + Send + Sync,
425{
426 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
427 write!(f, "Simulation at time {:?}", self.current_time())
428 }
429}
430
431#[cfg(test)]
432mod tests {
433 use super::*;
434 use crate::threadsafe::OkEvent;
435
436 #[derive(Debug)]
437 struct State {
438 executed_event_values: Vec<i32>,
439 complete: bool,
440 }
441 impl SimState<i32> for State {
442 fn is_complete(&self, _: &i32) -> bool {
443 self.complete
444 }
445 }
446
447 #[derive(Debug)]
448 struct TestEvent {
449 value: i32,
450 }
451
452 impl Event<State, i32> for TestEvent {
453 fn execute(&mut self, sim: &mut Simulation<State, i32>) -> crate::Result {
454 sim.state_mut().executed_event_values.push(self.value);
455 Ok(())
456 }
457 }
458
459 #[derive(Debug)]
460 struct CompletionEvent {}
461
462 impl OkEvent<State, i32> for CompletionEvent {
463 fn execute(&mut self, sim: &mut Simulation<State, i32>) {
464 sim.state_mut().complete = true;
465 }
466 }
467
468 fn setup() -> Simulation<State, i32> {
469 let sim = Simulation::new(
470 State {
471 executed_event_values: Vec::with_capacity(3),
472 complete: false,
473 },
474 0,
475 );
476
477 let events: [TestEvent; 3] = [TestEvent { value: 1 }, TestEvent { value: 3 }, TestEvent { value: 2 }];
478
479 for (i, event) in events.into_iter().enumerate() {
480 sim.schedule(event, 2 * i as i32).unwrap();
481 }
482 sim
483 }
484
485 #[test]
486 fn execution_time_ascends() {
487 let mut sim = setup();
488 sim.run().expect("simulation should run to completion");
489
490 assert_eq!(
491 vec![1, 3, 2],
492 sim.state().executed_event_values,
493 "events did not execute in expected order"
494 );
495 }
496
497 #[test]
498 fn schedule_fails_if_given_invalid_execution_time() {
499 let sim = setup();
500 let result = sim.schedule(TestEvent { value: 0 }, -1);
501 assert!(result.is_err(), "sim failed to reject event scheduled for the past");
502 assert_eq!(
503 crate::Error::BackInTime,
504 result.err().unwrap(),
505 "sim returned unexpected error type"
506 );
507 }
508
509 #[test]
510 fn unsafe_schedulers_allow_time_to_reverse() {
511 let mut sim = setup();
512 unsafe {
513 sim.schedule_unchecked(TestEvent { value: 1 }, -1);
514 }
515 sim.next_event().expect("event queue should yield a scheduled event");
516 assert_eq!(
517 -1,
518 *sim.current_time(),
519 "current time did not update when popping event"
520 );
521 }
522
523 #[test]
524 fn insertion_sequence_breaks_ties_in_execution_time() {
525 const NUM_EVENTS: i32 = 10;
526 let state = State {
527 executed_event_values: Vec::with_capacity(NUM_EVENTS as usize),
528 complete: false,
529 };
530 let mut sim = Simulation::new(state, 0);
531
532 for copy_id in 0..NUM_EVENTS {
533 sim.schedule(TestEvent { value: copy_id }, 1)
534 .expect("failed to schedule event");
535 }
536 while let Some(mut event) = sim.next_event() {
537 event.execute(&mut sim).expect("failed to execute event");
538 }
539
540 let expected: Vec<_> = (0..NUM_EVENTS).collect();
541 assert_eq!(
542 expected,
543 sim.state().executed_event_values,
544 "events executed out of insertion sequence"
545 );
546 }
547
548 #[test]
549 fn simulation_executes_events() {
550 let mut sim = setup();
551 sim.run().unwrap();
552
553 let expected = vec![1, 3, 2];
554 assert_eq!(
555 expected, sim.state.executed_event_values,
556 "events did not execute in correct order"
557 );
558 }
559
560 #[test]
561 fn simulation_stops_with_events_still_in_queue() {
562 let mut sim = setup();
563 sim.schedule_from_boxed(Box::new(CompletionEvent {}), 3).unwrap();
564 sim.run().unwrap();
565
566 let expected = vec![1, 3];
567 assert_eq!(
568 expected, sim.state.executed_event_values,
569 "simulation did not terminate with completion event"
570 );
571 }
572
573 #[test]
574 fn delay_schedulers_choose_expected_times() {
575 let state = State {
576 executed_event_values: Vec::with_capacity(3),
577 complete: false,
578 };
579 let mut sim = Simulation::new(state, 0);
580 sim.schedule(TestEvent { value: 1 }, 1).unwrap();
581 sim.schedule(TestEvent { value: 2 }, 3).unwrap();
582
583 let mut first_event = sim.next_event().expect("should be able to pop scheduled event");
584 first_event.execute(&mut sim).expect("event should execute normally");
585 assert_eq!(1, *sim.current_time(), "queue should be at time of last popped event");
586 assert_eq!(
587 vec![1],
588 sim.state().executed_event_values,
589 "state should match first executed event"
590 );
591
592 sim.schedule_now(TestEvent { value: 3 })
593 .expect("should be able to schedule new event");
594 sim.schedule_with_delay(TestEvent { value: 4 }, 1)
595 .expect("should be able to schedule new event");
596
597 let mut next_event = sim.next_event().expect("should be able to pop scheduled event");
598 next_event.execute(&mut sim).expect("event should execute normally");
599 assert_eq!(1, *sim.current_time(), "queue should be at time of last popped event");
600 assert_eq!(
601 vec![1, 3],
602 sim.state().executed_event_values,
603 "state should match first executed event"
604 );
605
606 next_event = sim.next_event().expect("should be able to pop scheduled event");
607 next_event.execute(&mut sim).expect("event should execute normally");
608 assert_eq!(2, *sim.current_time(), "queue should be at time of last popped event");
609 assert_eq!(
610 vec![1, 3, 4],
611 sim.state().executed_event_values,
612 "state should match first executed event"
613 );
614 }
615}