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}