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::{error, get_data_plugin_count, trace, 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            error!("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
511/// A supertrait that exposes useful methods from `Context`
512/// for plugins implementing Context extensions.
513///
514/// Usage:
515// This example triggers the error "#[ctor]/#[dtor] is not supported
516// on the current target," which appears to be spurious, so we
517// ignore it.
518/// ```ignore
519/// use ixa::prelude_for_plugins::*;
520/// define_data_plugin!(MyData, bool, false);
521/// pub trait MyPlugin: PluginContext {
522///     fn set_my_data(&mut self) {
523///         let my_data = self.get_data_container_mut(MyData);
524///         *my_data = true;
525///     }
526/// }
527pub trait PluginContext: Sized {
528    fn subscribe_to_event<E: IxaEvent + Copy + 'static>(
529        &mut self,
530        handler: impl Fn(&mut Context, E) + 'static,
531    );
532    fn emit_event<E: IxaEvent + Copy + 'static>(&mut self, event: E);
533    fn add_plan(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static) -> PlanId;
534    fn add_plan_with_phase(
535        &mut self,
536        time: f64,
537        callback: impl FnOnce(&mut Context) + 'static,
538        phase: ExecutionPhase,
539    ) -> PlanId;
540    fn add_periodic_plan_with_phase(
541        &mut self,
542        period: f64,
543        callback: impl Fn(&mut Context) + 'static,
544        phase: ExecutionPhase,
545    );
546    fn cancel_plan(&mut self, plan_id: &PlanId);
547    fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static);
548    fn get_data_mut<T: DataPlugin>(&mut self, plugin: T) -> &mut T::DataContainer;
549    fn get_data<T: DataPlugin>(&self, plugin: T) -> &T::DataContainer;
550    fn get_current_time(&self) -> f64;
551    fn get_execution_statistics(&mut self) -> ExecutionStatistics;
552}
553impl PluginContext for Context {
554    delegate::delegate! {
555        to self {
556            fn subscribe_to_event<E: IxaEvent + Copy + 'static>(&mut self, handler: impl Fn(&mut Context, E) + 'static);
557            fn emit_event<E: IxaEvent + Copy + 'static>(&mut self, event: E);
558            fn add_plan(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static) -> PlanId;
559            fn add_plan_with_phase(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static, phase: ExecutionPhase) -> PlanId;
560            fn add_periodic_plan_with_phase(&mut self, period: f64, callback: impl Fn(&mut Context) + 'static, phase: ExecutionPhase);
561            fn cancel_plan(&mut self, plan_id: &PlanId);
562            fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static);
563            fn get_data_mut<T: DataPlugin>(&mut self, plugin: T) -> &mut T::DataContainer;
564            fn get_data<T: DataPlugin>(&self, plugin: T) -> &T::DataContainer;
565            fn get_current_time(&self) -> f64;
566            fn get_execution_statistics(&mut self) -> ExecutionStatistics;
567        }
568    }
569}
570
571// Tests for the PluginContext trait, including:
572// - Making sure all the methods work identically to Context
573// - Defining a trait extension that uses it compiles correctly
574// - External functions can use impl PluginContext if necessary
575#[cfg(test)]
576mod test_plugin_context {
577    use crate::prelude_for_plugins::*;
578    #[derive(Copy, Clone, IxaEvent)]
579    struct MyEvent {
580        pub data: usize,
581    }
582
583    define_data_plugin!(MyData, i32, 0);
584
585    fn do_stuff_with_context(context: &mut impl PluginContext) {
586        context.add_plan(1.0, |context| {
587            let data = context.get_data(MyData);
588            assert_eq!(*data, 42);
589        });
590    }
591
592    trait MyDataExt: PluginContext {
593        fn all_methods(&mut self) {
594            assert_eq!(self.get_current_time(), 0.0);
595        }
596        fn all_methods_mut(&mut self) {
597            self.setup();
598            self.subscribe_to_event(|_: &mut Context, event: MyEvent| {
599                assert_eq!(event.data, 42);
600            });
601            self.emit_event(MyEvent { data: 42 });
602            self.add_plan_with_phase(
603                1.0,
604                |context| {
605                    let data = context.get_data(MyData);
606                    assert_eq!(*data, 42);
607                    context.set_my_data(100);
608                },
609                crate::ExecutionPhase::Last,
610            );
611            self.add_plan(1.0, |context| {
612                assert_eq!(context.get_my_data(), 42);
613            });
614            self.add_periodic_plan_with_phase(
615                1.0,
616                |context| {
617                    println!(
618                        "Periodic plan at time {} with data {}",
619                        context.get_current_time(),
620                        context.get_my_data()
621                    );
622                },
623                crate::ExecutionPhase::Normal,
624            );
625            self.queue_callback(|context| {
626                let data = context.get_data(MyData);
627                assert_eq!(*data, 42);
628            });
629        }
630        fn setup(&mut self) {
631            let data = self.get_data_mut(MyData);
632            *data = 42;
633            do_stuff_with_context(self);
634        }
635        fn get_my_data(&self) -> i32 {
636            *self.get_data(MyData)
637        }
638        fn set_my_data(&mut self, value: i32) {
639            let data = self.get_data_mut(MyData);
640            *data = value;
641        }
642        fn test_external_function(&mut self) {
643            self.setup();
644            do_stuff_with_context(self);
645        }
646    }
647    impl MyDataExt for Context {}
648
649    #[test]
650    fn test_all_methods() {
651        let mut context = Context::new();
652        context.all_methods_mut();
653        context.all_methods();
654        context.execute();
655    }
656
657    #[test]
658    fn test_plugin_context() {
659        let mut context = Context::new();
660        context.setup();
661        assert_eq!(context.get_my_data(), 42);
662    }
663
664    #[test]
665    fn test_external_function() {
666        let mut context = Context::new();
667        context.test_external_function();
668        assert_eq!(context.get_my_data(), 42);
669    }
670}
671
672impl Default for Context {
673    fn default() -> Self {
674        Self::new()
675    }
676}
677
678#[cfg(test)]
679#[allow(clippy::float_cmp)]
680mod tests {
681    use std::cell::RefCell;
682
683    use super::*;
684    use crate::define_data_plugin;
685    use ixa_derive::IxaEvent;
686
687    define_data_plugin!(ComponentA, Vec<u32>, vec![]);
688
689    #[test]
690    fn empty_context() {
691        let mut context = Context::new();
692        context.execute();
693        assert_eq!(context.get_current_time(), 0.0);
694    }
695
696    #[test]
697    fn get_data() {
698        let mut context = Context::new();
699        context.get_data_mut(ComponentA).push(1);
700        assert_eq!(*context.get_data(ComponentA), vec![1],);
701    }
702
703    fn add_plan(context: &mut Context, time: f64, value: u32) -> PlanId {
704        context.add_plan(time, move |context| {
705            context.get_data_mut(ComponentA).push(value);
706        })
707    }
708
709    fn add_plan_with_phase(
710        context: &mut Context,
711        time: f64,
712        value: u32,
713        phase: ExecutionPhase,
714    ) -> PlanId {
715        context.add_plan_with_phase(
716            time,
717            move |context| {
718                context.get_data_mut(ComponentA).push(value);
719            },
720            phase,
721        )
722    }
723
724    #[test]
725    #[should_panic(expected = "Time is invalid")]
726    fn negative_plan_time() {
727        let mut context = Context::new();
728        add_plan(&mut context, -1.0, 0);
729    }
730
731    #[test]
732    #[should_panic(expected = "Time is invalid")]
733    fn infinite_plan_time() {
734        let mut context = Context::new();
735        add_plan(&mut context, f64::INFINITY, 0);
736    }
737
738    #[test]
739    #[should_panic(expected = "Time is invalid")]
740    fn nan_plan_time() {
741        let mut context = Context::new();
742        add_plan(&mut context, f64::NAN, 0);
743    }
744
745    #[test]
746    fn timed_plan_only() {
747        let mut context = Context::new();
748        add_plan(&mut context, 1.0, 1);
749        context.execute();
750        assert_eq!(context.get_current_time(), 1.0);
751        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
752    }
753
754    #[test]
755    fn callback_only() {
756        let mut context = Context::new();
757        context.queue_callback(|context| {
758            context.get_data_mut(ComponentA).push(1);
759        });
760        context.execute();
761        assert_eq!(context.get_current_time(), 0.0);
762        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
763    }
764
765    #[test]
766    fn callback_before_timed_plan() {
767        let mut context = Context::new();
768        context.queue_callback(|context| {
769            context.get_data_mut(ComponentA).push(1);
770        });
771        add_plan(&mut context, 1.0, 2);
772        context.execute();
773        assert_eq!(context.get_current_time(), 1.0);
774        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
775    }
776
777    #[test]
778    fn callback_adds_timed_plan() {
779        let mut context = Context::new();
780        context.queue_callback(|context| {
781            context.get_data_mut(ComponentA).push(1);
782            add_plan(context, 1.0, 2);
783            context.get_data_mut(ComponentA).push(3);
784        });
785        context.execute();
786        assert_eq!(context.get_current_time(), 1.0);
787        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 2]);
788    }
789
790    #[test]
791    fn callback_adds_callback_and_timed_plan() {
792        let mut context = Context::new();
793        context.queue_callback(|context| {
794            context.get_data_mut(ComponentA).push(1);
795            add_plan(context, 1.0, 2);
796            context.queue_callback(|context| {
797                context.get_data_mut(ComponentA).push(4);
798            });
799            context.get_data_mut(ComponentA).push(3);
800        });
801        context.execute();
802        assert_eq!(context.get_current_time(), 1.0);
803        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 4, 2]);
804    }
805
806    #[test]
807    fn timed_plan_adds_callback_and_timed_plan() {
808        let mut context = Context::new();
809        context.add_plan(1.0, |context| {
810            context.get_data_mut(ComponentA).push(1);
811            // We add the plan first, but the callback will fire first.
812            add_plan(context, 2.0, 3);
813            context.queue_callback(|context| {
814                context.get_data_mut(ComponentA).push(2);
815            });
816        });
817        context.execute();
818        assert_eq!(context.get_current_time(), 2.0);
819        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
820    }
821
822    #[test]
823    fn cancel_plan() {
824        let mut context = Context::new();
825        let to_cancel = add_plan(&mut context, 2.0, 1);
826        context.add_plan(1.0, move |context| {
827            context.cancel_plan(&to_cancel);
828        });
829        context.execute();
830        assert_eq!(context.get_current_time(), 1.0);
831        let test_vec: Vec<u32> = vec![];
832        assert_eq!(*context.get_data_mut(ComponentA), test_vec);
833    }
834
835    #[test]
836    fn add_plan_with_current_time() {
837        let mut context = Context::new();
838        context.add_plan(1.0, move |context| {
839            context.get_data_mut(ComponentA).push(1);
840            add_plan(context, 1.0, 2);
841            context.queue_callback(|context| {
842                context.get_data_mut(ComponentA).push(3);
843            });
844        });
845        context.execute();
846        assert_eq!(context.get_current_time(), 1.0);
847        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 2]);
848    }
849
850    #[test]
851    fn plans_at_same_time_fire_in_order() {
852        let mut context = Context::new();
853        add_plan(&mut context, 1.0, 1);
854        add_plan(&mut context, 1.0, 2);
855        context.execute();
856        assert_eq!(context.get_current_time(), 1.0);
857        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
858    }
859
860    #[test]
861    fn check_plan_phase_ordering() {
862        assert!(ExecutionPhase::First < ExecutionPhase::Normal);
863        assert!(ExecutionPhase::Normal < ExecutionPhase::Last);
864    }
865
866    #[test]
867    fn plans_at_same_time_follow_phase() {
868        let mut context = Context::new();
869        add_plan(&mut context, 1.0, 1);
870        add_plan_with_phase(&mut context, 1.0, 5, ExecutionPhase::Last);
871        add_plan_with_phase(&mut context, 1.0, 3, ExecutionPhase::First);
872        add_plan(&mut context, 1.0, 2);
873        add_plan_with_phase(&mut context, 1.0, 6, ExecutionPhase::Last);
874        add_plan_with_phase(&mut context, 1.0, 4, ExecutionPhase::First);
875        context.execute();
876        assert_eq!(context.get_current_time(), 1.0);
877        assert_eq!(*context.get_data_mut(ComponentA), vec![3, 4, 1, 2, 5, 6]);
878    }
879
880    #[derive(Copy, Clone, IxaEvent)]
881    struct Event1 {
882        pub data: usize,
883    }
884
885    #[derive(Copy, Clone, IxaEvent)]
886    struct Event2 {
887        pub data: usize,
888    }
889
890    #[test]
891    fn simple_event() {
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
896        context.subscribe_to_event::<Event1>(move |_, event| {
897            *obs_data_clone.borrow_mut() = event.data;
898        });
899
900        context.emit_event(Event1 { data: 1 });
901        context.execute();
902        assert_eq!(*obs_data.borrow(), 1);
903    }
904
905    #[test]
906    fn multiple_events() {
907        let mut context = Context::new();
908        let obs_data = Rc::new(RefCell::new(0));
909        let obs_data_clone = Rc::clone(&obs_data);
910
911        context.subscribe_to_event::<Event1>(move |_, event| {
912            *obs_data_clone.borrow_mut() += event.data;
913        });
914
915        context.emit_event(Event1 { data: 1 });
916        context.emit_event(Event1 { data: 2 });
917        context.execute();
918
919        // Both of these should have been received.
920        assert_eq!(*obs_data.borrow(), 3);
921    }
922
923    #[test]
924    fn multiple_event_handlers() {
925        let mut context = Context::new();
926        let obs_data1 = Rc::new(RefCell::new(0));
927        let obs_data1_clone = Rc::clone(&obs_data1);
928        let obs_data2 = Rc::new(RefCell::new(0));
929        let obs_data2_clone = Rc::clone(&obs_data2);
930
931        context.subscribe_to_event::<Event1>(move |_, event| {
932            *obs_data1_clone.borrow_mut() = event.data;
933        });
934        context.subscribe_to_event::<Event1>(move |_, event| {
935            *obs_data2_clone.borrow_mut() = event.data;
936        });
937        context.emit_event(Event1 { data: 1 });
938        context.execute();
939        assert_eq!(*obs_data1.borrow(), 1);
940        assert_eq!(*obs_data2.borrow(), 1);
941    }
942
943    #[test]
944    fn multiple_event_types() {
945        let mut context = Context::new();
946        let obs_data1 = Rc::new(RefCell::new(0));
947        let obs_data1_clone = Rc::clone(&obs_data1);
948        let obs_data2 = Rc::new(RefCell::new(0));
949        let obs_data2_clone = Rc::clone(&obs_data2);
950
951        context.subscribe_to_event::<Event1>(move |_, event| {
952            *obs_data1_clone.borrow_mut() = event.data;
953        });
954        context.subscribe_to_event::<Event2>(move |_, event| {
955            *obs_data2_clone.borrow_mut() = event.data;
956        });
957        context.emit_event(Event1 { data: 1 });
958        context.emit_event(Event2 { data: 2 });
959        context.execute();
960        assert_eq!(*obs_data1.borrow(), 1);
961        assert_eq!(*obs_data2.borrow(), 2);
962    }
963
964    #[test]
965    fn subscribe_after_event() {
966        let mut context = Context::new();
967        let obs_data = Rc::new(RefCell::new(0));
968        let obs_data_clone = Rc::clone(&obs_data);
969
970        context.emit_event(Event1 { data: 1 });
971        context.subscribe_to_event::<Event1>(move |_, event| {
972            *obs_data_clone.borrow_mut() = event.data;
973        });
974
975        context.execute();
976        assert_eq!(*obs_data.borrow(), 0);
977    }
978
979    #[test]
980    fn shutdown_cancels_plans() {
981        let mut context = Context::new();
982        add_plan(&mut context, 1.0, 1);
983        context.add_plan(1.5, Context::shutdown);
984        add_plan(&mut context, 2.0, 2);
985        context.execute();
986        assert_eq!(context.get_current_time(), 1.5);
987        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
988    }
989
990    #[test]
991    fn shutdown_cancels_callbacks() {
992        let mut context = Context::new();
993        add_plan(&mut context, 1.0, 1);
994        context.add_plan(1.5, |context| {
995            // Note that we add the callback *before* we call shutdown
996            // but shutdown cancels everything.
997            context.queue_callback(|context| {
998                context.get_data_mut(ComponentA).push(3);
999            });
1000            context.shutdown();
1001        });
1002        context.execute();
1003        assert_eq!(context.get_current_time(), 1.5);
1004        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1005    }
1006
1007    #[test]
1008    fn shutdown_cancels_events() {
1009        let mut context = Context::new();
1010        let obs_data = Rc::new(RefCell::new(0));
1011        let obs_data_clone = Rc::clone(&obs_data);
1012        context.subscribe_to_event::<Event1>(move |_, event| {
1013            *obs_data_clone.borrow_mut() = event.data;
1014        });
1015        context.emit_event(Event1 { data: 1 });
1016        context.shutdown();
1017        context.execute();
1018        assert_eq!(*obs_data.borrow(), 0);
1019    }
1020
1021    #[test]
1022    #[allow(clippy::cast_sign_loss)]
1023    #[allow(clippy::cast_possible_truncation)]
1024    fn periodic_plan_self_schedules() {
1025        // checks whether the person properties report schedules itself
1026        // based on whether there are plans in the queue
1027        let mut context = Context::new();
1028        context.add_periodic_plan_with_phase(
1029            1.0,
1030            |context| {
1031                let time = context.get_current_time();
1032                context.get_data_mut(ComponentA).push(time as u32);
1033            },
1034            ExecutionPhase::Last,
1035        );
1036        context.add_plan(1.0, move |_context| {});
1037        context.add_plan(1.5, move |_context| {});
1038        context.execute();
1039        assert_eq!(context.get_current_time(), 2.0);
1040
1041        assert_eq!(*context.get_data(ComponentA), vec![0, 1, 2]); // time 0.0, 1.0, and 2.0
1042    }
1043}