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