ixa/
context.rs

1//! A manager for the state of a discrete-event simulation
2//!
3//! Defines a [`Context`] that is intended to provide the foundational mechanism
4//! for storing and manipulating the state of a given simulation.
5use std::any::{Any, TypeId};
6use std::cell::OnceCell;
7use std::collections::VecDeque;
8use std::fmt::{Display, Formatter};
9use std::rc::Rc;
10
11use crate::data_plugin::DataPlugin;
12use crate::entity::entity_store::EntityStore;
13use crate::entity::property::Property;
14use crate::entity::property_value_store_core::PropertyValueStoreCore;
15use crate::entity::Entity;
16use crate::execution_stats::{
17    log_execution_statistics, print_execution_statistics, ExecutionProfilingCollector,
18    ExecutionStatistics,
19};
20use crate::plan::{PlanId, Queue};
21#[cfg(feature = "progress_bar")]
22use crate::progress::update_timeline_progress;
23#[cfg(feature = "debugger")]
24use crate::{debugger::enter_debugger, plan::PlanSchedule};
25use crate::{get_data_plugin_count, trace, warn, HashMap, HashMapExt};
26
27/// The common callback used by multiple [`Context`] methods for future events
28type Callback = dyn FnOnce(&mut Context);
29
30/// A handler for an event type `E`
31type EventHandler<E> = dyn Fn(&mut Context, E);
32
33pub trait IxaEvent {
34    /// Called every time `context.subscribe_to_event` is called with this event
35    fn on_subscribe(_context: &mut Context) {}
36}
37
38/// An enum to indicate the phase for plans at a given time.
39///
40/// Most plans will occur as `Normal`. Plans with phase `First` are
41/// handled before all `Normal` plans, and those with phase `Last` are
42/// handled after all `Normal` plans. In all cases ties between plans at the
43/// same time and with the same phase are handled in the order of scheduling.
44///
45#[derive(PartialEq, Eq, Ord, Clone, Copy, PartialOrd, Hash, Debug)]
46pub enum ExecutionPhase {
47    First,
48    Normal,
49    Last,
50}
51
52impl Display for ExecutionPhase {
53    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
54        write!(f, "{self:?}")
55    }
56}
57
58/// A manager for the state of a discrete-event simulation
59///
60/// Provides core simulation services including
61/// * Maintaining a notion of time
62/// * Scheduling events to occur at some point in the future and executing them
63///   at that time
64/// * Holding data that can be accessed by simulation modules
65///
66/// Simulations are constructed out of a series of interacting modules that
67/// take turns manipulating the [`Context`] through a mutable reference. Modules
68/// store data in the simulation using the [`DataPlugin`] trait that allows them
69/// to retrieve data by type.
70///
71/// The future event list of the simulation is a queue of `Callback` objects -
72/// called `plans` - that will assume control of the [`Context`] at a future point
73/// in time and execute the logic in the associated `FnOnce(&mut Context)`
74/// closure. Modules can add plans to this queue through the [`Context`].
75///
76/// The simulation also has a separate callback mechanism. Callbacks
77/// fire before the next timed event (even if it is scheduled for the
78/// current time). This allows modules to schedule actions for immediate
79/// execution but outside of the current iteration of the event loop.
80///
81/// Modules can also emit 'events' that other modules can subscribe to handle by
82/// event type. This allows modules to broadcast that specific things have
83/// occurred and have other modules take turns reacting to these occurrences.
84///
85pub struct Context {
86    plan_queue: Queue<Box<Callback>, ExecutionPhase>,
87    callback_queue: VecDeque<Box<Callback>>,
88    event_handlers: HashMap<TypeId, Box<dyn Any>>,
89    pub(crate) entity_store: EntityStore,
90    data_plugins: Vec<OnceCell<Box<dyn Any>>>,
91    #[cfg(feature = "debugger")]
92    breakpoints_scheduled: Queue<Box<Callback>, ExecutionPhase>,
93    current_time: Option<f64>,
94    start_time: Option<f64>,
95    shutdown_requested: bool,
96    #[cfg(feature = "debugger")]
97    break_requested: bool,
98    #[cfg(feature = "debugger")]
99    breakpoints_enabled: bool,
100    execution_profiler: ExecutionProfilingCollector,
101    pub(crate) print_execution_statistics: bool,
102}
103
104impl Context {
105    /// Create a new empty `Context`
106    #[must_use]
107    pub fn new() -> Context {
108        // Create a vector to accommodate all registered data plugins
109        let data_plugins = std::iter::repeat_with(OnceCell::new)
110            .take(get_data_plugin_count())
111            .collect();
112
113        Context {
114            plan_queue: Queue::new(),
115            callback_queue: VecDeque::new(),
116            event_handlers: HashMap::new(),
117            entity_store: EntityStore::new(),
118            data_plugins,
119            #[cfg(feature = "debugger")]
120            breakpoints_scheduled: Queue::new(),
121            current_time: None,
122            start_time: None,
123            shutdown_requested: false,
124            #[cfg(feature = "debugger")]
125            break_requested: false,
126            #[cfg(feature = "debugger")]
127            breakpoints_enabled: true,
128            execution_profiler: ExecutionProfilingCollector::new(),
129            print_execution_statistics: false,
130        }
131    }
132
133    pub(crate) fn get_property_value_store<E: Entity, P: Property<E>>(
134        &self,
135    ) -> &PropertyValueStoreCore<E, P> {
136        self.entity_store.get_property_store::<E>().get::<P>()
137    }
138    pub(crate) fn get_property_value_store_mut<E: Entity, P: Property<E>>(
139        &mut self,
140    ) -> &mut PropertyValueStoreCore<E, P> {
141        self.entity_store
142            .get_property_store_mut::<E>()
143            .get_mut::<P>()
144    }
145
146    /// Schedule the simulation to pause at time t and start the debugger.
147    /// This will give you a REPL which allows you to inspect the state of
148    /// the simulation (type help to see a list of commands)
149    ///
150    /// # Errors
151    /// Internal debugger errors e.g., reading or writing to stdin/stdout;
152    /// errors in Ixa are printed to stdout
153    #[cfg(feature = "debugger")]
154    pub fn schedule_debugger(
155        &mut self,
156        time: f64,
157        priority: Option<ExecutionPhase>,
158        callback: Box<Callback>,
159    ) {
160        trace!("scheduling debugger");
161        let priority = priority.unwrap_or(ExecutionPhase::First);
162        self.breakpoints_scheduled
163            .add_plan(time, callback, priority);
164    }
165
166    /// Register to handle emission of events of type E
167    ///
168    /// Handlers will be called upon event emission in order of subscription as
169    /// queued `Callback`s with the appropriate event.
170    #[allow(clippy::missing_panics_doc)]
171    pub fn subscribe_to_event<E: IxaEvent + Copy + 'static>(
172        &mut self,
173        handler: impl Fn(&mut Context, E) + 'static,
174    ) {
175        let handler_vec = self
176            .event_handlers
177            .entry(TypeId::of::<E>())
178            .or_insert_with(|| Box::<Vec<Rc<EventHandler<E>>>>::default());
179        let handler_vec: &mut Vec<Rc<EventHandler<E>>> = handler_vec.downcast_mut().unwrap();
180        handler_vec.push(Rc::new(handler));
181        E::on_subscribe(self);
182    }
183
184    /// Emit an event of type E to be handled by registered receivers
185    ///
186    /// Receivers will handle events in the order that they have subscribed and
187    /// are queued as callbacks
188    #[allow(clippy::missing_panics_doc)]
189    pub fn emit_event<E: IxaEvent + Copy + 'static>(&mut self, event: E) {
190        // Destructure to obtain event handlers and plan queue
191        let Context {
192            event_handlers,
193            callback_queue,
194            ..
195        } = self;
196        if let Some(handler_vec) = event_handlers.get(&TypeId::of::<E>()) {
197            let handler_vec: &Vec<Rc<EventHandler<E>>> = handler_vec.downcast_ref().unwrap();
198            for handler in handler_vec {
199                let handler_clone = Rc::clone(handler);
200                callback_queue.push_back(Box::new(move |context| handler_clone(context, event)));
201            }
202        }
203    }
204
205    /// Add a plan to the future event list at the specified time in the normal
206    /// phase
207    ///
208    /// Returns a [`PlanId`] for the newly-added plan that can be used to cancel it
209    /// if needed.
210    /// # Panics
211    ///
212    /// Panics if time is in the past, infinite, or NaN.
213    pub fn add_plan(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static) -> PlanId {
214        self.add_plan_with_phase(time, callback, ExecutionPhase::Normal)
215    }
216
217    /// Add a plan to the future event list at the specified time and with the
218    /// specified phase (first, normal, or last among plans at the
219    /// specified time)
220    ///
221    /// Returns a [`PlanId`] for the newly-added plan that can be used to cancel it
222    /// if needed.
223    /// # Panics
224    ///
225    /// Panics if time is in the past, infinite, or NaN.
226    pub fn add_plan_with_phase(
227        &mut self,
228        time: f64,
229        callback: impl FnOnce(&mut Context) + 'static,
230        phase: ExecutionPhase,
231    ) -> PlanId {
232        let current = self.get_current_time();
233        assert!(!time.is_nan(), "Time {time} is invalid: cannot be NaN");
234        assert!(
235            !time.is_infinite(),
236            "Time {time} is invalid: cannot be infinite"
237        );
238        assert!(
239            time >= current,
240            "Time {time} is invalid: cannot be less than the current time ({}). Consider calling set_start_time() before scheduling plans.",
241            current
242        );
243        self.plan_queue.add_plan(time, Box::new(callback), phase)
244    }
245
246    fn evaluate_periodic_and_schedule_next(
247        &mut self,
248        period: f64,
249        callback: impl Fn(&mut Context) + 'static,
250        phase: ExecutionPhase,
251    ) {
252        trace!(
253            "evaluate periodic at {} (period={})",
254            self.get_current_time(),
255            period
256        );
257        callback(self);
258        if !self.plan_queue.is_empty() {
259            let next_time = self.get_current_time() + period;
260            self.add_plan_with_phase(
261                next_time,
262                move |context| context.evaluate_periodic_and_schedule_next(period, callback, phase),
263                phase,
264            );
265        }
266    }
267
268    /// Add a plan with specified priority to the future event list, and
269    /// continuously repeat the plan at the specified period, stopping
270    /// only once there are no other plans scheduled.
271    ///
272    /// Notes:
273    /// * The first periodic plan is scheduled at time `0.0`. If `set_start_time` was
274    ///   set to a positive value, this will currently panic because the first plan
275    ///   occurs before the start time (see issue #634 for future behavior).
276    ///
277    /// # Panics
278    ///
279    /// Panics if plan period is negative, infinite, or NaN.
280    pub fn add_periodic_plan_with_phase(
281        &mut self,
282        period: f64,
283        callback: impl Fn(&mut Context) + 'static,
284        phase: ExecutionPhase,
285    ) {
286        assert!(
287            period > 0.0 && !period.is_nan() && !period.is_infinite(),
288            "Period must be greater than 0"
289        );
290
291        self.add_plan_with_phase(
292            0.0,
293            move |context| context.evaluate_periodic_and_schedule_next(period, callback, phase),
294            phase,
295        );
296    }
297
298    /// Cancel a plan that has been added to the queue
299    ///
300    /// # Panics
301    ///
302    /// This function panics if you cancel a plan which has already been
303    /// cancelled or executed.
304    pub fn cancel_plan(&mut self, plan_id: &PlanId) {
305        trace!("canceling plan {plan_id:?}");
306        let result = self.plan_queue.cancel_plan(plan_id);
307        if result.is_none() {
308            warn!("Tried to cancel nonexistent plan with ID = {plan_id:?}");
309        }
310    }
311
312    #[doc(hidden)]
313    #[allow(dead_code)]
314    pub(crate) fn remaining_plan_count(&self) -> usize {
315        self.plan_queue.remaining_plan_count()
316    }
317
318    /// Add a `Callback` to the queue to be executed before the next plan
319    pub fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static) {
320        trace!("queuing callback");
321        self.callback_queue.push_back(Box::new(callback));
322    }
323
324    /// Retrieve a mutable reference to the data container associated with a
325    /// [`DataPlugin`]
326    ///
327    /// If the data container has not been already added to the [`Context`] then
328    /// this function will use the [`DataPlugin::init`] method
329    /// to construct a new data container and store it in the [`Context`].
330    ///
331    /// Returns a mutable reference to the data container
332    #[must_use]
333    #[allow(clippy::needless_pass_by_value)]
334    pub fn get_data_mut<T: DataPlugin>(&mut self, _data_plugin: T) -> &mut T::DataContainer {
335        let index = T::index_within_context();
336
337        // If the data plugin is already initialized, return a mutable reference.
338        if self.data_plugins[index].get().is_some() {
339            return self.data_plugins[index]
340                .get_mut()
341                .unwrap()
342                .downcast_mut::<T::DataContainer>()
343                .expect("TypeID does not match data plugin type");
344        }
345
346        // Initialize the data plugin if not already initialized.
347        let data = T::init(self);
348        let cell = self
349            .data_plugins
350            .get_mut(index)
351            .unwrap_or_else(|| panic!("No data plugin found with index = {index:?}. You must use the `define_data_plugin!` macro to create a data plugin."));
352        let _ = cell.set(Box::new(data));
353        cell.get_mut()
354            .unwrap()
355            .downcast_mut::<T::DataContainer>()
356            .expect("TypeID does not match data plugin type. You must use the `define_data_plugin!` macro to create a data plugin.")
357    }
358
359    /// Retrieve a reference to the data container associated with a
360    /// [`DataPlugin`]
361    ///
362    /// Returns a reference to the data container if it exists or else `None`
363    #[must_use]
364    #[allow(clippy::needless_pass_by_value)]
365    pub fn get_data<T: DataPlugin>(&self, _data_plugin: T) -> &T::DataContainer {
366        let index = T::index_within_context();
367        self.data_plugins
368            .get(index)
369            .unwrap_or_else(|| panic!("No data plugin found with index = {index:?}. You must use the `define_data_plugin!` macro to create a data plugin."))
370            .get_or_init(|| Box::new(T::init(self)))
371            .downcast_ref::<T::DataContainer>()
372            .expect("TypeID does not match data plugin type. You must use the `define_data_plugin!` macro to create a data plugin.")
373    }
374
375    /// Shutdown the simulation cleanly, abandoning all events after whatever
376    /// is currently executing.
377    pub fn shutdown(&mut self) {
378        trace!("shutdown context");
379        self.shutdown_requested = true;
380    }
381
382    /// Get the current simulation time
383    ///
384    /// Returns the current time in the simulation. The behavior depends on execution state:
385    /// * During execution: returns the time of the currently executing plan or callback
386    /// * Before execution: returns the start time (if set via [`Context::set_start_time`]), or `0.0`
387    ///
388    /// The time can be negative if a negative start time was set before execution.
389    #[must_use]
390    pub fn get_current_time(&self) -> f64 {
391        self.current_time.or(self.start_time).unwrap_or(0.0)
392    }
393
394    /// Set the start time for the simulation. Must be finite.
395    ///
396    /// * Call before `Context.execute()`.
397    /// * `start_time` must be finite (not NaN or infinite).
398    /// * May be called only once.
399    /// * If plans are already scheduled, `start_time` must be earlier than or equal to
400    ///   the earliest scheduled plan time.
401    ///
402    /// # Panics
403    ///
404    /// Panics if:
405    /// * `start_time` is NaN or infinite.
406    /// * the start time was already set.
407    /// * `Context::execute()` has been called.
408    /// * `start_time` is later than the earliest scheduled plan time.
409    pub fn set_start_time(&mut self, start_time: f64) {
410        assert!(
411            !start_time.is_nan() && !start_time.is_infinite(),
412            "Start time {start_time} must be finite"
413        );
414        assert!(
415            self.start_time.is_none(),
416            "Start time has already been set. It can only be set once."
417        );
418        assert!(
419            self.current_time.is_none(),
420            "Start time cannot be set after execution has begun."
421        );
422        if let Some(next_time) = self.plan_queue.next_time() {
423            assert!(
424                start_time <= next_time,
425                "Start time {} is later than the earliest scheduled plan time {}. Remove or reschedule existing plans first.",
426                start_time,
427                next_time
428            );
429        }
430        self.start_time = Some(start_time);
431    }
432
433    /// Get the start time that was set via `set_start_time`, or `None` if not set.
434    #[must_use]
435    pub fn get_start_time(&self) -> Option<f64> {
436        self.start_time
437    }
438
439    /// Request to enter a debugger session at next event loop
440    #[cfg(feature = "debugger")]
441    pub fn request_debugger(&mut self) {
442        self.break_requested = true;
443    }
444
445    /// Request to enter a debugger session at next event loop
446    #[cfg(feature = "debugger")]
447    pub fn cancel_debugger_request(&mut self) {
448        self.break_requested = false;
449    }
450
451    /// Disable breakpoints
452    #[cfg(feature = "debugger")]
453    pub fn disable_breakpoints(&mut self) {
454        self.breakpoints_enabled = false;
455    }
456
457    /// Enable breakpoints
458    #[cfg(feature = "debugger")]
459    pub fn enable_breakpoints(&mut self) {
460        self.breakpoints_enabled = true;
461    }
462
463    /// Returns `true` if breakpoints are enabled.
464    #[must_use]
465    #[cfg(feature = "debugger")]
466    pub fn breakpoints_are_enabled(&self) -> bool {
467        self.breakpoints_enabled
468    }
469
470    /// Delete the breakpoint with the given ID
471    #[cfg(feature = "debugger")]
472    pub fn delete_breakpoint(&mut self, breakpoint_id: u64) -> Option<Box<Callback>> {
473        self.breakpoints_scheduled
474            .cancel_plan(&PlanId(breakpoint_id))
475    }
476
477    /// Returns a list of length `at_most`, or unbounded if `at_most=0`, of active scheduled
478    /// `PlanSchedule`s ordered as they are in the queue itself.
479    #[must_use]
480    #[cfg(feature = "debugger")]
481    pub fn list_breakpoints(&self, at_most: usize) -> Vec<&PlanSchedule<ExecutionPhase>> {
482        self.breakpoints_scheduled.list_schedules(at_most)
483    }
484
485    /// Deletes all breakpoints.
486    #[cfg(feature = "debugger")]
487    pub fn clear_breakpoints(&mut self) {
488        self.breakpoints_scheduled.clear();
489    }
490
491    /// Execute the simulation until the plan and callback queues are empty
492    pub fn execute(&mut self) {
493        trace!("entering event loop");
494
495        if self.current_time.is_none() {
496            self.current_time = Some(self.start_time.unwrap_or(0.0));
497        }
498
499        // Start plan loop
500        loop {
501            #[cfg(feature = "progress_bar")]
502            if crate::progress::MAX_TIME.get().is_some() {
503                update_timeline_progress(self.get_current_time());
504            }
505
506            #[cfg(feature = "debugger")]
507            if self.break_requested {
508                enter_debugger(self);
509            } else if self.shutdown_requested {
510                self.shutdown_requested = false;
511                break;
512            } else {
513                self.execute_single_step();
514            }
515
516            self.execution_profiler.refresh();
517
518            #[cfg(not(feature = "debugger"))]
519            if self.shutdown_requested {
520                self.shutdown_requested = false;
521                break;
522            } else {
523                self.execute_single_step();
524            }
525        }
526
527        let stats = self.get_execution_statistics();
528        if self.print_execution_statistics {
529            print_execution_statistics(&stats);
530            #[cfg(feature = "profiling")]
531            crate::profiling::print_profiling_data();
532        } else {
533            log_execution_statistics(&stats);
534        }
535    }
536
537    /// Executes a single step of the simulation, prioritizing tasks as follows:
538    ///   1. Breakpoints
539    ///   2. Callbacks
540    ///   3. Plans
541    ///   4. Shutdown
542    pub fn execute_single_step(&mut self) {
543        // This always runs the breakpoint before anything scheduled in the task queue regardless
544        // of the `ExecutionPhase` of the breakpoint. If breakpoints are disabled, they are still
545        // popped from the breakpoint queue at the time they are scheduled even though they are not
546        // executed.
547        #[cfg(feature = "debugger")]
548        if let Some((bp, _)) = self.breakpoints_scheduled.peek() {
549            // If the priority of bp is `ExecutionPhase::First`, and if the next scheduled plan
550            // is scheduled at or after bp's time (or doesn't exist), run bp.
551            // If the priority of bp is `ExecutionPhase::Last`, and if the next scheduled plan
552            // is scheduled strictly after bp's time (or doesn't exist), run bp.
553            if let Some(plan_time) = self.plan_queue.next_time() {
554                if (bp.priority == ExecutionPhase::First && bp.time <= plan_time)
555                    || (bp.priority == ExecutionPhase::Last && bp.time < plan_time)
556                {
557                    self.breakpoints_scheduled.get_next_plan(); // Pop the breakpoint
558                    if self.breakpoints_enabled {
559                        self.break_requested = true;
560                        return;
561                    }
562                }
563            } else {
564                self.breakpoints_scheduled.get_next_plan(); // Pop the breakpoint
565                if self.breakpoints_enabled {
566                    self.break_requested = true;
567                    return;
568                }
569            }
570        }
571
572        // If there is a callback, run it.
573        if let Some(callback) = self.callback_queue.pop_front() {
574            trace!("calling callback");
575            callback(self);
576        }
577        // There aren't any callbacks, so look at the first plan.
578        else if let Some(plan) = self.plan_queue.get_next_plan() {
579            trace!("calling plan at {:.6}", plan.time);
580            self.current_time = Some(plan.time);
581            (plan.data)(self);
582        } else {
583            trace!("No callbacks or plans; exiting event loop");
584            // OK, there aren't any plans, so we're done.
585            self.shutdown_requested = true;
586        }
587    }
588
589    pub fn get_execution_statistics(&mut self) -> ExecutionStatistics {
590        self.execution_profiler.compute_final_statistics()
591    }
592}
593
594pub trait ContextBase: Sized {
595    fn subscribe_to_event<E: IxaEvent + Copy + 'static>(
596        &mut self,
597        handler: impl Fn(&mut Context, E) + 'static,
598    );
599    fn emit_event<E: IxaEvent + Copy + 'static>(&mut self, event: E);
600    fn add_plan(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static) -> PlanId;
601    fn add_plan_with_phase(
602        &mut self,
603        time: f64,
604        callback: impl FnOnce(&mut Context) + 'static,
605        phase: ExecutionPhase,
606    ) -> PlanId;
607    fn add_periodic_plan_with_phase(
608        &mut self,
609        period: f64,
610        callback: impl Fn(&mut Context) + 'static,
611        phase: ExecutionPhase,
612    );
613    fn cancel_plan(&mut self, plan_id: &PlanId);
614    fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static);
615    fn get_data_mut<T: DataPlugin>(&mut self, plugin: T) -> &mut T::DataContainer;
616    fn get_data<T: DataPlugin>(&self, plugin: T) -> &T::DataContainer;
617    fn get_current_time(&self) -> f64;
618    fn get_execution_statistics(&mut self) -> ExecutionStatistics;
619}
620impl ContextBase for Context {
621    delegate::delegate! {
622        to self {
623            fn subscribe_to_event<E: IxaEvent + Copy + 'static>(&mut self, handler: impl Fn(&mut Context, E) + 'static);
624            fn emit_event<E: IxaEvent + Copy + 'static>(&mut self, event: E);
625            fn add_plan(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static) -> PlanId;
626            fn add_plan_with_phase(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static, phase: ExecutionPhase) -> PlanId;
627            fn add_periodic_plan_with_phase(&mut self, period: f64, callback: impl Fn(&mut Context) + 'static, phase: ExecutionPhase);
628            fn cancel_plan(&mut self, plan_id: &PlanId);
629            fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static);
630            fn get_data_mut<T: DataPlugin>(&mut self, plugin: T) -> &mut T::DataContainer;
631            fn get_data<T: DataPlugin>(&self, plugin: T) -> &T::DataContainer;
632            fn get_current_time(&self) -> f64;
633            fn get_execution_statistics(&mut self) -> ExecutionStatistics;
634        }
635    }
636}
637
638impl Default for Context {
639    fn default() -> Self {
640        Self::new()
641    }
642}
643
644#[cfg(test)]
645mod tests {
646    // We allow defining items that are never used to test macros.
647    #![allow(dead_code)]
648    use std::cell::RefCell;
649
650    use ixa_derive::IxaEvent;
651
652    use super::*;
653    use crate::{define_data_plugin, define_entity, define_property, ContextEntitiesExt};
654
655    define_data_plugin!(ComponentA, Vec<u32>, vec![]);
656
657    define_entity!(Person);
658
659    define_property!(struct Age(u8), Person);
660
661    define_property!(
662        enum InfectionStatus {
663            Susceptible,
664            Infected,
665            Recovered,
666        },
667        Person,
668        default_const = InfectionStatus::Susceptible
669    );
670
671    define_property!(
672        struct Vaccinated(bool),
673        Person,
674        default_const = Vaccinated(false)
675    );
676
677    #[test]
678    fn empty_context() {
679        let mut context = Context::new();
680        context.execute();
681        assert_eq!(context.get_current_time(), 0.0);
682    }
683
684    #[test]
685    fn get_data() {
686        let mut context = Context::new();
687        context.get_data_mut(ComponentA).push(1);
688        assert_eq!(*context.get_data(ComponentA), vec![1],);
689    }
690
691    fn add_plan(context: &mut Context, time: f64, value: u32) -> PlanId {
692        context.add_plan(time, move |context| {
693            context.get_data_mut(ComponentA).push(value);
694        })
695    }
696
697    fn add_plan_with_phase(
698        context: &mut Context,
699        time: f64,
700        value: u32,
701        phase: ExecutionPhase,
702    ) -> PlanId {
703        context.add_plan_with_phase(
704            time,
705            move |context| {
706                context.get_data_mut(ComponentA).push(value);
707            },
708            phase,
709        )
710    }
711
712    #[test]
713    #[should_panic(expected = "Time inf is invalid")]
714    fn infinite_plan_time() {
715        let mut context = Context::new();
716        add_plan(&mut context, f64::INFINITY, 0);
717    }
718
719    #[test]
720    #[should_panic(expected = "Time NaN is invalid")]
721    fn nan_plan_time() {
722        let mut context = Context::new();
723        add_plan(&mut context, f64::NAN, 0);
724    }
725
726    #[test]
727    fn timed_plan_only() {
728        let mut context = Context::new();
729        add_plan(&mut context, 1.0, 1);
730        context.execute();
731        assert_eq!(context.get_current_time(), 1.0);
732        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
733    }
734
735    #[test]
736    fn callback_only() {
737        let mut context = Context::new();
738        context.queue_callback(|context| {
739            context.get_data_mut(ComponentA).push(1);
740        });
741        context.execute();
742        assert_eq!(context.get_current_time(), 0.0);
743        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
744    }
745
746    #[test]
747    fn callback_before_timed_plan() {
748        let mut context = Context::new();
749        context.queue_callback(|context| {
750            context.get_data_mut(ComponentA).push(1);
751        });
752        add_plan(&mut context, 1.0, 2);
753        context.execute();
754        assert_eq!(context.get_current_time(), 1.0);
755        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
756    }
757
758    #[test]
759    fn callback_adds_timed_plan() {
760        let mut context = Context::new();
761        context.queue_callback(|context| {
762            context.get_data_mut(ComponentA).push(1);
763            add_plan(context, 1.0, 2);
764            context.get_data_mut(ComponentA).push(3);
765        });
766        context.execute();
767        assert_eq!(context.get_current_time(), 1.0);
768        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 2]);
769    }
770
771    #[test]
772    fn callback_adds_callback_and_timed_plan() {
773        let mut context = Context::new();
774        context.queue_callback(|context| {
775            context.get_data_mut(ComponentA).push(1);
776            add_plan(context, 1.0, 2);
777            context.queue_callback(|context| {
778                context.get_data_mut(ComponentA).push(4);
779            });
780            context.get_data_mut(ComponentA).push(3);
781        });
782        context.execute();
783        assert_eq!(context.get_current_time(), 1.0);
784        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 4, 2]);
785    }
786
787    #[test]
788    fn timed_plan_adds_callback_and_timed_plan() {
789        let mut context = Context::new();
790        context.add_plan(1.0, |context| {
791            context.get_data_mut(ComponentA).push(1);
792            // We add the plan first, but the callback will fire first.
793            add_plan(context, 2.0, 3);
794            context.queue_callback(|context| {
795                context.get_data_mut(ComponentA).push(2);
796            });
797        });
798        context.execute();
799        assert_eq!(context.get_current_time(), 2.0);
800        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
801    }
802
803    #[test]
804    fn cancel_plan() {
805        let mut context = Context::new();
806        let to_cancel = add_plan(&mut context, 2.0, 1);
807        context.add_plan(1.0, move |context| {
808            context.cancel_plan(&to_cancel);
809        });
810        context.execute();
811        assert_eq!(context.get_current_time(), 1.0);
812        let test_vec: Vec<u32> = vec![];
813        assert_eq!(*context.get_data_mut(ComponentA), test_vec);
814    }
815
816    #[test]
817    fn add_plan_with_current_time() {
818        let mut context = Context::new();
819        context.add_plan(1.0, move |context| {
820            context.get_data_mut(ComponentA).push(1);
821            add_plan(context, 1.0, 2);
822            context.queue_callback(|context| {
823                context.get_data_mut(ComponentA).push(3);
824            });
825        });
826        context.execute();
827        assert_eq!(context.get_current_time(), 1.0);
828        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 2]);
829    }
830
831    #[test]
832    fn plans_at_same_time_fire_in_order() {
833        let mut context = Context::new();
834        add_plan(&mut context, 1.0, 1);
835        add_plan(&mut context, 1.0, 2);
836        context.execute();
837        assert_eq!(context.get_current_time(), 1.0);
838        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
839    }
840
841    #[test]
842    fn check_plan_phase_ordering() {
843        assert!(ExecutionPhase::First < ExecutionPhase::Normal);
844        assert!(ExecutionPhase::Normal < ExecutionPhase::Last);
845    }
846
847    #[test]
848    fn plans_at_same_time_follow_phase() {
849        let mut context = Context::new();
850        add_plan(&mut context, 1.0, 1);
851        add_plan_with_phase(&mut context, 1.0, 5, ExecutionPhase::Last);
852        add_plan_with_phase(&mut context, 1.0, 3, ExecutionPhase::First);
853        add_plan(&mut context, 1.0, 2);
854        add_plan_with_phase(&mut context, 1.0, 6, ExecutionPhase::Last);
855        add_plan_with_phase(&mut context, 1.0, 4, ExecutionPhase::First);
856        context.execute();
857        assert_eq!(context.get_current_time(), 1.0);
858        assert_eq!(*context.get_data_mut(ComponentA), vec![3, 4, 1, 2, 5, 6]);
859    }
860
861    #[derive(Copy, Clone, IxaEvent)]
862    struct Event1 {
863        pub data: usize,
864    }
865
866    #[derive(Copy, Clone, IxaEvent)]
867    struct Event2 {
868        pub data: usize,
869    }
870
871    #[test]
872    fn simple_event() {
873        let mut context = Context::new();
874        let obs_data = Rc::new(RefCell::new(0));
875        let obs_data_clone = Rc::clone(&obs_data);
876
877        context.subscribe_to_event::<Event1>(move |_, event| {
878            *obs_data_clone.borrow_mut() = event.data;
879        });
880
881        context.emit_event(Event1 { data: 1 });
882        context.execute();
883        assert_eq!(*obs_data.borrow(), 1);
884    }
885
886    #[test]
887    fn multiple_events() {
888        let mut context = Context::new();
889        let obs_data = Rc::new(RefCell::new(0));
890        let obs_data_clone = Rc::clone(&obs_data);
891
892        context.subscribe_to_event::<Event1>(move |_, event| {
893            *obs_data_clone.borrow_mut() += event.data;
894        });
895
896        context.emit_event(Event1 { data: 1 });
897        context.emit_event(Event1 { data: 2 });
898        context.execute();
899
900        // Both of these should have been received.
901        assert_eq!(*obs_data.borrow(), 3);
902    }
903
904    #[test]
905    fn multiple_event_handlers() {
906        let mut context = Context::new();
907        let obs_data1 = Rc::new(RefCell::new(0));
908        let obs_data1_clone = Rc::clone(&obs_data1);
909        let obs_data2 = Rc::new(RefCell::new(0));
910        let obs_data2_clone = Rc::clone(&obs_data2);
911
912        context.subscribe_to_event::<Event1>(move |_, event| {
913            *obs_data1_clone.borrow_mut() = event.data;
914        });
915        context.subscribe_to_event::<Event1>(move |_, event| {
916            *obs_data2_clone.borrow_mut() = event.data;
917        });
918        context.emit_event(Event1 { data: 1 });
919        context.execute();
920        assert_eq!(*obs_data1.borrow(), 1);
921        assert_eq!(*obs_data2.borrow(), 1);
922    }
923
924    #[test]
925    fn multiple_event_types() {
926        let mut context = Context::new();
927        let obs_data1 = Rc::new(RefCell::new(0));
928        let obs_data1_clone = Rc::clone(&obs_data1);
929        let obs_data2 = Rc::new(RefCell::new(0));
930        let obs_data2_clone = Rc::clone(&obs_data2);
931
932        context.subscribe_to_event::<Event1>(move |_, event| {
933            *obs_data1_clone.borrow_mut() = event.data;
934        });
935        context.subscribe_to_event::<Event2>(move |_, event| {
936            *obs_data2_clone.borrow_mut() = event.data;
937        });
938        context.emit_event(Event1 { data: 1 });
939        context.emit_event(Event2 { data: 2 });
940        context.execute();
941        assert_eq!(*obs_data1.borrow(), 1);
942        assert_eq!(*obs_data2.borrow(), 2);
943    }
944
945    #[test]
946    fn subscribe_after_event() {
947        let mut context = Context::new();
948        let obs_data = Rc::new(RefCell::new(0));
949        let obs_data_clone = Rc::clone(&obs_data);
950
951        context.emit_event(Event1 { data: 1 });
952        context.subscribe_to_event::<Event1>(move |_, event| {
953            *obs_data_clone.borrow_mut() = event.data;
954        });
955
956        context.execute();
957        assert_eq!(*obs_data.borrow(), 0);
958    }
959
960    #[test]
961    fn shutdown_cancels_plans() {
962        let mut context = Context::new();
963        add_plan(&mut context, 1.0, 1);
964        context.add_plan(1.5, Context::shutdown);
965        add_plan(&mut context, 2.0, 2);
966        context.execute();
967        assert_eq!(context.get_current_time(), 1.5);
968        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
969    }
970
971    #[test]
972    fn shutdown_cancels_callbacks() {
973        let mut context = Context::new();
974        add_plan(&mut context, 1.0, 1);
975        context.add_plan(1.5, |context| {
976            // Note that we add the callback *before* we call shutdown
977            // but shutdown cancels everything.
978            context.queue_callback(|context| {
979                context.get_data_mut(ComponentA).push(3);
980            });
981            context.shutdown();
982        });
983        context.execute();
984        assert_eq!(context.get_current_time(), 1.5);
985        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
986    }
987
988    #[test]
989    fn shutdown_cancels_events() {
990        let mut context = Context::new();
991        let obs_data = Rc::new(RefCell::new(0));
992        let obs_data_clone = Rc::clone(&obs_data);
993        context.subscribe_to_event::<Event1>(move |_, event| {
994            *obs_data_clone.borrow_mut() = event.data;
995        });
996        context.emit_event(Event1 { data: 1 });
997        context.shutdown();
998        context.execute();
999        assert_eq!(*obs_data.borrow(), 0);
1000    }
1001
1002    #[test]
1003    #[allow(clippy::cast_sign_loss)]
1004    #[allow(clippy::cast_possible_truncation)]
1005    fn periodic_plan_self_schedules() {
1006        // checks whether the person properties report schedules itself
1007        // based on whether there are plans in the queue
1008        let mut context = Context::new();
1009        context.add_periodic_plan_with_phase(
1010            1.0,
1011            |context| {
1012                let time = context.get_current_time();
1013                context.get_data_mut(ComponentA).push(time as u32);
1014            },
1015            ExecutionPhase::Last,
1016        );
1017        context.add_plan(1.0, move |_context| {});
1018        context.add_plan(1.5, move |_context| {});
1019        context.execute();
1020        assert_eq!(context.get_current_time(), 2.0);
1021
1022        assert_eq!(*context.get_data(ComponentA), vec![0, 1, 2]); // time 0.0, 1.0, and 2.0
1023    }
1024
1025    // Tests for negative time handling
1026
1027    #[test]
1028    fn negative_plan_time_allowed() {
1029        let mut context = Context::new();
1030        context.set_start_time(-1.0);
1031        add_plan(&mut context, -1.0, 1);
1032        context.execute();
1033        assert_eq!(context.get_current_time(), -1.0);
1034        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1035    }
1036
1037    #[test]
1038    fn add_plan_get_current_time() {
1039        let mut context = Context::new();
1040        let current_time = context.get_current_time();
1041        add_plan(&mut context, current_time, 1);
1042        context.execute();
1043        assert_eq!(context.get_current_time(), 0.0);
1044        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1045    }
1046
1047    #[test]
1048    fn multiple_negative_plans() {
1049        let mut context = Context::new();
1050        context.set_start_time(-3.0);
1051        add_plan(&mut context, -3.0, 1);
1052        add_plan(&mut context, -1.0, 3);
1053        add_plan(&mut context, -2.0, 2);
1054        context.execute();
1055        assert_eq!(context.get_current_time(), -1.0);
1056        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1057    }
1058
1059    #[test]
1060    fn negative_and_positive_plans() {
1061        let mut context = Context::new();
1062        context.set_start_time(-1.0);
1063        add_plan(&mut context, -1.0, 1);
1064        add_plan(&mut context, 1.0, 3);
1065        add_plan(&mut context, 0.0, 2);
1066        context.execute();
1067        assert_eq!(context.get_current_time(), 1.0);
1068        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1069    }
1070
1071    #[test]
1072    fn get_current_time_before_execute_defaults() {
1073        let mut context = Context::new();
1074        assert_eq!(context.get_current_time(), 0.0);
1075
1076        context.set_start_time(-2.0);
1077        assert_eq!(context.get_current_time(), -2.0);
1078    }
1079
1080    #[test]
1081    fn get_current_time_initializes_to_zero_when_all_positive() {
1082        let mut context = Context::new();
1083        let seen_time = Rc::new(RefCell::new(f64::NAN));
1084        let seen_time_clone = Rc::clone(&seen_time);
1085        context.queue_callback(move |ctx| {
1086            *seen_time_clone.borrow_mut() = ctx.get_current_time();
1087        });
1088        context.execute();
1089        assert_eq!(*seen_time.borrow(), 0.0);
1090    }
1091
1092    #[test]
1093    fn get_current_time_initializes_to_zero_when_empty() {
1094        let mut context = Context::new();
1095        context.execute();
1096        assert_eq!(context.get_current_time(), 0.0);
1097    }
1098
1099    #[test]
1100    fn get_current_time_initializes_to_zero_with_plan() {
1101        let mut context = Context::new();
1102        add_plan(&mut context, 0.0, 1);
1103        context.execute();
1104        assert_eq!(context.get_current_time(), 0.0);
1105        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1106    }
1107
1108    #[test]
1109    #[should_panic(expected = "Time -1 is invalid")]
1110    fn negative_time_from_callback_panics() {
1111        let mut context = Context::new();
1112        context.queue_callback(|context| {
1113            context.get_data_mut(ComponentA).push(1);
1114            add_plan(context, -1.0, 2);
1115        });
1116        add_plan(&mut context, 1.0, 3);
1117        context.execute();
1118    }
1119
1120    #[test]
1121    fn large_negative_time() {
1122        let mut context = Context::new();
1123        context.set_start_time(-1_000_000.0);
1124        add_plan(&mut context, -1_000_000.0, 1);
1125        context.execute();
1126        assert_eq!(context.get_current_time(), -1_000_000.0);
1127        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1128    }
1129
1130    #[test]
1131    fn very_small_negative_time() {
1132        let mut context = Context::new();
1133        context.set_start_time(-1e-10);
1134        add_plan(&mut context, -1e-10, 1);
1135        context.execute();
1136        assert_eq!(context.get_current_time(), -1e-10);
1137        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1138    }
1139
1140    #[test]
1141    fn negative_time_ordering_with_phases() {
1142        let mut context = Context::new();
1143        context.set_start_time(-1.0);
1144        add_plan_with_phase(&mut context, -1.0, 1, ExecutionPhase::Normal);
1145        add_plan_with_phase(&mut context, -1.0, 3, ExecutionPhase::Last);
1146        add_plan_with_phase(&mut context, -1.0, 2, ExecutionPhase::First);
1147        context.execute();
1148        assert_eq!(context.get_current_time(), -1.0);
1149        assert_eq!(*context.get_data_mut(ComponentA), vec![2, 1, 3]);
1150    }
1151
1152    #[test]
1153    #[should_panic(expected = "Time 4 is invalid")]
1154    fn cannot_schedule_plan_before_current_time() {
1155        let mut context = Context::new();
1156        add_plan(&mut context, 5.0, 1);
1157        context.add_plan(5.0, |context| {
1158            // At time 5.0, we cannot schedule a plan at time 4.0
1159            add_plan(context, 4.0, 2);
1160        });
1161        context.execute();
1162    }
1163
1164    #[test]
1165    fn get_current_time_multiple_calls_before_execute() {
1166        let mut context = Context::new();
1167        context.set_start_time(-2.0);
1168        add_plan(&mut context, -2.0, 1);
1169        context.execute();
1170        assert_eq!(context.get_current_time(), -2.0);
1171    }
1172
1173    #[test]
1174    fn negative_plan_can_add_positive_plan() {
1175        let mut context = Context::new();
1176        context.set_start_time(-1.0);
1177        add_plan(&mut context, -1.0, 1);
1178        context.add_plan(-1.0, |context| {
1179            add_plan(context, 2.0, 2);
1180        });
1181        context.execute();
1182        assert_eq!(context.get_current_time(), 2.0);
1183        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1184    }
1185
1186    #[test]
1187    fn negative_plan_can_schedule_negative_plan() {
1188        let mut context = Context::new();
1189        context.set_start_time(-2.0);
1190        add_plan(&mut context, -2.0, 1);
1191        context.add_plan(-2.0, |context| {
1192            add_plan(context, -1.0, 2);
1193        });
1194        context.execute();
1195        assert_eq!(context.get_current_time(), -1.0);
1196        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1197    }
1198
1199    #[test]
1200    #[should_panic(expected = "Start time has already been set. It can only be set once.")]
1201    fn set_start_time_only_once() {
1202        let mut context = Context::new();
1203        context.set_start_time(1.0);
1204        context.set_start_time(2.0);
1205    }
1206
1207    // Additional coverage around time and plans
1208
1209    #[test]
1210    #[should_panic(expected = "Start time NaN must be finite")]
1211    fn set_start_time_nan_panics() {
1212        let mut context = Context::new();
1213        context.set_start_time(f64::NAN);
1214    }
1215
1216    #[test]
1217    #[should_panic(expected = "Start time inf must be finite")]
1218    fn set_start_time_inf_panics() {
1219        let mut context = Context::new();
1220        context.set_start_time(f64::INFINITY);
1221    }
1222
1223    #[test]
1224    fn set_start_time_equal_to_earliest_plan_allowed() {
1225        let mut context = Context::new();
1226        context.set_start_time(-2.0);
1227        add_plan(&mut context, -2.0, 1);
1228        context.execute();
1229        assert_eq!(context.get_current_time(), -2.0);
1230        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1231    }
1232
1233    // Note: adding a plan earlier than current_time after setting start time
1234    // is already covered by `add_plan_less_than_current_time_panics`.
1235
1236    #[test]
1237    fn set_start_time_with_only_callbacks_keeps_time() {
1238        let mut context = Context::new();
1239        context.set_start_time(5.0);
1240        context.queue_callback(|ctx| {
1241            ctx.get_data_mut(ComponentA).push(42);
1242        });
1243        context.execute();
1244        assert_eq!(context.get_current_time(), 5.0);
1245        assert_eq!(*context.get_data_mut(ComponentA), vec![42]);
1246    }
1247
1248    #[test]
1249    fn multiple_plans_final_time_is_last() {
1250        let mut context = Context::new();
1251        add_plan(&mut context, 1.0, 1);
1252        add_plan(&mut context, 3.0, 3);
1253        add_plan(&mut context, 2.0, 2);
1254        context.execute();
1255        assert_eq!(context.get_current_time(), 3.0);
1256        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1257    }
1258
1259    #[test]
1260    fn add_plan_same_time_fifo_and_phases() {
1261        let mut context = Context::new();
1262        add_plan_with_phase(&mut context, 1.0, 3, ExecutionPhase::Last);
1263        add_plan(&mut context, 1.0, 1);
1264        add_plan_with_phase(&mut context, 1.0, 2, ExecutionPhase::First);
1265        add_plan(&mut context, 1.0, 4);
1266        context.execute();
1267        assert_eq!(context.get_current_time(), 1.0);
1268        assert_eq!(*context.get_data_mut(ComponentA), vec![2, 1, 4, 3]);
1269    }
1270
1271    #[test]
1272    #[should_panic(expected = "Time -2 is invalid")]
1273    fn add_plan_less_than_current_time_panics() {
1274        let mut context = Context::new();
1275        context.set_start_time(-1.0);
1276        add_plan(&mut context, -1.0, 1);
1277        // Attempt to schedule before current time
1278        add_plan(&mut context, -2.0, 2);
1279    }
1280
1281    #[test]
1282    #[should_panic(expected = "Period must be greater than 0")]
1283    fn add_periodic_plan_zero_period_panics() {
1284        let mut context = Context::new();
1285        context.add_periodic_plan_with_phase(0.0, |_ctx| {}, ExecutionPhase::Normal);
1286    }
1287
1288    #[test]
1289    #[should_panic(expected = "Period must be greater than 0")]
1290    fn add_periodic_plan_nan_panics() {
1291        let mut context = Context::new();
1292        context.add_periodic_plan_with_phase(f64::NAN, |_ctx| {}, ExecutionPhase::Normal);
1293    }
1294
1295    #[test]
1296    #[should_panic(expected = "Period must be greater than 0")]
1297    fn add_periodic_plan_inf_panics() {
1298        let mut context = Context::new();
1299        context.add_periodic_plan_with_phase(f64::INFINITY, |_ctx| {}, ExecutionPhase::Normal);
1300    }
1301
1302    #[test]
1303    fn shutdown_requested_reset() {
1304        // This test verifies that shutdown_requested is properly reset after
1305        // being acted upon. This allows the context to be reused after shutdown.
1306        let mut context = Context::new();
1307        let _: PersonId = context.add_entity((Age(50),)).unwrap();
1308
1309        // Schedule a plan at time 0.0 that calls shutdown
1310        context.add_plan(0.0, |ctx| {
1311            ctx.shutdown();
1312        });
1313
1314        // First execute - should run until shutdown
1315        context.execute();
1316        assert_eq!(context.get_current_time(), 0.0);
1317        assert_eq!(context.get_entity_count::<Person>(), 1);
1318
1319        // Add a new plan at time 2.0
1320        context.add_plan(2.0, |ctx| {
1321            let _: PersonId = ctx.add_entity((Age(50),)).unwrap();
1322        });
1323
1324        // Second execute - should execute the new plan
1325        // If shutdown_requested wasn't reset, this would immediately break
1326        // without executing the plan, leaving population at 1.
1327        context.execute();
1328        assert_eq!(context.get_current_time(), 2.0);
1329        assert_eq!(
1330            context.get_entity_count::<Person>(),
1331            2,
1332            "If this fails, shutdown_requested was not properly reset"
1333        );
1334    }
1335}