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