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::execution_stats::{
13    log_execution_statistics, print_execution_statistics, ExecutionProfilingCollector,
14    ExecutionStatistics,
15};
16use crate::plan::{PlanId, Queue};
17#[cfg(feature = "progress_bar")]
18use crate::progress::update_timeline_progress;
19#[cfg(feature = "debugger")]
20use crate::{debugger::enter_debugger, plan::PlanSchedule};
21use crate::{get_data_plugin_count, trace, warn, ContextPeopleExt, HashMap, HashMapExt};
22
23/// The common callback used by multiple [`Context`] methods for future events
24type Callback = dyn FnOnce(&mut Context);
25
26/// A handler for an event type `E`
27type EventHandler<E> = dyn Fn(&mut Context, E);
28
29pub trait IxaEvent {
30    /// Called every time `context.subscribe_to_event` is called with this event
31    fn on_subscribe(_context: &mut Context) {}
32}
33
34/// An enum to indicate the phase for plans at a given time.
35///
36/// Most plans will occur as `Normal`. Plans with phase `First` are
37/// handled before all `Normal` plans, and those with phase `Last` are
38/// handled after all `Normal` plans. In all cases ties between plans at the
39/// same time and with the same phase are handled in the order of scheduling.
40///
41#[derive(PartialEq, Eq, Ord, Clone, Copy, PartialOrd, Hash, Debug)]
42pub enum ExecutionPhase {
43    First,
44    Normal,
45    Last,
46}
47
48impl Display for ExecutionPhase {
49    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
50        write!(f, "{self:?}")
51    }
52}
53
54/// A manager for the state of a discrete-event simulation
55///
56/// Provides core simulation services including
57/// * Maintaining a notion of time
58/// * Scheduling events to occur at some point in the future and executing them
59///   at that time
60/// * Holding data that can be accessed by simulation modules
61///
62/// Simulations are constructed out of a series of interacting modules that
63/// take turns manipulating the [`Context`] through a mutable reference. Modules
64/// store data in the simulation using the [`DataPlugin`] trait that allows them
65/// to retrieve data by type.
66///
67/// The future event list of the simulation is a queue of `Callback` objects -
68/// called `plans` - that will assume control of the [`Context`] at a future point
69/// in time and execute the logic in the associated `FnOnce(&mut Context)`
70/// closure. Modules can add plans to this queue through the [`Context`].
71///
72/// The simulation also has a separate callback mechanism. Callbacks
73/// fire before the next timed event (even if it is scheduled for the
74/// current time). This allows modules to schedule actions for immediate
75/// execution but outside of the current iteration of the event loop.
76///
77/// Modules can also emit 'events' that other modules can subscribe to handle by
78/// event type. This allows modules to broadcast that specific things have
79/// occurred and have other modules take turns reacting to these occurrences.
80///
81pub struct Context {
82    plan_queue: Queue<Box<Callback>, ExecutionPhase>,
83    callback_queue: VecDeque<Box<Callback>>,
84    event_handlers: HashMap<TypeId, Box<dyn Any>>,
85    data_plugins: Vec<OnceCell<Box<dyn Any>>>,
86    #[cfg(feature = "debugger")]
87    breakpoints_scheduled: Queue<Box<Callback>, ExecutionPhase>,
88    current_time: f64,
89    shutdown_requested: bool,
90    #[cfg(feature = "debugger")]
91    break_requested: bool,
92    #[cfg(feature = "debugger")]
93    breakpoints_enabled: bool,
94    execution_profiler: ExecutionProfilingCollector,
95    pub print_execution_statistics: bool,
96}
97
98impl Context {
99    /// Create a new empty `Context`
100    #[must_use]
101    pub fn new() -> Context {
102        // Create a vector to accommodate all registered data plugins
103        let data_plugins = std::iter::repeat_with(OnceCell::new)
104            .take(get_data_plugin_count())
105            .collect();
106
107        Context {
108            plan_queue: Queue::new(),
109            callback_queue: VecDeque::new(),
110            event_handlers: HashMap::new(),
111            data_plugins,
112            #[cfg(feature = "debugger")]
113            breakpoints_scheduled: Queue::new(),
114            current_time: 0.0,
115            shutdown_requested: false,
116            #[cfg(feature = "debugger")]
117            break_requested: false,
118            #[cfg(feature = "debugger")]
119            breakpoints_enabled: true,
120            execution_profiler: ExecutionProfilingCollector::new(),
121            print_execution_statistics: false,
122        }
123    }
124
125    /// Schedule the simulation to pause at time t and start the debugger.
126    /// This will give you a REPL which allows you to inspect the state of
127    /// the simulation (type help to see a list of commands)
128    ///
129    /// # Errors
130    /// Internal debugger errors e.g., reading or writing to stdin/stdout;
131    /// errors in Ixa are printed to stdout
132    #[cfg(feature = "debugger")]
133    pub fn schedule_debugger(
134        &mut self,
135        time: f64,
136        priority: Option<ExecutionPhase>,
137        callback: Box<Callback>,
138    ) {
139        trace!("scheduling debugger");
140        let priority = priority.unwrap_or(ExecutionPhase::First);
141        self.breakpoints_scheduled
142            .add_plan(time, callback, priority);
143    }
144
145    /// Register to handle emission of events of type E
146    ///
147    /// Handlers will be called upon event emission in order of subscription as
148    /// queued `Callback`s with the appropriate event.
149    #[allow(clippy::missing_panics_doc)]
150    pub fn subscribe_to_event<E: IxaEvent + Copy + 'static>(
151        &mut self,
152        handler: impl Fn(&mut Context, E) + 'static,
153    ) {
154        let handler_vec = self
155            .event_handlers
156            .entry(TypeId::of::<E>())
157            .or_insert_with(|| Box::<Vec<Rc<EventHandler<E>>>>::default());
158        let handler_vec: &mut Vec<Rc<EventHandler<E>>> = handler_vec.downcast_mut().unwrap();
159        handler_vec.push(Rc::new(handler));
160        E::on_subscribe(self);
161    }
162
163    /// Emit an event of type E to be handled by registered receivers
164    ///
165    /// Receivers will handle events in the order that they have subscribed and
166    /// are queued as callbacks
167    #[allow(clippy::missing_panics_doc)]
168    pub fn emit_event<E: IxaEvent + Copy + 'static>(&mut self, event: E) {
169        // Destructure to obtain event handlers and plan queue
170        let Context {
171            event_handlers,
172            callback_queue,
173            ..
174        } = self;
175        if let Some(handler_vec) = event_handlers.get(&TypeId::of::<E>()) {
176            let handler_vec: &Vec<Rc<EventHandler<E>>> = handler_vec.downcast_ref().unwrap();
177            for handler in handler_vec {
178                let handler_clone = Rc::clone(handler);
179                callback_queue.push_back(Box::new(move |context| handler_clone(context, event)));
180            }
181        }
182    }
183
184    /// Add a plan to the future event list at the specified time in the normal
185    /// phase
186    ///
187    /// Returns a [`PlanId`] for the newly-added plan that can be used to cancel it
188    /// if needed.
189    /// # Panics
190    ///
191    /// Panics if time is in the past, infinite, or NaN.
192    pub fn add_plan(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static) -> PlanId {
193        self.add_plan_with_phase(time, callback, ExecutionPhase::Normal)
194    }
195
196    /// Add a plan to the future event list at the specified time and with the
197    /// specified phase (first, normal, or last among plans at the
198    /// specified time)
199    ///
200    /// Returns a [`PlanId`] for the newly-added plan that can be used to cancel it
201    /// if needed.
202    /// # Panics
203    ///
204    /// Panics if time is in the past, infinite, or NaN.
205    pub fn add_plan_with_phase(
206        &mut self,
207        time: f64,
208        callback: impl FnOnce(&mut Context) + 'static,
209        phase: ExecutionPhase,
210    ) -> PlanId {
211        assert!(
212            !time.is_nan() && !time.is_infinite() && time >= self.current_time,
213            "Time is invalid"
214        );
215        self.plan_queue.add_plan(time, Box::new(callback), phase)
216    }
217
218    fn evaluate_periodic_and_schedule_next(
219        &mut self,
220        period: f64,
221        callback: impl Fn(&mut Context) + 'static,
222        phase: ExecutionPhase,
223    ) {
224        trace!(
225            "evaluate periodic at {} (period={})",
226            self.current_time,
227            period
228        );
229        callback(self);
230        if !self.plan_queue.is_empty() {
231            let next_time = self.current_time + period;
232            self.add_plan_with_phase(
233                next_time,
234                move |context| context.evaluate_periodic_and_schedule_next(period, callback, phase),
235                phase,
236            );
237        }
238    }
239
240    /// Add a plan with specified priority to the future event list, and
241    /// continuously repeat the plan at the specified period, stopping
242    /// only once there are no other plans scheduled.
243    ///
244    /// # Panics
245    ///
246    /// Panics if plan period is negative, infinite, or NaN.
247    pub fn add_periodic_plan_with_phase(
248        &mut self,
249        period: f64,
250        callback: impl Fn(&mut Context) + 'static,
251        phase: ExecutionPhase,
252    ) {
253        assert!(
254            period > 0.0 && !period.is_nan() && !period.is_infinite(),
255            "Period must be greater than 0"
256        );
257
258        self.add_plan_with_phase(
259            0.0,
260            move |context| context.evaluate_periodic_and_schedule_next(period, callback, phase),
261            phase,
262        );
263    }
264
265    /// Cancel a plan that has been added to the queue
266    ///
267    /// # Panics
268    ///
269    /// This function panics if you cancel a plan which has already been
270    /// cancelled or executed.
271    pub fn cancel_plan(&mut self, plan_id: &PlanId) {
272        trace!("canceling plan {plan_id:?}");
273        let result = self.plan_queue.cancel_plan(plan_id);
274        if result.is_none() {
275            warn!("Tried to cancel nonexistent plan with ID = {plan_id:?}");
276        }
277    }
278
279    #[doc(hidden)]
280    #[allow(dead_code)]
281    pub(crate) fn remaining_plan_count(&self) -> usize {
282        self.plan_queue.remaining_plan_count()
283    }
284
285    /// Add a `Callback` to the queue to be executed before the next plan
286    pub fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static) {
287        trace!("queuing callback");
288        self.callback_queue.push_back(Box::new(callback));
289    }
290
291    /// Retrieve a mutable reference to the data container associated with a
292    /// [`DataPlugin`]
293    ///
294    /// If the data container has not been already added to the [`Context`] then
295    /// this function will use the [`DataPlugin::init`] method
296    /// to construct a new data container and store it in the [`Context`].
297    ///
298    /// Returns a mutable reference to the data container
299    #[must_use]
300    #[allow(clippy::missing_panics_doc)]
301    #[allow(clippy::needless_pass_by_value)]
302    pub fn get_data_mut<T: DataPlugin>(&mut self, _data_plugin: T) -> &mut T::DataContainer {
303        let index = T::index_within_context();
304
305        // If the data plugin is already initialized, return a mutable reference.
306        if self.data_plugins[index].get().is_some() {
307            return self.data_plugins[index]
308                .get_mut()
309                .unwrap()
310                .downcast_mut::<T::DataContainer>()
311                .expect("TypeID does not match data plugin type");
312        }
313
314        // Initialize the data plugin if not already initialized.
315        let data = T::init(self);
316        let cell = self
317            .data_plugins
318            .get_mut(index)
319            .unwrap_or_else(|| panic!("No data plugin found with index = {index:?}. You must use the `define_data_plugin!` macro to create a data plugin."));
320        let _ = cell.set(Box::new(data));
321        cell.get_mut()
322            .unwrap()
323            .downcast_mut::<T::DataContainer>()
324            .expect("TypeID does not match data plugin type. You must use the `define_data_plugin!` macro to create a data plugin.")
325    }
326
327    /// Retrieve a reference to the data container associated with a
328    /// [`DataPlugin`]
329    ///
330    /// Returns a reference to the data container if it exists or else `None`
331    #[must_use]
332    #[allow(clippy::needless_pass_by_value)]
333    pub fn get_data<T: DataPlugin>(&self, _data_plugin: T) -> &T::DataContainer {
334        let index = T::index_within_context();
335        self.data_plugins
336            .get(index)
337            .unwrap_or_else(|| panic!("No data plugin found with index = {index:?}. You must use the `define_data_plugin!` macro to create a data plugin."))
338            .get_or_init(|| Box::new(T::init(self)))
339            .downcast_ref::<T::DataContainer>()
340            .expect("TypeID does not match data plugin type. You must use the `define_data_plugin!` macro to create a data plugin.")
341    }
342
343    /// Shutdown the simulation cleanly, abandoning all events after whatever
344    /// is currently executing.
345    pub fn shutdown(&mut self) {
346        trace!("shutdown context");
347        self.shutdown_requested = true;
348    }
349
350    /// Get the current time in the simulation
351    ///
352    /// Returns the current time
353    #[must_use]
354    pub fn get_current_time(&self) -> f64 {
355        self.current_time
356    }
357
358    /// Request to enter a debugger session at next event loop
359    #[cfg(feature = "debugger")]
360    pub fn request_debugger(&mut self) {
361        self.break_requested = true;
362    }
363
364    /// Request to enter a debugger session at next event loop
365    #[cfg(feature = "debugger")]
366    pub fn cancel_debugger_request(&mut self) {
367        self.break_requested = false;
368    }
369
370    /// Disable breakpoints
371    #[cfg(feature = "debugger")]
372    pub fn disable_breakpoints(&mut self) {
373        self.breakpoints_enabled = false;
374    }
375
376    /// Enable breakpoints
377    #[cfg(feature = "debugger")]
378    pub fn enable_breakpoints(&mut self) {
379        self.breakpoints_enabled = true;
380    }
381
382    /// Returns `true` if breakpoints are enabled.
383    #[must_use]
384    #[cfg(feature = "debugger")]
385    pub fn breakpoints_are_enabled(&self) -> bool {
386        self.breakpoints_enabled
387    }
388
389    /// Delete the breakpoint with the given ID
390    #[cfg(feature = "debugger")]
391    pub fn delete_breakpoint(&mut self, breakpoint_id: u64) -> Option<Box<Callback>> {
392        self.breakpoints_scheduled
393            .cancel_plan(&PlanId(breakpoint_id))
394    }
395
396    /// Returns a list of length `at_most`, or unbounded if `at_most=0`, of active scheduled
397    /// `PlanSchedule`s ordered as they are in the queue itself.
398    #[must_use]
399    #[cfg(feature = "debugger")]
400    pub fn list_breakpoints(&self, at_most: usize) -> Vec<&PlanSchedule<ExecutionPhase>> {
401        self.breakpoints_scheduled.list_schedules(at_most)
402    }
403
404    /// Deletes all breakpoints.
405    #[cfg(feature = "debugger")]
406    pub fn clear_breakpoints(&mut self) {
407        self.breakpoints_scheduled.clear();
408    }
409
410    /// Execute the simulation until the plan and callback queues are empty
411    pub fn execute(&mut self) {
412        trace!("entering event loop");
413        // Start plan loop
414        loop {
415            #[cfg(feature = "progress_bar")]
416            if crate::progress::MAX_TIME.get().is_some() {
417                update_timeline_progress(self.current_time);
418            }
419
420            #[cfg(feature = "debugger")]
421            if self.break_requested {
422                enter_debugger(self);
423            } else if self.shutdown_requested {
424                self.shutdown_requested = false;
425                break;
426            } else {
427                self.execute_single_step();
428            }
429
430            self.execution_profiler.refresh();
431
432            #[cfg(not(feature = "debugger"))]
433            if self.shutdown_requested {
434                self.shutdown_requested = false;
435                break;
436            } else {
437                self.execute_single_step();
438            }
439        }
440
441        let stats = self.get_execution_statistics();
442        if self.print_execution_statistics {
443            print_execution_statistics(&stats);
444        } else {
445            log_execution_statistics(&stats);
446        }
447    }
448
449    /// Executes a single step of the simulation, prioritizing tasks as follows:
450    ///   1. Breakpoints
451    ///   2. Callbacks
452    ///   3. Plans
453    ///   4. Shutdown
454    pub fn execute_single_step(&mut self) {
455        // This always runs the breakpoint before anything scheduled in the task queue regardless
456        // of the `ExecutionPhase` of the breakpoint. If breakpoints are disabled, they are still
457        // popped from the breakpoint queue at the time they are scheduled even though they are not
458        // executed.
459        #[cfg(feature = "debugger")]
460        if let Some((bp, _)) = self.breakpoints_scheduled.peek() {
461            // If the priority of bp is `ExecutionPhase::First`, and if the next scheduled plan
462            // is scheduled at or after bp's time (or doesn't exist), run bp.
463            // If the priority of bp is `ExecutionPhase::Last`, and if the next scheduled plan
464            // is scheduled strictly after bp's time (or doesn't exist), run bp.
465            if let Some(plan_time) = self.plan_queue.next_time() {
466                if (bp.priority == ExecutionPhase::First && bp.time <= plan_time)
467                    || (bp.priority == ExecutionPhase::Last && bp.time < plan_time)
468                {
469                    self.breakpoints_scheduled.get_next_plan(); // Pop the breakpoint
470                    if self.breakpoints_enabled {
471                        self.break_requested = true;
472                        return;
473                    }
474                }
475            } else {
476                self.breakpoints_scheduled.get_next_plan(); // Pop the breakpoint
477                if self.breakpoints_enabled {
478                    self.break_requested = true;
479                    return;
480                }
481            }
482        }
483
484        // If there is a callback, run it.
485        if let Some(callback) = self.callback_queue.pop_front() {
486            trace!("calling callback");
487            callback(self);
488        }
489        // There aren't any callbacks, so look at the first plan.
490        else if let Some(plan) = self.plan_queue.get_next_plan() {
491            trace!("calling plan at {:.6}", plan.time);
492            self.current_time = plan.time;
493            (plan.data)(self);
494        } else {
495            trace!("No callbacks or plans; exiting event loop");
496            // OK, there aren't any plans, so we're done.
497            self.shutdown_requested = true;
498        }
499    }
500
501    pub fn get_execution_statistics(&mut self) -> ExecutionStatistics {
502        let population = self.get_current_population();
503        self.execution_profiler.compute_final_statistics(population)
504    }
505}
506
507pub trait ContextBase: Sized {
508    fn subscribe_to_event<E: IxaEvent + Copy + 'static>(
509        &mut self,
510        handler: impl Fn(&mut Context, E) + 'static,
511    );
512    fn emit_event<E: IxaEvent + Copy + 'static>(&mut self, event: E);
513    fn add_plan(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static) -> PlanId;
514    fn add_plan_with_phase(
515        &mut self,
516        time: f64,
517        callback: impl FnOnce(&mut Context) + 'static,
518        phase: ExecutionPhase,
519    ) -> PlanId;
520    fn add_periodic_plan_with_phase(
521        &mut self,
522        period: f64,
523        callback: impl Fn(&mut Context) + 'static,
524        phase: ExecutionPhase,
525    );
526    fn cancel_plan(&mut self, plan_id: &PlanId);
527    fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static);
528    fn get_data_mut<T: DataPlugin>(&mut self, plugin: T) -> &mut T::DataContainer;
529    fn get_data<T: DataPlugin>(&self, plugin: T) -> &T::DataContainer;
530    fn get_current_time(&self) -> f64;
531    fn get_execution_statistics(&mut self) -> ExecutionStatistics;
532}
533impl ContextBase for Context {
534    delegate::delegate! {
535        to self {
536            fn subscribe_to_event<E: IxaEvent + Copy + 'static>(&mut self, handler: impl Fn(&mut Context, E) + 'static);
537            fn emit_event<E: IxaEvent + Copy + 'static>(&mut self, event: E);
538            fn add_plan(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static) -> PlanId;
539            fn add_plan_with_phase(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static, phase: ExecutionPhase) -> PlanId;
540            fn add_periodic_plan_with_phase(&mut self, period: f64, callback: impl Fn(&mut Context) + 'static, phase: ExecutionPhase);
541            fn cancel_plan(&mut self, plan_id: &PlanId);
542            fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static);
543            fn get_data_mut<T: DataPlugin>(&mut self, plugin: T) -> &mut T::DataContainer;
544            fn get_data<T: DataPlugin>(&self, plugin: T) -> &T::DataContainer;
545            fn get_current_time(&self) -> f64;
546            fn get_execution_statistics(&mut self) -> ExecutionStatistics;
547        }
548    }
549}
550
551impl Default for Context {
552    fn default() -> Self {
553        Self::new()
554    }
555}
556
557#[cfg(test)]
558#[allow(clippy::float_cmp)]
559mod tests {
560    use std::cell::RefCell;
561
562    use ixa_derive::IxaEvent;
563
564    use super::*;
565    use crate::define_data_plugin;
566
567    define_data_plugin!(ComponentA, Vec<u32>, vec![]);
568
569    #[test]
570    fn empty_context() {
571        let mut context = Context::new();
572        context.execute();
573        assert_eq!(context.get_current_time(), 0.0);
574    }
575
576    #[test]
577    fn get_data() {
578        let mut context = Context::new();
579        context.get_data_mut(ComponentA).push(1);
580        assert_eq!(*context.get_data(ComponentA), vec![1],);
581    }
582
583    fn add_plan(context: &mut Context, time: f64, value: u32) -> PlanId {
584        context.add_plan(time, move |context| {
585            context.get_data_mut(ComponentA).push(value);
586        })
587    }
588
589    fn add_plan_with_phase(
590        context: &mut Context,
591        time: f64,
592        value: u32,
593        phase: ExecutionPhase,
594    ) -> PlanId {
595        context.add_plan_with_phase(
596            time,
597            move |context| {
598                context.get_data_mut(ComponentA).push(value);
599            },
600            phase,
601        )
602    }
603
604    #[test]
605    #[should_panic(expected = "Time is invalid")]
606    fn negative_plan_time() {
607        let mut context = Context::new();
608        add_plan(&mut context, -1.0, 0);
609    }
610
611    #[test]
612    #[should_panic(expected = "Time is invalid")]
613    fn infinite_plan_time() {
614        let mut context = Context::new();
615        add_plan(&mut context, f64::INFINITY, 0);
616    }
617
618    #[test]
619    #[should_panic(expected = "Time is invalid")]
620    fn nan_plan_time() {
621        let mut context = Context::new();
622        add_plan(&mut context, f64::NAN, 0);
623    }
624
625    #[test]
626    fn timed_plan_only() {
627        let mut context = Context::new();
628        add_plan(&mut context, 1.0, 1);
629        context.execute();
630        assert_eq!(context.get_current_time(), 1.0);
631        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
632    }
633
634    #[test]
635    fn callback_only() {
636        let mut context = Context::new();
637        context.queue_callback(|context| {
638            context.get_data_mut(ComponentA).push(1);
639        });
640        context.execute();
641        assert_eq!(context.get_current_time(), 0.0);
642        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
643    }
644
645    #[test]
646    fn callback_before_timed_plan() {
647        let mut context = Context::new();
648        context.queue_callback(|context| {
649            context.get_data_mut(ComponentA).push(1);
650        });
651        add_plan(&mut context, 1.0, 2);
652        context.execute();
653        assert_eq!(context.get_current_time(), 1.0);
654        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
655    }
656
657    #[test]
658    fn callback_adds_timed_plan() {
659        let mut context = Context::new();
660        context.queue_callback(|context| {
661            context.get_data_mut(ComponentA).push(1);
662            add_plan(context, 1.0, 2);
663            context.get_data_mut(ComponentA).push(3);
664        });
665        context.execute();
666        assert_eq!(context.get_current_time(), 1.0);
667        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 2]);
668    }
669
670    #[test]
671    fn callback_adds_callback_and_timed_plan() {
672        let mut context = Context::new();
673        context.queue_callback(|context| {
674            context.get_data_mut(ComponentA).push(1);
675            add_plan(context, 1.0, 2);
676            context.queue_callback(|context| {
677                context.get_data_mut(ComponentA).push(4);
678            });
679            context.get_data_mut(ComponentA).push(3);
680        });
681        context.execute();
682        assert_eq!(context.get_current_time(), 1.0);
683        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 4, 2]);
684    }
685
686    #[test]
687    fn timed_plan_adds_callback_and_timed_plan() {
688        let mut context = Context::new();
689        context.add_plan(1.0, |context| {
690            context.get_data_mut(ComponentA).push(1);
691            // We add the plan first, but the callback will fire first.
692            add_plan(context, 2.0, 3);
693            context.queue_callback(|context| {
694                context.get_data_mut(ComponentA).push(2);
695            });
696        });
697        context.execute();
698        assert_eq!(context.get_current_time(), 2.0);
699        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
700    }
701
702    #[test]
703    fn cancel_plan() {
704        let mut context = Context::new();
705        let to_cancel = add_plan(&mut context, 2.0, 1);
706        context.add_plan(1.0, move |context| {
707            context.cancel_plan(&to_cancel);
708        });
709        context.execute();
710        assert_eq!(context.get_current_time(), 1.0);
711        let test_vec: Vec<u32> = vec![];
712        assert_eq!(*context.get_data_mut(ComponentA), test_vec);
713    }
714
715    #[test]
716    fn add_plan_with_current_time() {
717        let mut context = Context::new();
718        context.add_plan(1.0, move |context| {
719            context.get_data_mut(ComponentA).push(1);
720            add_plan(context, 1.0, 2);
721            context.queue_callback(|context| {
722                context.get_data_mut(ComponentA).push(3);
723            });
724        });
725        context.execute();
726        assert_eq!(context.get_current_time(), 1.0);
727        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 2]);
728    }
729
730    #[test]
731    fn plans_at_same_time_fire_in_order() {
732        let mut context = Context::new();
733        add_plan(&mut context, 1.0, 1);
734        add_plan(&mut context, 1.0, 2);
735        context.execute();
736        assert_eq!(context.get_current_time(), 1.0);
737        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
738    }
739
740    #[test]
741    fn check_plan_phase_ordering() {
742        assert!(ExecutionPhase::First < ExecutionPhase::Normal);
743        assert!(ExecutionPhase::Normal < ExecutionPhase::Last);
744    }
745
746    #[test]
747    fn plans_at_same_time_follow_phase() {
748        let mut context = Context::new();
749        add_plan(&mut context, 1.0, 1);
750        add_plan_with_phase(&mut context, 1.0, 5, ExecutionPhase::Last);
751        add_plan_with_phase(&mut context, 1.0, 3, ExecutionPhase::First);
752        add_plan(&mut context, 1.0, 2);
753        add_plan_with_phase(&mut context, 1.0, 6, ExecutionPhase::Last);
754        add_plan_with_phase(&mut context, 1.0, 4, ExecutionPhase::First);
755        context.execute();
756        assert_eq!(context.get_current_time(), 1.0);
757        assert_eq!(*context.get_data_mut(ComponentA), vec![3, 4, 1, 2, 5, 6]);
758    }
759
760    #[derive(Copy, Clone, IxaEvent)]
761    struct Event1 {
762        pub data: usize,
763    }
764
765    #[derive(Copy, Clone, IxaEvent)]
766    struct Event2 {
767        pub data: usize,
768    }
769
770    #[test]
771    fn simple_event() {
772        let mut context = Context::new();
773        let obs_data = Rc::new(RefCell::new(0));
774        let obs_data_clone = Rc::clone(&obs_data);
775
776        context.subscribe_to_event::<Event1>(move |_, event| {
777            *obs_data_clone.borrow_mut() = event.data;
778        });
779
780        context.emit_event(Event1 { data: 1 });
781        context.execute();
782        assert_eq!(*obs_data.borrow(), 1);
783    }
784
785    #[test]
786    fn multiple_events() {
787        let mut context = Context::new();
788        let obs_data = Rc::new(RefCell::new(0));
789        let obs_data_clone = Rc::clone(&obs_data);
790
791        context.subscribe_to_event::<Event1>(move |_, event| {
792            *obs_data_clone.borrow_mut() += event.data;
793        });
794
795        context.emit_event(Event1 { data: 1 });
796        context.emit_event(Event1 { data: 2 });
797        context.execute();
798
799        // Both of these should have been received.
800        assert_eq!(*obs_data.borrow(), 3);
801    }
802
803    #[test]
804    fn multiple_event_handlers() {
805        let mut context = Context::new();
806        let obs_data1 = Rc::new(RefCell::new(0));
807        let obs_data1_clone = Rc::clone(&obs_data1);
808        let obs_data2 = Rc::new(RefCell::new(0));
809        let obs_data2_clone = Rc::clone(&obs_data2);
810
811        context.subscribe_to_event::<Event1>(move |_, event| {
812            *obs_data1_clone.borrow_mut() = event.data;
813        });
814        context.subscribe_to_event::<Event1>(move |_, event| {
815            *obs_data2_clone.borrow_mut() = event.data;
816        });
817        context.emit_event(Event1 { data: 1 });
818        context.execute();
819        assert_eq!(*obs_data1.borrow(), 1);
820        assert_eq!(*obs_data2.borrow(), 1);
821    }
822
823    #[test]
824    fn multiple_event_types() {
825        let mut context = Context::new();
826        let obs_data1 = Rc::new(RefCell::new(0));
827        let obs_data1_clone = Rc::clone(&obs_data1);
828        let obs_data2 = Rc::new(RefCell::new(0));
829        let obs_data2_clone = Rc::clone(&obs_data2);
830
831        context.subscribe_to_event::<Event1>(move |_, event| {
832            *obs_data1_clone.borrow_mut() = event.data;
833        });
834        context.subscribe_to_event::<Event2>(move |_, event| {
835            *obs_data2_clone.borrow_mut() = event.data;
836        });
837        context.emit_event(Event1 { data: 1 });
838        context.emit_event(Event2 { data: 2 });
839        context.execute();
840        assert_eq!(*obs_data1.borrow(), 1);
841        assert_eq!(*obs_data2.borrow(), 2);
842    }
843
844    #[test]
845    fn subscribe_after_event() {
846        let mut context = Context::new();
847        let obs_data = Rc::new(RefCell::new(0));
848        let obs_data_clone = Rc::clone(&obs_data);
849
850        context.emit_event(Event1 { data: 1 });
851        context.subscribe_to_event::<Event1>(move |_, event| {
852            *obs_data_clone.borrow_mut() = event.data;
853        });
854
855        context.execute();
856        assert_eq!(*obs_data.borrow(), 0);
857    }
858
859    #[test]
860    fn shutdown_cancels_plans() {
861        let mut context = Context::new();
862        add_plan(&mut context, 1.0, 1);
863        context.add_plan(1.5, Context::shutdown);
864        add_plan(&mut context, 2.0, 2);
865        context.execute();
866        assert_eq!(context.get_current_time(), 1.5);
867        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
868    }
869
870    #[test]
871    fn shutdown_cancels_callbacks() {
872        let mut context = Context::new();
873        add_plan(&mut context, 1.0, 1);
874        context.add_plan(1.5, |context| {
875            // Note that we add the callback *before* we call shutdown
876            // but shutdown cancels everything.
877            context.queue_callback(|context| {
878                context.get_data_mut(ComponentA).push(3);
879            });
880            context.shutdown();
881        });
882        context.execute();
883        assert_eq!(context.get_current_time(), 1.5);
884        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
885    }
886
887    #[test]
888    fn shutdown_cancels_events() {
889        let mut context = Context::new();
890        let obs_data = Rc::new(RefCell::new(0));
891        let obs_data_clone = Rc::clone(&obs_data);
892        context.subscribe_to_event::<Event1>(move |_, event| {
893            *obs_data_clone.borrow_mut() = event.data;
894        });
895        context.emit_event(Event1 { data: 1 });
896        context.shutdown();
897        context.execute();
898        assert_eq!(*obs_data.borrow(), 0);
899    }
900
901    #[test]
902    #[allow(clippy::cast_sign_loss)]
903    #[allow(clippy::cast_possible_truncation)]
904    fn periodic_plan_self_schedules() {
905        // checks whether the person properties report schedules itself
906        // based on whether there are plans in the queue
907        let mut context = Context::new();
908        context.add_periodic_plan_with_phase(
909            1.0,
910            |context| {
911                let time = context.get_current_time();
912                context.get_data_mut(ComponentA).push(time as u32);
913            },
914            ExecutionPhase::Last,
915        );
916        context.add_plan(1.0, move |_context| {});
917        context.add_plan(1.5, move |_context| {});
918        context.execute();
919        assert_eq!(context.get_current_time(), 2.0);
920
921        assert_eq!(*context.get_data(ComponentA), vec![0, 1, 2]); // time 0.0, 1.0, and 2.0
922    }
923
924    #[test]
925    fn shutdown_requested_reset() {
926        // This test verifies that shutdown_requested is properly reset after
927        // being acted upon. This allows the context to be reused after shutdown.
928        let mut context = Context::new();
929        context.add_person(()).unwrap();
930
931        // Schedule a plan at time 0.0 that calls shutdown
932        context.add_plan(0.0, |ctx| {
933            ctx.shutdown();
934        });
935
936        // First execute - should run until shutdown
937        context.execute();
938        assert_eq!(context.get_current_time(), 0.0);
939        assert_eq!(context.get_current_population(), 1);
940
941        // Add a new plan at time 2.0
942        context.add_plan(2.0, |ctx| {
943            ctx.add_person(()).unwrap();
944        });
945
946        // Second execute - should execute the new plan
947        // If shutdown_requested wasn't reset, this would immediately break
948        // without executing the plan, leaving population at 1.
949        context.execute();
950        assert_eq!(context.get_current_time(), 2.0);
951        assert_eq!(
952            context.get_current_population(),
953            2,
954            "If this fails, shutdown_requested was not properly reset"
955        );
956    }
957}