Skip to main content

ixa/
context.rs

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