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::marker::PhantomData;
10use std::rc::Rc;
11
12use crate::data_plugin::DataPlugin;
13use crate::entity::entity_store::EntityStore;
14use crate::entity::multi_property::emit_pre_main_diagnostics;
15#[cfg(feature = "profiling")]
16use crate::entity::multi_property::QueryIdentityId;
17use crate::entity::property::Property;
18use crate::entity::property_value_store_core::PropertyValueStoreCore;
19use crate::entity::Entity;
20use crate::execution_stats::{
21    log_execution_statistics, print_execution_statistics, ExecutionProfilingCollector,
22    ExecutionStatistics,
23};
24use crate::global_properties::get_global_property_count;
25use crate::plan_queue::{PlanId, PlanQueue};
26#[cfg(feature = "profiling")]
27use crate::profiling::{
28    print_profiling_data, print_query_timings, QueryProfileHandle, QueryProfilingData,
29};
30use crate::{get_data_plugin_count, trace, warn, HashMap, HashMapExt};
31
32/// The common callback used by multiple [`Context`] methods for future events
33type Callback = dyn FnOnce(&mut Context);
34
35/// A handler for an event type `E`
36type EventHandler<E> = dyn Fn(&mut Context, E);
37
38/// An opaque token for a registered event listener.
39///
40/// Pass this token to [`Context::unsubscribe_from_event`] to stop the listener
41/// from receiving future emissions of the same event type.
42#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
43pub struct EventListenerId<E: IxaEvent> {
44    id: usize,
45    event_type: PhantomData<fn() -> E>,
46}
47
48impl<E: IxaEvent> EventListenerId<E> {
49    fn new(id: usize) -> Self {
50        Self {
51            id,
52            event_type: PhantomData,
53        }
54    }
55}
56
57struct EventHandlerRegistration<E: IxaEvent> {
58    listener_id: EventListenerId<E>,
59    handler: Rc<EventHandler<E>>,
60}
61
62pub trait IxaEvent: Copy + 'static {
63    /// Called after [`Context::subscribe_to_event`] registers a listener for
64    /// this event type.
65    fn on_subscribe(_context: &mut Context) {}
66
67    /// Called after [`Context::unsubscribe_from_event`] successfully removes a
68    /// listener for this event type.
69    fn on_unsubscribe(_context: &mut Context) {}
70}
71
72/// An enum to indicate the phase for plans at a given time.
73///
74/// Most plans will occur as `Normal`. Plans with phase `First` are
75/// handled before all `Normal` plans, and those with phase `Last` are
76/// handled after all `Normal` plans. In all cases ties between plans at the
77/// same time and with the same phase are handled in the order of scheduling.
78///
79#[derive(PartialEq, Eq, Ord, Clone, Copy, PartialOrd, Hash, Debug)]
80pub enum ExecutionPhase {
81    First,
82    Normal,
83    Last,
84}
85
86impl Display for ExecutionPhase {
87    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
88        write!(f, "{self:?}")
89    }
90}
91
92/// Tracks event-loop shutdown state and the current shutdown lifecycle phase.
93///
94/// This is private implementation state, not public API. `Context::shutdown`
95/// requests normal shutdown and `Context::abort` requests an immediate stop of
96/// the current `execute` loop. The stopped status is deliberately cleared when a
97/// later `execute` call begins.
98#[derive(Clone, Copy, Debug, Eq, PartialEq)]
99enum ShutdownStatus {
100    /// Normal execution; no shutdown has been requested.
101    None,
102    /// Normal shutdown requested or in progress.
103    ///
104    /// In this state, callbacks still run first, but regular plans are executed
105    /// only if they are scheduled at `Context::current_time`. Simulation time is
106    /// not advanced.
107    Normal,
108    /// Drain the distinguished shutdown-time plan queue.
109    ///
110    /// Once this state is reached, regular plans are not inspected again during
111    /// the same execution pass, even if shutdown-time work schedules a regular
112    /// plan at the current simulation time. Callbacks are still executed.
113    ShutdownTimePlans,
114    /// Stop the current `execute` event loop.
115    ///
116    /// This is set by `Context::abort` and when the shutdown-time queue is
117    /// exhausted. Manual `execute_single_step` calls clear this state when there
118    /// is no callback to run.
119    Stopped,
120}
121
122/// A manager for the state of a discrete-event simulation
123///
124/// Provides core simulation services including
125/// * Maintaining a notion of time
126/// * Scheduling events to occur at some point in the future and executing them
127///   at that time
128/// * Holding data that can be accessed by simulation modules
129///
130/// Simulations are constructed out of a series of interacting modules that
131/// take turns manipulating the [`Context`] through a mutable reference. Modules
132/// store data in the simulation using the [`DataPlugin`] trait that allows them
133/// to retrieve data by type.
134///
135/// The future event list of the simulation is a queue of `Callback` objects -
136/// called `plans` - that will assume control of the [`Context`] at a future point
137/// in time and execute the logic in the associated `FnOnce(&mut Context)`
138/// closure. Modules can add plans to this queue through the [`Context`].
139///
140/// The simulation also has a separate callback mechanism. Callbacks
141/// fire before the next timed event (even if it is scheduled for the
142/// current time). This allows modules to schedule actions for immediate
143/// execution but outside of the current iteration of the event loop.
144///
145/// Modules can also emit 'events' that other modules can subscribe to handle by
146/// event type. This allows modules to broadcast that specific things have
147/// occurred and have other modules take turns reacting to these occurrences.
148///
149pub struct Context {
150    plan_queue: PlanQueue,
151    callback_queue: VecDeque<Box<Callback>>,
152    event_handlers: HashMap<TypeId, Box<dyn Any>>,
153    next_event_listener_id: usize,
154    pub(crate) entity_store: EntityStore,
155    data_plugins: Vec<OnceCell<Box<dyn Any>>>,
156    pub(crate) global_properties: Vec<OnceCell<Box<dyn Any>>>,
157    current_time: Option<f64>,
158    start_time: Option<f64>,
159    shutdown_status: ShutdownStatus,
160    execution_profiler: ExecutionProfilingCollector,
161    pub(crate) print_execution_statistics: bool,
162}
163
164impl Context {
165    /// Create a new empty `Context`
166    #[must_use]
167    pub fn new() -> Context {
168        emit_pre_main_diagnostics();
169
170        // Create a vector to accommodate all registered data plugins
171        let data_plugins = std::iter::repeat_with(OnceCell::new)
172            .take(get_data_plugin_count())
173            .collect();
174        let global_properties = std::iter::repeat_with(OnceCell::new)
175            .take(get_global_property_count())
176            .collect();
177
178        Context {
179            plan_queue: PlanQueue::new(),
180            callback_queue: VecDeque::new(),
181            event_handlers: HashMap::new(),
182            next_event_listener_id: 0,
183            entity_store: EntityStore::new(),
184            data_plugins,
185            global_properties,
186            current_time: None,
187            start_time: None,
188            shutdown_status: ShutdownStatus::None,
189            execution_profiler: ExecutionProfilingCollector::new(),
190            print_execution_statistics: false,
191        }
192    }
193
194    #[cfg(feature = "profiling")]
195    pub(crate) fn query_profile_handle(&self, identity: QueryIdentityId) -> QueryProfileHandle<'_> {
196        QueryProfileHandle::new(&self.execution_profiler.query_profiler, identity)
197    }
198
199    #[cfg(feature = "profiling")]
200    pub(crate) fn query_profiling_snapshot(&self) -> Vec<(&'static str, QueryProfilingData)> {
201        self.execution_profiler.query_profiler.snapshot()
202    }
203
204    #[cfg(all(test, feature = "profiling"))]
205    pub(crate) fn query_profiling_data(
206        &self,
207        identity: QueryIdentityId,
208    ) -> Option<QueryProfilingData> {
209        self.execution_profiler
210            .query_profiler
211            .query_profiling_data(identity)
212    }
213
214    #[inline]
215    pub(crate) fn get_property_value_store<E: Entity, P: Property<E>>(
216        &self,
217    ) -> &PropertyValueStoreCore<E, P> {
218        self.entity_store.get_property_store::<E>().get::<P>()
219    }
220    pub(crate) fn get_property_value_store_mut<E: Entity, P: Property<E>>(
221        &mut self,
222    ) -> &mut PropertyValueStoreCore<E, P> {
223        self.entity_store
224            .get_property_store_mut::<E>()
225            .get_mut::<P>()
226    }
227
228    /// Register to handle emission of events of type E
229    ///
230    /// Handlers will be called upon event emission in order of subscription as
231    /// queued `Callback`s with the appropriate event.
232    pub fn subscribe_to_event<E: IxaEvent>(
233        &mut self,
234        handler: impl Fn(&mut Context, E) + 'static,
235    ) -> EventListenerId<E> {
236        let listener_id = EventListenerId::new(self.next_event_listener_id);
237        self.next_event_listener_id = self
238            .next_event_listener_id
239            .checked_add(1)
240            .expect("Ixa internal error: event listener id overflow");
241
242        let handler_vec = self
243            .event_handlers
244            .entry(TypeId::of::<E>())
245            .or_insert_with(|| Box::<Vec<EventHandlerRegistration<E>>>::default());
246        let handler_vec: &mut Vec<EventHandlerRegistration<E>> = handler_vec
247            .downcast_mut()
248            .expect("Ixa internal error: event handler registry type mismatch");
249        handler_vec.push(EventHandlerRegistration {
250            listener_id,
251            handler: Rc::new(handler),
252        });
253        E::on_subscribe(self);
254        listener_id
255    }
256
257    /// Unsubscribe a previously registered event listener.
258    ///
259    /// Returns `true` if a listener was unsubscribed and `false` if the token is
260    /// unknown, already unsubscribed, or otherwise absent.
261    #[allow(clippy::missing_panics_doc)]
262    pub fn unsubscribe_from_event<E: IxaEvent>(
263        &mut self,
264        listener_id: &EventListenerId<E>,
265    ) -> bool {
266        {
267            let Some(handler_vec) = self.event_handlers.get_mut(&TypeId::of::<E>()) else {
268                return false;
269            };
270            let handler_vec: &mut Vec<EventHandlerRegistration<E>> = handler_vec
271                .downcast_mut()
272                .expect("Ixa internal error: event handler registry type mismatch");
273            let Some(index) = handler_vec
274                .iter()
275                .position(|entry| entry.listener_id.id == listener_id.id)
276            else {
277                return false;
278            };
279
280            handler_vec.swap_remove(index);
281        }
282
283        E::on_unsubscribe(self);
284        true
285    }
286
287    pub(crate) fn has_event_handlers<E: IxaEvent>(&self) -> bool {
288        self.event_handlers
289            .get(&TypeId::of::<E>())
290            .is_some_and(|handler_vec| {
291                let handler_vec: &Vec<EventHandlerRegistration<E>> = handler_vec
292                    .downcast_ref()
293                    .expect("Ixa internal error: event handler registry type mismatch");
294                !handler_vec.is_empty()
295            })
296    }
297
298    /// Emit an event of type E to be handled by registered receivers
299    ///
300    /// Receivers will handle events in the order that they have subscribed and
301    /// are queued as callbacks
302    pub fn emit_event<E: IxaEvent>(&mut self, event: E) {
303        // Destructure to obtain event handlers and plan queue
304        let Context {
305            event_handlers,
306            callback_queue,
307            ..
308        } = self;
309        if let Some(handler_vec) = event_handlers.get(&TypeId::of::<E>()) {
310            let handler_vec: &Vec<EventHandlerRegistration<E>> = handler_vec
311                .downcast_ref()
312                .expect("Ixa internal error: event handler registry type mismatch");
313            for registration in handler_vec {
314                let handler_clone = Rc::clone(&registration.handler);
315                callback_queue.push_back(Box::new(move |context| handler_clone(context, event)));
316            }
317        }
318    }
319
320    /// Add a plan to the future event list at the specified time in the normal
321    /// phase
322    ///
323    /// The supplied time is converted to `f64` before validation.
324    ///
325    /// ```
326    /// use ixa::Context;
327    ///
328    /// struct ModelTime(f64);
329    ///
330    /// impl From<ModelTime> for f64 {
331    ///     fn from(time: ModelTime) -> Self {
332    ///         time.0
333    ///     }
334    /// }
335    ///
336    /// let mut context = Context::new();
337    /// context.add_plan(ModelTime(1.0), |_| {});
338    /// ```
339    ///
340    /// Returns a [`PlanId`] for the newly-added plan that can be used to cancel it
341    /// if needed.
342    /// # Panics
343    ///
344    /// Panics if time is in the past, infinite, or NaN.
345    pub fn add_plan(
346        &mut self,
347        time: impl Into<f64>,
348        callback: impl FnOnce(&mut Context) + 'static,
349    ) -> PlanId {
350        self.add_plan_with_phase(time, callback, ExecutionPhase::Normal)
351    }
352
353    /// Add a plan to the future event list at the specified time and with the
354    /// specified phase (first, normal, or last among plans at the
355    /// specified time)
356    ///
357    /// The supplied time is converted to `f64` before validation.
358    ///
359    /// Returns a [`PlanId`] for the newly-added plan that can be used to cancel it
360    /// if needed.
361    /// # Panics
362    ///
363    /// Panics if time is in the past, infinite, or NaN.
364    pub fn add_plan_with_phase(
365        &mut self,
366        time: impl Into<f64>,
367        callback: impl FnOnce(&mut Context) + 'static,
368        phase: ExecutionPhase,
369    ) -> PlanId {
370        self.add_plan_with_phase_and_passivity(time.into(), callback, phase, false)
371    }
372
373    /// Add a passive plan to the future event list at the specified time in the
374    /// normal phase.
375    ///
376    /// Passive plans execute like regular plans but do not keep the simulation
377    /// timeline alive.
378    ///
379    /// The supplied time is converted to `f64` before validation.
380    ///
381    /// Returns a [`PlanId`] for the newly-added plan that can be used to cancel it
382    /// if needed.
383    /// # Panics
384    ///
385    /// Panics if time is in the past, infinite, or NaN.
386    pub fn add_passive_plan(
387        &mut self,
388        time: impl Into<f64>,
389        callback: impl FnOnce(&mut Context) + 'static,
390    ) -> PlanId {
391        self.add_passive_plan_with_phase(time, callback, ExecutionPhase::Normal)
392    }
393
394    /// Schedule [`Context::shutdown`] at the specified maximum simulation time.
395    ///
396    /// The shutdown request is scheduled through [`Context::add_passive_plan`],
397    /// so it does not keep the simulation timeline alive when non-passive work is
398    /// exhausted before `time`. If execution reaches `time`, normal shutdown
399    /// finishes queued callbacks and regular plans at that time before running
400    /// shutdown-time plans. If execution ends earlier, the future passive
401    /// shutdown plan remains queued like any other future passive plan.
402    ///
403    /// This schedules the shutdown request itself. Use [`Context::add_shutdown_plan`]
404    /// to schedule work that should run during normal shutdown.
405    ///
406    /// Returns a [`PlanId`] that can be passed to [`Context::cancel_plan`].
407    ///
408    /// # Examples
409    ///
410    /// ```
411    /// use ixa::Context;
412    ///
413    /// let mut context = Context::new();
414    /// context.schedule_shutdown(10.0);
415    /// context.add_plan(20.0, |_| {});
416    ///
417    /// context.execute();
418    /// assert_eq!(context.get_current_time(), 10.0);
419    /// ```
420    ///
421    /// # Panics
422    ///
423    /// Panics if `time` is in the past, infinite, or NaN.
424    pub fn schedule_shutdown(&mut self, time: f64) -> PlanId {
425        self.add_passive_plan(time, Context::shutdown)
426    }
427
428    /// Add a passive plan to the future event list at the specified time and
429    /// with the specified phase.
430    ///
431    /// Passive plans execute like regular plans but do not keep the simulation
432    /// timeline alive.
433    ///
434    /// The supplied time is converted to `f64` before validation.
435    ///
436    /// Returns a [`PlanId`] for the newly-added plan that can be used to cancel it
437    /// if needed.
438    /// # Panics
439    ///
440    /// Panics if time is in the past, infinite, or NaN.
441    pub fn add_passive_plan_with_phase(
442        &mut self,
443        time: impl Into<f64>,
444        callback: impl FnOnce(&mut Context) + 'static,
445        phase: ExecutionPhase,
446    ) -> PlanId {
447        self.add_plan_with_phase_and_passivity(time.into(), callback, phase, true)
448    }
449
450    fn add_plan_with_phase_and_passivity(
451        &mut self,
452        time: f64,
453        callback: impl FnOnce(&mut Context) + 'static,
454        phase: ExecutionPhase,
455        is_passive: bool,
456    ) -> PlanId {
457        let current = self.get_current_time();
458        assert!(!time.is_nan(), "Time {time} is invalid: cannot be NaN");
459        assert!(
460            !time.is_infinite(),
461            "Time {time} is invalid: cannot be infinite"
462        );
463        assert!(
464            time >= current,
465            "Time {time} is invalid: cannot be less than the current time ({}). Consider calling set_start_time() before scheduling plans.",
466            current
467        );
468        self.plan_queue
469            .add_plan(time, Box::new(callback), phase, is_passive)
470    }
471
472    /// Add a plan to execute during shutdown-time in the normal phase.
473    ///
474    /// Shutdown-time plans execute after regular plans at the current simulation
475    /// time are exhausted during normal shutdown, and after natural exhaustion of
476    /// the regular plan queue.
477    ///
478    /// Returns a [`PlanId`] for the newly-added plan that can be used to cancel it
479    /// if needed.
480    pub fn add_shutdown_plan(&mut self, callback: impl FnOnce(&mut Context) + 'static) -> PlanId {
481        self.add_shutdown_plan_with_phase(callback, ExecutionPhase::Normal)
482    }
483
484    /// Add a plan to execute during shutdown-time with the specified phase.
485    ///
486    /// Shutdown-time plans have no simulation time. They are ordered by phase and
487    /// insertion order.
488    ///
489    /// Returns a [`PlanId`] for the newly-added plan that can be used to cancel it
490    /// if needed.
491    pub fn add_shutdown_plan_with_phase(
492        &mut self,
493        callback: impl FnOnce(&mut Context) + 'static,
494        phase: ExecutionPhase,
495    ) -> PlanId {
496        self.plan_queue.add_shutdown_plan(Box::new(callback), phase)
497    }
498
499    pub(crate) fn evaluate_periodic_and_schedule_next(
500        &mut self,
501        period: f64,
502        callback: impl Fn(&mut Context) + 'static,
503        phase: ExecutionPhase,
504    ) {
505        trace!(
506            "evaluate periodic at {} (period={})",
507            self.get_current_time(),
508            period
509        );
510        callback(self);
511        let next_time = self.get_current_time() + period;
512        self.add_passive_plan_with_phase(
513            next_time,
514            move |context| context.evaluate_periodic_and_schedule_next(period, callback, phase),
515            phase,
516        );
517    }
518
519    /// Add a passive periodic plan with specified priority to the future event
520    /// list.
521    ///
522    /// Periodic plans reschedule themselves after every run. They do not keep
523    /// the simulation timeline alive: when no non-passive plans remain, normal
524    /// shutdown begins, and only passive plans at the final current time can
525    /// still run during that execution pass. Future passive periodic plans
526    /// remain queued and may run if later non-passive work is scheduled.
527    ///
528    /// The supplied period is converted to `f64` before validation.
529    ///
530    /// Notes:
531    /// * The first periodic plan is scheduled at time `0.0`. If `set_start_time` was
532    ///   set to a positive value, this will currently panic because the first plan
533    ///   occurs before the start time (see issue #634 for future behavior).
534    ///
535    /// # Panics
536    ///
537    /// Panics if plan period is negative, infinite, or NaN.
538    pub fn add_periodic_plan_with_phase(
539        &mut self,
540        period: impl Into<f64>,
541        callback: impl Fn(&mut Context) + 'static,
542        phase: ExecutionPhase,
543    ) {
544        let period = period.into();
545        assert!(
546            period > 0.0 && !period.is_nan() && !period.is_infinite(),
547            "Period must be greater than 0"
548        );
549
550        self.add_passive_plan_with_phase(
551            0.0,
552            move |context| context.evaluate_periodic_and_schedule_next(period, callback, phase),
553            phase,
554        );
555    }
556
557    /// Cancel a plan that has been added to the queue
558    ///
559    /// # Panics
560    ///
561    /// This function panics if you cancel a plan which has already been
562    /// cancelled or executed.
563    pub fn cancel_plan(&mut self, plan_id: &PlanId) {
564        trace!("canceling plan {plan_id:?}");
565        let result = self.plan_queue.cancel_plan(plan_id);
566        if result.is_none() {
567            warn!("Tried to cancel nonexistent plan with ID = {plan_id:?}");
568        }
569    }
570
571    /// Add a `Callback` to the queue to be executed before the next plan
572    pub fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static) {
573        trace!("queuing callback");
574        self.callback_queue.push_back(Box::new(callback));
575    }
576
577    /// Retrieve a mutable reference to the data container associated with a
578    /// [`DataPlugin`]
579    ///
580    /// If the data container has not been already added to the [`Context`] then
581    /// this function will use the [`DataPlugin::init`] method
582    /// to construct a new data container and store it in the [`Context`].
583    ///
584    /// Returns a mutable reference to the data container
585    ///
586    /// # Panics
587    ///
588    /// Panics if `T` was not defined with [`define_data_plugin!`](crate::define_data_plugin).
589    #[must_use]
590    pub fn get_data_mut<T: DataPlugin>(&mut self, _data_plugin: T) -> &mut T::DataContainer {
591        let index = T::index_within_context();
592
593        // If the data plugin is already initialized, return a mutable reference.
594        if self.data_plugins[index].get().is_some() {
595            // The preceding check proves that the cell contains a value.
596            return self.data_plugins[index]
597                .get_mut()
598                .unwrap()
599                .downcast_mut::<T::DataContainer>()
600                .expect("TypeID does not match data plugin type");
601        }
602
603        // Initialize the data plugin if not already initialized.
604        let data = T::init(self);
605        let cell = self
606            .data_plugins
607            .get_mut(index)
608            .unwrap_or_else(|| panic!("No data plugin found with index = {index:?}. You must use the `define_data_plugin!` macro to create a data plugin."));
609        let _ = cell.set(Box::new(data));
610        // `cell` was empty and has just been initialized above.
611        cell.get_mut()
612            .unwrap()
613            .downcast_mut::<T::DataContainer>()
614            .expect("TypeID does not match data plugin type. You must use the `define_data_plugin!` macro to create a data plugin.")
615    }
616
617    /// Retrieve a reference to the data container associated with a
618    /// [`DataPlugin`]
619    ///
620    /// Returns a reference to the data container if it exists or else `None`
621    ///
622    /// # Panics
623    ///
624    /// Panics if `T` was not defined with [`define_data_plugin!`](crate::define_data_plugin).
625    #[must_use]
626    pub fn get_data<T: DataPlugin>(&self, _data_plugin: T) -> &T::DataContainer {
627        let index = T::index_within_context();
628        self.data_plugins
629            .get(index)
630            .unwrap_or_else(|| panic!("No data plugin found with index = {index:?}. You must use the `define_data_plugin!` macro to create a data plugin."))
631            .get_or_init(|| Box::new(T::init(self)))
632            .downcast_ref::<T::DataContainer>()
633            .expect("TypeID does not match data plugin type. You must use the `define_data_plugin!` macro to create a data plugin.")
634    }
635
636    /// Request normal shutdown.
637    ///
638    /// Normal shutdown stops simulation time from advancing. Execution continues
639    /// through queued callbacks, regular plans at the current time, and then
640    /// shutdown-time plans. Calling `shutdown` during shutdown-time execution
641    /// does not return execution to regular current-time plans.
642    pub fn shutdown(&mut self) {
643        trace!("shutdown context");
644        if self.shutdown_status == ShutdownStatus::None {
645            self.shutdown_status = ShutdownStatus::Normal;
646        }
647    }
648
649    /// Stop the current event loop immediately.
650    ///
651    /// Abort only stops the current `execute` loop. The stopped status is cleared
652    /// when `execute` is called again.
653    pub fn abort(&mut self) {
654        trace!("abort context");
655        self.shutdown_status = ShutdownStatus::Stopped;
656    }
657
658    /// Get the current simulation time
659    ///
660    /// Returns the current time in the simulation. The behavior depends on execution state:
661    /// * During execution: returns the time of the currently executing plan or callback
662    /// * Before execution: returns the start time (if set via [`Context::set_start_time`]), or `0.0`
663    ///
664    /// The time can be negative if a negative start time was set before execution.
665    #[must_use]
666    pub fn get_current_time(&self) -> f64 {
667        self.current_time.or(self.start_time).unwrap_or(0.0)
668    }
669
670    /// Set the start time for the simulation. Must be finite.
671    ///
672    /// The supplied start time is converted to `f64` before validation.
673    ///
674    /// * Call before `Context.execute()`.
675    /// * `start_time` must be finite (not NaN or infinite).
676    /// * May be called only once.
677    /// * If plans are already scheduled, `start_time` must be earlier than or equal to
678    ///   the earliest scheduled plan time.
679    ///
680    /// # Panics
681    ///
682    /// Panics if:
683    /// * `start_time` is NaN or infinite.
684    /// * the start time was already set.
685    /// * `Context::execute()` has been called.
686    /// * `start_time` is later than the earliest scheduled plan time.
687    pub fn set_start_time(&mut self, start_time: impl Into<f64>) {
688        let start_time = start_time.into();
689        assert!(
690            !start_time.is_nan() && !start_time.is_infinite(),
691            "Start time {start_time} must be finite"
692        );
693        assert!(
694            self.start_time.is_none(),
695            "Start time has already been set. It can only be set once."
696        );
697        assert!(
698            self.current_time.is_none(),
699            "Start time cannot be set after execution has begun."
700        );
701        if let Some(next_time) = self.plan_queue.next_time() {
702            assert!(
703                start_time <= next_time,
704                "Start time {} is later than the earliest scheduled plan time {}. Remove or reschedule existing plans first.",
705                start_time,
706                next_time
707            );
708        }
709        self.start_time = Some(start_time);
710    }
711
712    /// Get the start time that was set via `set_start_time`, or `None` if not set.
713    #[must_use]
714    pub fn get_start_time(&self) -> Option<f64> {
715        self.start_time
716    }
717
718    /// Execute the simulation until callbacks and plans are exhausted and shutdown
719    /// work is complete.
720    pub fn execute(&mut self) {
721        trace!("entering event loop");
722
723        if self.shutdown_status == ShutdownStatus::Stopped {
724            self.shutdown_status = ShutdownStatus::None;
725        }
726
727        if self.current_time.is_none() {
728            self.current_time = Some(self.start_time.unwrap_or(0.0));
729        }
730
731        // Start plan loop
732        loop {
733            if self.shutdown_status == ShutdownStatus::Stopped {
734                self.shutdown_status = ShutdownStatus::None;
735                break;
736            }
737
738            self.execute_single_step();
739            self.execution_profiler.refresh();
740        }
741
742        let stats = self.get_execution_statistics();
743        if self.print_execution_statistics {
744            print_execution_statistics(&stats);
745            #[cfg(feature = "profiling")]
746            {
747                print_profiling_data();
748                print_query_timings(&self.query_profiling_snapshot());
749            }
750        } else {
751            log_execution_statistics(&stats);
752        }
753    }
754
755    /// Executes a single callback, plan, or shutdown status transition.
756    pub fn execute_single_step(&mut self) {
757        // Callbacks always have priority over plan selection. This remains true
758        // even in `Stopped` during manual stepping; `Stopped` only stops the
759        // `execute` loop, not the ability to explicitly step callbacks manually.
760        if let Some(callback) = self.callback_queue.pop_front() {
761            trace!("calling callback");
762            callback(self);
763            return;
764        }
765
766        // No callback is available, so the shutdown status determines which
767        // plan queue, if any, can provide the next unit of work.
768        match self.shutdown_status {
769            ShutdownStatus::None => {
770                // Normal execution may advance simulation time to the next
771                // regular plan only while non-passive regular work remains. Once no
772                // non-passive regular plans remain, enter normal shutdown to drain
773                // current-time regular work without advancing time.
774                if let Some(plan) = self.plan_queue.pop_next_if_active() {
775                    trace!("calling plan at {:.6}", plan.time);
776                    self.current_time = Some(plan.time);
777                    (plan.data)(self);
778                } else {
779                    self.shutdown_status = ShutdownStatus::Normal;
780                }
781            }
782            ShutdownStatus::Normal => {
783                // Normal shutdown drains only regular plans scheduled at the
784                // current simulation time. Future regular plans must remain in
785                // the queue so a later `execute` call can run them.
786                if let Some(plan) = self.plan_queue.pop_next_at(self.get_current_time()) {
787                    trace!("calling plan at {:.6}", plan.time);
788                    (plan.data)(self);
789                } else {
790                    self.shutdown_status = ShutdownStatus::ShutdownTimePlans;
791                }
792            }
793            ShutdownStatus::ShutdownTimePlans => {
794                // Once shutdown-time draining begins, do not return to the
795                // regular plan queue during this execution pass.
796                if let Some(plan) = self.plan_queue.pop_next_shutdown() {
797                    trace!("calling shutdown-time plan");
798                    (plan.data)(self);
799                } else {
800                    self.shutdown_status = ShutdownStatus::Stopped;
801                }
802            }
803            ShutdownStatus::Stopped => {
804                // `execute` exits before calling `execute_single_step` in this
805                // state. This arm supports manual single-step use after a prior
806                // stop by consuming the stopped status when no callback exists.
807                self.shutdown_status = ShutdownStatus::None;
808            }
809        }
810    }
811
812    #[must_use]
813    pub fn get_execution_statistics(&mut self) -> ExecutionStatistics {
814        #[allow(unused_mut)]
815        let mut stats = self.execution_profiler.compute_final_statistics();
816        #[cfg(feature = "profiling")]
817        {
818            stats.max_plans_in_flight = self.plan_queue.max_plans_in_flight;
819            stats.max_plan_queue_memory_in_use = self.plan_queue.max_memory_in_use;
820        }
821        stats
822    }
823}
824
825pub trait ContextBase: Sized {
826    fn subscribe_to_event<E: IxaEvent>(
827        &mut self,
828        handler: impl Fn(&mut Context, E) + 'static,
829    ) -> EventListenerId<E>;
830    fn unsubscribe_from_event<E: IxaEvent>(&mut self, listener_id: &EventListenerId<E>) -> bool;
831    fn emit_event<E: IxaEvent>(&mut self, event: E);
832    fn add_plan(
833        &mut self,
834        time: impl Into<f64>,
835        callback: impl FnOnce(&mut Context) + 'static,
836    ) -> PlanId;
837    fn add_plan_with_phase(
838        &mut self,
839        time: impl Into<f64>,
840        callback: impl FnOnce(&mut Context) + 'static,
841        phase: ExecutionPhase,
842    ) -> PlanId;
843    fn add_passive_plan(
844        &mut self,
845        time: impl Into<f64>,
846        callback: impl FnOnce(&mut Context) + 'static,
847    ) -> PlanId;
848    fn add_passive_plan_with_phase(
849        &mut self,
850        time: impl Into<f64>,
851        callback: impl FnOnce(&mut Context) + 'static,
852        phase: ExecutionPhase,
853    ) -> PlanId;
854    /// Schedule normal shutdown as a passive plan at the specified maximum time.
855    ///
856    /// See [`Context::schedule_shutdown`] for full behavior and panic semantics.
857    fn schedule_shutdown(&mut self, time: f64) -> PlanId {
858        self.add_passive_plan(time, Context::shutdown)
859    }
860    fn add_shutdown_plan(&mut self, callback: impl FnOnce(&mut Context) + 'static) -> PlanId;
861    fn add_shutdown_plan_with_phase(
862        &mut self,
863        callback: impl FnOnce(&mut Context) + 'static,
864        phase: ExecutionPhase,
865    ) -> PlanId;
866    fn add_periodic_plan_with_phase(
867        &mut self,
868        period: impl Into<f64>,
869        callback: impl Fn(&mut Context) + 'static,
870        phase: ExecutionPhase,
871    );
872    fn cancel_plan(&mut self, plan_id: &PlanId);
873    fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static);
874    #[must_use]
875    fn get_data_mut<T: DataPlugin>(&mut self, plugin: T) -> &mut T::DataContainer;
876    #[must_use]
877    fn get_data<T: DataPlugin>(&self, plugin: T) -> &T::DataContainer;
878    #[must_use]
879    fn get_current_time(&self) -> f64;
880    #[must_use]
881    fn get_execution_statistics(&mut self) -> ExecutionStatistics;
882    fn abort(&mut self);
883}
884impl ContextBase for Context {
885    delegate::delegate! {
886        to self {
887            fn subscribe_to_event<E: IxaEvent>(&mut self, handler: impl Fn(&mut Context, E) + 'static) -> EventListenerId<E>;
888            fn unsubscribe_from_event<E: IxaEvent>(&mut self, listener_id: &EventListenerId<E>) -> bool;
889            fn emit_event<E: IxaEvent>(&mut self, event: E);
890            fn add_plan(&mut self, time: impl Into<f64>, callback: impl FnOnce(&mut Context) + 'static) -> PlanId;
891            fn add_plan_with_phase(&mut self, time: impl Into<f64>, callback: impl FnOnce(&mut Context) + 'static, phase: ExecutionPhase) -> PlanId;
892            fn add_passive_plan(&mut self, time: impl Into<f64>, callback: impl FnOnce(&mut Context) + 'static) -> PlanId;
893            fn add_passive_plan_with_phase(&mut self, time: impl Into<f64>, callback: impl FnOnce(&mut Context) + 'static, phase: ExecutionPhase) -> PlanId;
894            fn add_shutdown_plan(&mut self, callback: impl FnOnce(&mut Context) + 'static) -> PlanId;
895            fn add_shutdown_plan_with_phase(&mut self, callback: impl FnOnce(&mut Context) + 'static, phase: ExecutionPhase) -> PlanId;
896            fn add_periodic_plan_with_phase(&mut self, period: impl Into<f64>, callback: impl Fn(&mut Context) + 'static, phase: ExecutionPhase);
897            fn cancel_plan(&mut self, plan_id: &PlanId);
898            fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static);
899            fn get_data_mut<T: DataPlugin>(&mut self, plugin: T) -> &mut T::DataContainer;
900            fn get_data<T: DataPlugin>(&self, plugin: T) -> &T::DataContainer;
901            fn get_current_time(&self) -> f64;
902            fn get_execution_statistics(&mut self) -> ExecutionStatistics;
903            fn abort(&mut self);
904        }
905    }
906}
907
908impl Default for Context {
909    fn default() -> Self {
910        Self::new()
911    }
912}
913
914#[cfg(test)]
915mod tests {
916    // We allow defining items that are never used to test macros.
917    #![allow(dead_code)]
918    use std::cell::RefCell;
919    use std::marker::PhantomData;
920
921    use super::*;
922    use crate::{
923        define_data_plugin, define_entity, define_property, with, ContextEntitiesExt, IxaEvent,
924    };
925
926    #[derive(Clone, Copy)]
927    struct WrappedF64(f64);
928
929    impl From<WrappedF64> for f64 {
930        fn from(value: WrappedF64) -> Self {
931            value.0
932        }
933    }
934
935    define_data_plugin!(ComponentA, Vec<u32>, vec![]);
936    define_data_plugin!(UnsubscribeHookObservations, Vec<bool>, vec![]);
937
938    define_entity!(Person);
939
940    define_property!(struct Age(u8), Person);
941
942    define_property!(
943        enum InfectionStatus {
944            Susceptible,
945            Infected,
946            Recovered,
947        },
948        Person,
949        default_const = InfectionStatus::Susceptible
950    );
951
952    define_property!(
953        struct Vaccinated(bool),
954        Person,
955        default_const = Vaccinated(false)
956    );
957
958    #[test]
959    fn empty_context() {
960        let mut context = Context::new();
961        context.execute();
962        assert_eq!(context.get_current_time(), 0.0);
963    }
964
965    #[test]
966    fn get_data() {
967        let mut context = Context::new();
968        context.get_data_mut(ComponentA).push(1);
969        assert_eq!(*context.get_data(ComponentA), vec![1],);
970    }
971
972    fn add_plan(context: &mut Context, time: f64, value: u32) -> PlanId {
973        context.add_plan(time, move |context| {
974            context.get_data_mut(ComponentA).push(value);
975        })
976    }
977
978    fn add_plan_with_phase(
979        context: &mut Context,
980        time: f64,
981        value: u32,
982        phase: ExecutionPhase,
983    ) -> PlanId {
984        context.add_plan_with_phase(
985            time,
986            move |context| {
987                context.get_data_mut(ComponentA).push(value);
988            },
989            phase,
990        )
991    }
992
993    fn add_passive_plan(context: &mut Context, time: f64, value: u32) -> PlanId {
994        context.add_passive_plan(time, move |context| {
995            context.get_data_mut(ComponentA).push(value);
996        })
997    }
998
999    fn add_passive_plan_with_phase(
1000        context: &mut Context,
1001        time: f64,
1002        value: u32,
1003        phase: ExecutionPhase,
1004    ) -> PlanId {
1005        context.add_passive_plan_with_phase(
1006            time,
1007            move |context| {
1008                context.get_data_mut(ComponentA).push(value);
1009            },
1010            phase,
1011        )
1012    }
1013
1014    fn add_wrapped_plan_through_context_base(context: &mut impl ContextBase, value: u32) {
1015        context.add_plan(WrappedF64(0.0), move |context| {
1016            context.get_data_mut(ComponentA).push(value);
1017        });
1018    }
1019
1020    #[test]
1021    fn time_boundaries_accept_into_f64() {
1022        let mut context = Context::new();
1023        context.set_start_time(WrappedF64(0.0));
1024
1025        add_wrapped_plan_through_context_base(&mut context, 1);
1026        context.add_plan_with_phase(
1027            WrappedF64(0.0),
1028            |context| context.get_data_mut(ComponentA).push(2),
1029            ExecutionPhase::First,
1030        );
1031        context.add_passive_plan(WrappedF64(0.0), |context| {
1032            context.get_data_mut(ComponentA).push(3);
1033        });
1034        context.add_passive_plan_with_phase(
1035            WrappedF64(0.0),
1036            |context| context.get_data_mut(ComponentA).push(4),
1037            ExecutionPhase::Last,
1038        );
1039        context.add_periodic_plan_with_phase(
1040            WrappedF64(1.0),
1041            |context| context.get_data_mut(ComponentA).push(5),
1042            ExecutionPhase::Normal,
1043        );
1044
1045        context.execute();
1046
1047        let mut observed = context.get_data(ComponentA).clone();
1048        observed.sort_unstable();
1049        assert_eq!(observed, vec![1, 2, 3, 4, 5]);
1050        assert_eq!(context.get_start_time(), Some(0.0));
1051    }
1052
1053    #[test]
1054    #[should_panic(expected = "Time inf is invalid")]
1055    fn infinite_plan_time() {
1056        let mut context = Context::new();
1057        add_plan(&mut context, f64::INFINITY, 0);
1058    }
1059
1060    #[test]
1061    #[should_panic(expected = "Time NaN is invalid")]
1062    fn nan_plan_time() {
1063        let mut context = Context::new();
1064        add_plan(&mut context, f64::NAN, 0);
1065    }
1066
1067    #[test]
1068    #[should_panic(expected = "Time NaN is invalid")]
1069    fn wrapped_nan_plan_time_panics() {
1070        let mut context = Context::new();
1071        context.add_plan(WrappedF64(f64::NAN), |_| {});
1072    }
1073
1074    #[test]
1075    fn timed_plan_only() {
1076        let mut context = Context::new();
1077        add_plan(&mut context, 1.0, 1);
1078        context.execute();
1079        assert_eq!(context.get_current_time(), 1.0);
1080        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1081    }
1082
1083    #[test]
1084    fn callback_only() {
1085        let mut context = Context::new();
1086        context.queue_callback(|context| {
1087            context.get_data_mut(ComponentA).push(1);
1088        });
1089        context.execute();
1090        assert_eq!(context.get_current_time(), 0.0);
1091        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1092    }
1093
1094    #[test]
1095    fn callback_before_timed_plan() {
1096        let mut context = Context::new();
1097        context.queue_callback(|context| {
1098            context.get_data_mut(ComponentA).push(1);
1099        });
1100        add_plan(&mut context, 1.0, 2);
1101        context.execute();
1102        assert_eq!(context.get_current_time(), 1.0);
1103        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1104    }
1105
1106    #[test]
1107    fn callback_adds_timed_plan() {
1108        let mut context = Context::new();
1109        context.queue_callback(|context| {
1110            context.get_data_mut(ComponentA).push(1);
1111            add_plan(context, 1.0, 2);
1112            context.get_data_mut(ComponentA).push(3);
1113        });
1114        context.execute();
1115        assert_eq!(context.get_current_time(), 1.0);
1116        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 2]);
1117    }
1118
1119    #[test]
1120    fn callback_adds_callback_and_timed_plan() {
1121        let mut context = Context::new();
1122        context.queue_callback(|context| {
1123            context.get_data_mut(ComponentA).push(1);
1124            add_plan(context, 1.0, 2);
1125            context.queue_callback(|context| {
1126                context.get_data_mut(ComponentA).push(4);
1127            });
1128            context.get_data_mut(ComponentA).push(3);
1129        });
1130        context.execute();
1131        assert_eq!(context.get_current_time(), 1.0);
1132        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 4, 2]);
1133    }
1134
1135    #[test]
1136    fn timed_plan_adds_callback_and_timed_plan() {
1137        let mut context = Context::new();
1138        context.add_plan(1.0, |context| {
1139            context.get_data_mut(ComponentA).push(1);
1140            // We add the plan first, but the callback will fire first.
1141            add_plan(context, 2.0, 3);
1142            context.queue_callback(|context| {
1143                context.get_data_mut(ComponentA).push(2);
1144            });
1145        });
1146        context.execute();
1147        assert_eq!(context.get_current_time(), 2.0);
1148        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1149    }
1150
1151    #[test]
1152    fn cancel_plan() {
1153        let mut context = Context::new();
1154        let to_cancel = add_plan(&mut context, 2.0, 1);
1155        context.add_plan(1.0, move |context| {
1156            context.cancel_plan(&to_cancel);
1157        });
1158        context.execute();
1159        assert_eq!(context.get_current_time(), 1.0);
1160        let test_vec: Vec<u32> = vec![];
1161        assert_eq!(*context.get_data_mut(ComponentA), test_vec);
1162    }
1163
1164    #[test]
1165    fn add_plan_with_current_time() {
1166        let mut context = Context::new();
1167        context.add_plan(1.0, move |context| {
1168            context.get_data_mut(ComponentA).push(1);
1169            add_plan(context, 1.0, 2);
1170            context.queue_callback(|context| {
1171                context.get_data_mut(ComponentA).push(3);
1172            });
1173        });
1174        context.execute();
1175        assert_eq!(context.get_current_time(), 1.0);
1176        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 2]);
1177    }
1178
1179    #[test]
1180    fn plans_at_same_time_fire_in_order() {
1181        let mut context = Context::new();
1182        add_plan(&mut context, 1.0, 1);
1183        add_plan(&mut context, 1.0, 2);
1184        context.execute();
1185        assert_eq!(context.get_current_time(), 1.0);
1186        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1187    }
1188
1189    #[test]
1190    fn check_plan_phase_ordering() {
1191        assert!(ExecutionPhase::First < ExecutionPhase::Normal);
1192        assert!(ExecutionPhase::Normal < ExecutionPhase::Last);
1193    }
1194
1195    #[test]
1196    fn plans_at_same_time_follow_phase() {
1197        let mut context = Context::new();
1198        add_plan(&mut context, 1.0, 1);
1199        add_plan_with_phase(&mut context, 1.0, 5, ExecutionPhase::Last);
1200        add_plan_with_phase(&mut context, 1.0, 3, ExecutionPhase::First);
1201        add_plan(&mut context, 1.0, 2);
1202        add_plan_with_phase(&mut context, 1.0, 6, ExecutionPhase::Last);
1203        add_plan_with_phase(&mut context, 1.0, 4, ExecutionPhase::First);
1204        context.execute();
1205        assert_eq!(context.get_current_time(), 1.0);
1206        assert_eq!(*context.get_data_mut(ComponentA), vec![3, 4, 1, 2, 5, 6]);
1207    }
1208
1209    #[derive(IxaEvent)]
1210    struct Event1 {
1211        pub data: usize,
1212    }
1213
1214    #[derive(IxaEvent)]
1215    struct Event2 {
1216        pub data: usize,
1217    }
1218
1219    #[derive(Clone, Copy)]
1220    struct EventWithOnUnsubscribe;
1221
1222    impl IxaEvent for EventWithOnUnsubscribe {
1223        fn on_unsubscribe(context: &mut Context) {
1224            let has_event_handlers = context.has_event_handlers::<Self>();
1225            context
1226                .get_data_mut(UnsubscribeHookObservations)
1227                .push(has_event_handlers);
1228        }
1229    }
1230
1231    struct NotCopy;
1232
1233    #[derive(IxaEvent)]
1234    struct GenericEvent<T> {
1235        pub data: usize,
1236        _marker: PhantomData<T>,
1237    }
1238
1239    #[test]
1240    fn simple_event() {
1241        let mut context = Context::new();
1242        let obs_data = Rc::new(RefCell::new(0));
1243        let obs_data_clone = Rc::clone(&obs_data);
1244
1245        context.subscribe_to_event::<Event1>(move |_, event| {
1246            *obs_data_clone.borrow_mut() = event.data;
1247        });
1248
1249        context.emit_event(Event1 { data: 1 });
1250        context.execute();
1251        assert_eq!(*obs_data.borrow(), 1);
1252    }
1253
1254    #[test]
1255    fn derive_ixa_event_implements_copy_for_generic_events() {
1256        fn assert_clone<T: Clone>() {}
1257        fn assert_copy<T: Copy>() {}
1258        assert_clone::<GenericEvent<NotCopy>>();
1259        assert_copy::<GenericEvent<NotCopy>>();
1260
1261        let mut context = Context::new();
1262        let obs_data = Rc::new(RefCell::new(0));
1263        let obs_data_clone = Rc::clone(&obs_data);
1264
1265        context.subscribe_to_event::<GenericEvent<NotCopy>>(move |_, event| {
1266            *obs_data_clone.borrow_mut() = event.data;
1267        });
1268
1269        let event = GenericEvent::<NotCopy> {
1270            data: 5,
1271            _marker: PhantomData,
1272        };
1273        let copied_event = event;
1274
1275        assert_eq!(copied_event.data, 5);
1276        context.emit_event(copied_event);
1277        context.execute();
1278        assert_eq!(*obs_data.borrow(), 5);
1279    }
1280
1281    #[test]
1282    fn multiple_events() {
1283        let mut context = Context::new();
1284        let obs_data = Rc::new(RefCell::new(0));
1285        let obs_data_clone = Rc::clone(&obs_data);
1286
1287        context.subscribe_to_event::<Event1>(move |_, event| {
1288            *obs_data_clone.borrow_mut() += event.data;
1289        });
1290
1291        context.emit_event(Event1 { data: 1 });
1292        context.emit_event(Event1 { data: 2 });
1293        context.execute();
1294
1295        // Both of these should have been received.
1296        assert_eq!(*obs_data.borrow(), 3);
1297    }
1298
1299    #[test]
1300    fn multiple_event_handlers() {
1301        let mut context = Context::new();
1302        let obs_data1 = Rc::new(RefCell::new(0));
1303        let obs_data1_clone = Rc::clone(&obs_data1);
1304        let obs_data2 = Rc::new(RefCell::new(0));
1305        let obs_data2_clone = Rc::clone(&obs_data2);
1306
1307        context.subscribe_to_event::<Event1>(move |_, event| {
1308            *obs_data1_clone.borrow_mut() = event.data;
1309        });
1310        context.subscribe_to_event::<Event1>(move |_, event| {
1311            *obs_data2_clone.borrow_mut() = event.data;
1312        });
1313        context.emit_event(Event1 { data: 1 });
1314        context.execute();
1315        assert_eq!(*obs_data1.borrow(), 1);
1316        assert_eq!(*obs_data2.borrow(), 1);
1317    }
1318
1319    #[test]
1320    fn multiple_event_types() {
1321        let mut context = Context::new();
1322        let obs_data1 = Rc::new(RefCell::new(0));
1323        let obs_data1_clone = Rc::clone(&obs_data1);
1324        let obs_data2 = Rc::new(RefCell::new(0));
1325        let obs_data2_clone = Rc::clone(&obs_data2);
1326
1327        context.subscribe_to_event::<Event1>(move |_, event| {
1328            *obs_data1_clone.borrow_mut() = event.data;
1329        });
1330        context.subscribe_to_event::<Event2>(move |_, event| {
1331            *obs_data2_clone.borrow_mut() = event.data;
1332        });
1333        context.emit_event(Event1 { data: 1 });
1334        context.emit_event(Event2 { data: 2 });
1335        context.execute();
1336        assert_eq!(*obs_data1.borrow(), 1);
1337        assert_eq!(*obs_data2.borrow(), 2);
1338    }
1339
1340    #[test]
1341    fn unsubscribe_from_event_before_emit() {
1342        let mut context = Context::new();
1343        let obs_data = Rc::new(RefCell::new(0));
1344        let obs_data_clone = Rc::clone(&obs_data);
1345
1346        let listener_id = context.subscribe_to_event::<Event1>(move |_, event| {
1347            *obs_data_clone.borrow_mut() = event.data;
1348        });
1349
1350        assert!(context.has_event_handlers::<Event1>());
1351        assert!(context.unsubscribe_from_event(&listener_id));
1352        assert!(!context.has_event_handlers::<Event1>());
1353
1354        context.emit_event(Event1 { data: 1 });
1355        context.execute();
1356        assert_eq!(*obs_data.borrow(), 0);
1357    }
1358
1359    #[test]
1360    fn unsubscribe_from_event_calls_hook_after_removal() {
1361        let mut context = Context::new();
1362        let listener_id = context.subscribe_to_event::<EventWithOnUnsubscribe>(|_, _| {});
1363
1364        assert!(context.unsubscribe_from_event(&listener_id));
1365
1366        assert_eq!(context.get_data(UnsubscribeHookObservations), &vec![false]);
1367    }
1368
1369    #[test]
1370    fn unsubscribe_from_event_calls_hook_only_for_successful_removals() {
1371        let mut context = Context::new();
1372        let first_listener = context.subscribe_to_event::<EventWithOnUnsubscribe>(|_, _| {});
1373        let second_listener = context.subscribe_to_event::<EventWithOnUnsubscribe>(|_, _| {});
1374        let unknown_listener = EventListenerId::<EventWithOnUnsubscribe>::new(usize::MAX);
1375
1376        assert!(context.unsubscribe_from_event(&first_listener));
1377        assert!(!context.unsubscribe_from_event(&first_listener));
1378        assert!(!context.unsubscribe_from_event(&unknown_listener));
1379        assert!(context.unsubscribe_from_event(&second_listener));
1380
1381        assert_eq!(
1382            context.get_data(UnsubscribeHookObservations),
1383            &vec![true, false]
1384        );
1385    }
1386
1387    #[test]
1388    fn unsubscribe_from_event_preserves_other_listeners_in_order() {
1389        let mut context = Context::new();
1390        let observed = Rc::new(RefCell::new(Vec::new()));
1391
1392        let observed_clone = Rc::clone(&observed);
1393        context.subscribe_to_event::<Event1>(move |_, event| {
1394            observed_clone.borrow_mut().push(event.data);
1395        });
1396        let observed_clone = Rc::clone(&observed);
1397        let listener_to_unsubscribe = context.subscribe_to_event::<Event1>(move |_, event| {
1398            observed_clone.borrow_mut().push(event.data + 10);
1399        });
1400        let observed_clone = Rc::clone(&observed);
1401        context.subscribe_to_event::<Event1>(move |_, event| {
1402            observed_clone.borrow_mut().push(event.data + 20);
1403        });
1404
1405        assert!(context.unsubscribe_from_event(&listener_to_unsubscribe));
1406
1407        context.emit_event(Event1 { data: 1 });
1408        context.execute();
1409        assert_eq!(*observed.borrow(), vec![1, 21]);
1410    }
1411
1412    #[test]
1413    fn unsubscribe_from_event_does_not_affect_other_event_types() {
1414        let mut context = Context::new();
1415        let obs_data1 = Rc::new(RefCell::new(0));
1416        let obs_data1_clone = Rc::clone(&obs_data1);
1417        let obs_data2 = Rc::new(RefCell::new(0));
1418        let obs_data2_clone = Rc::clone(&obs_data2);
1419
1420        let listener_id = context.subscribe_to_event::<Event1>(move |_, event| {
1421            *obs_data1_clone.borrow_mut() = event.data;
1422        });
1423        context.subscribe_to_event::<Event2>(move |_, event| {
1424            *obs_data2_clone.borrow_mut() = event.data;
1425        });
1426
1427        assert!(context.unsubscribe_from_event(&listener_id));
1428
1429        context.emit_event(Event1 { data: 1 });
1430        context.emit_event(Event2 { data: 2 });
1431        context.execute();
1432        assert_eq!(*obs_data1.borrow(), 0);
1433        assert_eq!(*obs_data2.borrow(), 2);
1434    }
1435
1436    #[test]
1437    fn unsubscribe_from_event_does_not_cancel_already_queued_callback() {
1438        let mut context = Context::new();
1439        let obs_data = Rc::new(RefCell::new(0));
1440        let obs_data_clone = Rc::clone(&obs_data);
1441
1442        let listener_id = context.subscribe_to_event::<Event1>(move |_, event| {
1443            *obs_data_clone.borrow_mut() = event.data;
1444        });
1445
1446        context.emit_event(Event1 { data: 1 });
1447        assert!(context.unsubscribe_from_event(&listener_id));
1448        context.execute();
1449        assert_eq!(*obs_data.borrow(), 1);
1450    }
1451
1452    #[test]
1453    fn unsubscribe_from_event_returns_false_when_already_unsubscribed() {
1454        let mut context = Context::new();
1455        let listener_id = context.subscribe_to_event::<Event1>(move |_, _| {});
1456
1457        assert!(context.unsubscribe_from_event(&listener_id));
1458        assert!(!context.unsubscribe_from_event(&listener_id));
1459    }
1460
1461    #[test]
1462    fn unsubscribe_from_event_returns_false_for_unknown_listener_id() {
1463        let mut context1 = Context::new();
1464        let mut context2 = Context::new();
1465        let obs_data = Rc::new(RefCell::new(0));
1466        let obs_data_clone = Rc::clone(&obs_data);
1467
1468        context1.subscribe_to_event::<Event1>(move |_, _| {});
1469        let unknown_listener_id = context1.subscribe_to_event::<Event1>(move |_, _| {});
1470        context2.subscribe_to_event::<Event1>(move |_, event| {
1471            *obs_data_clone.borrow_mut() = event.data;
1472        });
1473
1474        assert!(!context2.unsubscribe_from_event(&unknown_listener_id));
1475
1476        context2.emit_event(Event1 { data: 1 });
1477        context2.execute();
1478        assert_eq!(*obs_data.borrow(), 1);
1479    }
1480
1481    #[test]
1482    fn subscribe_after_event() {
1483        let mut context = Context::new();
1484        let obs_data = Rc::new(RefCell::new(0));
1485        let obs_data_clone = Rc::clone(&obs_data);
1486
1487        context.emit_event(Event1 { data: 1 });
1488        context.subscribe_to_event::<Event1>(move |_, event| {
1489            *obs_data_clone.borrow_mut() = event.data;
1490        });
1491
1492        context.execute();
1493        assert_eq!(*obs_data.borrow(), 0);
1494    }
1495
1496    #[test]
1497    fn shutdown_runs_current_time_plans_and_preserves_future_plan() {
1498        let mut context = Context::new();
1499        add_plan(&mut context, 1.0, 1);
1500        context.add_plan(1.5, |context| {
1501            context.get_data_mut(ComponentA).push(2);
1502            context.shutdown();
1503        });
1504        add_plan(&mut context, 1.5, 3);
1505        add_plan(&mut context, 2.0, 2);
1506        context.execute();
1507        assert_eq!(context.get_current_time(), 1.5);
1508        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1509
1510        context.execute();
1511        assert_eq!(context.get_current_time(), 2.0);
1512        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3, 2]);
1513    }
1514
1515    #[test]
1516    fn shutdown_runs_queued_callbacks() {
1517        let mut context = Context::new();
1518        add_plan(&mut context, 1.0, 1);
1519        context.add_plan(1.5, |context| {
1520            context.queue_callback(|context| {
1521                context.get_data_mut(ComponentA).push(3);
1522            });
1523            context.shutdown();
1524        });
1525        context.execute();
1526        assert_eq!(context.get_current_time(), 1.5);
1527        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3]);
1528    }
1529
1530    #[test]
1531    fn shutdown_runs_queued_events() {
1532        let mut context = Context::new();
1533        let obs_data = Rc::new(RefCell::new(0));
1534        let obs_data_clone = Rc::clone(&obs_data);
1535        context.subscribe_to_event::<Event1>(move |_, event| {
1536            *obs_data_clone.borrow_mut() = event.data;
1537        });
1538        context.emit_event(Event1 { data: 1 });
1539        context.shutdown();
1540        context.execute();
1541        assert_eq!(*obs_data.borrow(), 1);
1542    }
1543
1544    #[test]
1545    fn shutdown_runs_regular_plans_at_current_time_all_phases() {
1546        let mut context = Context::new();
1547        context.add_plan_with_phase(
1548            1.0,
1549            |context| {
1550                context.get_data_mut(ComponentA).push(1);
1551            },
1552            ExecutionPhase::First,
1553        );
1554        context.add_plan(1.0, |context| {
1555            context.get_data_mut(ComponentA).push(2);
1556            context.shutdown();
1557        });
1558        add_plan(&mut context, 1.0, 3);
1559        add_plan_with_phase(&mut context, 1.0, 4, ExecutionPhase::Last);
1560        add_plan(&mut context, 2.0, 5);
1561
1562        context.execute();
1563
1564        assert_eq!(context.get_current_time(), 1.0);
1565        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3, 4]);
1566    }
1567
1568    #[test]
1569    fn shutdown_time_plans_run_after_current_time_plans() {
1570        let mut context = Context::new();
1571        context.add_shutdown_plan(|context| {
1572            context.get_data_mut(ComponentA).push(3);
1573        });
1574        context.add_plan(1.0, |context| {
1575            context.get_data_mut(ComponentA).push(1);
1576            context.shutdown();
1577        });
1578        add_plan(&mut context, 1.0, 2);
1579
1580        context.execute();
1581
1582        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1583    }
1584
1585    #[test]
1586    fn shutdown_plan_phase_order_is_respected() {
1587        let mut context = Context::new();
1588        context.add_shutdown_plan_with_phase(
1589            |context| {
1590                context.get_data_mut(ComponentA).push(3);
1591            },
1592            ExecutionPhase::Last,
1593        );
1594        context.add_shutdown_plan_with_phase(
1595            |context| {
1596                context.get_data_mut(ComponentA).push(1);
1597            },
1598            ExecutionPhase::First,
1599        );
1600        context.add_shutdown_plan(|context| {
1601            context.get_data_mut(ComponentA).push(2);
1602        });
1603        context.execute();
1604
1605        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1606    }
1607
1608    #[test]
1609    fn shutdown_plan_callbacks_are_drained() {
1610        let mut context = Context::new();
1611        context.add_shutdown_plan(|context| {
1612            context.get_data_mut(ComponentA).push(1);
1613            context.queue_callback(|context| {
1614                context.get_data_mut(ComponentA).push(2);
1615            });
1616        });
1617        context.add_shutdown_plan(|context| {
1618            context.get_data_mut(ComponentA).push(3);
1619        });
1620
1621        context.execute();
1622
1623        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1624    }
1625
1626    #[test]
1627    fn shutdown_time_does_not_return_to_regular_queue() {
1628        let mut context = Context::new();
1629        context.add_shutdown_plan(|context| {
1630            context.get_data_mut(ComponentA).push(1);
1631            context.add_plan(context.get_current_time(), |context| {
1632                context.get_data_mut(ComponentA).push(3);
1633            });
1634        });
1635        context.add_shutdown_plan(|context| {
1636            context.get_data_mut(ComponentA).push(2);
1637        });
1638
1639        context.execute();
1640        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1641
1642        context.execute();
1643        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1644    }
1645
1646    #[test]
1647    fn passive_only_initial_time_runs_during_normal_shutdown() {
1648        let mut context = Context::new();
1649        add_passive_plan(&mut context, 0.0, 1);
1650        add_passive_plan(&mut context, 1.0, 2);
1651
1652        context.execute();
1653
1654        assert_eq!(context.get_current_time(), 0.0);
1655        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1656    }
1657
1658    #[test]
1659    fn passive_plans_at_final_non_passive_time_run_across_phases() {
1660        let mut context = Context::new();
1661        add_passive_plan_with_phase(&mut context, 1.0, 1, ExecutionPhase::First);
1662        add_plan(&mut context, 1.0, 2);
1663        add_passive_plan_with_phase(&mut context, 1.0, 3, ExecutionPhase::Last);
1664
1665        context.execute();
1666
1667        assert_eq!(context.get_current_time(), 1.0);
1668        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1669    }
1670
1671    #[test]
1672    fn passive_future_plan_survives_until_later_non_passive_work() {
1673        let mut context = Context::new();
1674        add_plan(&mut context, 1.0, 1);
1675        add_passive_plan(&mut context, 2.0, 2);
1676
1677        context.execute();
1678
1679        assert_eq!(context.get_current_time(), 1.0);
1680        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1681
1682        add_plan(&mut context, 2.0, 3);
1683        context.execute();
1684
1685        assert_eq!(context.get_current_time(), 2.0);
1686        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1687    }
1688
1689    #[test]
1690    fn scheduled_shutdown_stops_at_requested_time_and_drains_current_time_work() {
1691        let mut context = Context::new();
1692        add_plan(&mut context, 1.0, 1);
1693        context.schedule_shutdown(2.0);
1694        add_plan(&mut context, 2.0, 2);
1695        add_plan(&mut context, 3.0, 3);
1696
1697        context.execute();
1698
1699        assert_eq!(context.get_current_time(), 2.0);
1700        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1701
1702        context.execute();
1703
1704        assert_eq!(context.get_current_time(), 3.0);
1705        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1706    }
1707
1708    #[test]
1709    fn scheduled_shutdown_does_not_keep_the_timeline_alive() {
1710        let mut context = Context::new();
1711        context.schedule_shutdown(5.0);
1712
1713        context.execute();
1714
1715        assert_eq!(context.get_current_time(), 0.0);
1716
1717        add_plan(&mut context, 10.0, 1);
1718        context.execute();
1719
1720        assert_eq!(context.get_current_time(), 5.0);
1721        assert_eq!(*context.get_data_mut(ComponentA), Vec::<u32>::new());
1722    }
1723
1724    #[test]
1725    fn scheduled_shutdown_can_be_cancelled() {
1726        let mut context = Context::new();
1727        let shutdown_plan = context.schedule_shutdown(2.0);
1728        add_plan(&mut context, 3.0, 1);
1729        context.cancel_plan(&shutdown_plan);
1730
1731        context.execute();
1732
1733        assert_eq!(context.get_current_time(), 3.0);
1734        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1735    }
1736
1737    #[test]
1738    fn cancel_plan_can_cancel_shutdown_plan() {
1739        let mut context = Context::new();
1740        let to_cancel = context.add_shutdown_plan(|context| {
1741            context.get_data_mut(ComponentA).push(1);
1742        });
1743        context.cancel_plan(&to_cancel);
1744
1745        context.execute();
1746
1747        assert_eq!(*context.get_data_mut(ComponentA), Vec::<u32>::new());
1748    }
1749
1750    #[test]
1751    fn abort_inside_plan_stops_execute_loop() {
1752        let mut context = Context::new();
1753        context.add_plan(1.0, |context| {
1754            context.get_data_mut(ComponentA).push(1);
1755            context.queue_callback(|context| {
1756                context.get_data_mut(ComponentA).push(2);
1757            });
1758            context.abort();
1759        });
1760        add_plan(&mut context, 2.0, 3);
1761
1762        context.execute();
1763        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1764
1765        context.execute();
1766        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1767    }
1768
1769    #[test]
1770    fn abort_before_execute_does_not_poison_later_execute() {
1771        let mut context = Context::new();
1772        context.abort();
1773        add_plan(&mut context, 1.0, 1);
1774
1775        context.execute();
1776
1777        assert_eq!(context.get_current_time(), 1.0);
1778        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1779    }
1780
1781    #[test]
1782    fn abort_during_normal_shutdown_exits_immediately() {
1783        let mut context = Context::new();
1784        context.add_plan(1.0, |context| {
1785            context.get_data_mut(ComponentA).push(1);
1786            context.shutdown();
1787        });
1788        context.add_plan(1.0, |context| {
1789            context.get_data_mut(ComponentA).push(2);
1790            context.abort();
1791        });
1792        add_plan(&mut context, 1.0, 3);
1793
1794        context.execute();
1795        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1796
1797        context.execute();
1798        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1799    }
1800
1801    #[test]
1802    fn shutdown_does_not_restart_stopped_status() {
1803        let mut context = Context::new();
1804        context.abort();
1805        context.shutdown();
1806        add_plan(&mut context, 1.0, 1);
1807
1808        context.execute();
1809
1810        assert_eq!(context.get_current_time(), 1.0);
1811        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1812    }
1813
1814    #[test]
1815    fn execute_single_step_runs_one_status_transition() {
1816        let mut context = Context::new();
1817        context.add_shutdown_plan(|context| {
1818            context.get_data_mut(ComponentA).push(1);
1819        });
1820
1821        context.execute_single_step();
1822        assert_eq!(*context.get_data_mut(ComponentA), Vec::<u32>::new());
1823
1824        context.execute_single_step();
1825        assert_eq!(*context.get_data_mut(ComponentA), Vec::<u32>::new());
1826
1827        context.execute_single_step();
1828        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1829    }
1830
1831    #[test]
1832    fn execute_single_step_stopped_runs_callback_then_resets() {
1833        let mut context = Context::new();
1834        context.abort();
1835        context.queue_callback(|context| {
1836            context.get_data_mut(ComponentA).push(1);
1837        });
1838        add_plan(&mut context, 0.0, 2);
1839
1840        context.execute_single_step();
1841        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1842
1843        context.execute_single_step();
1844        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1845
1846        context.execute_single_step();
1847        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1848    }
1849
1850    #[test]
1851    fn periodic_plan_self_schedules() {
1852        // checks whether the periodic plan schedules itself passively without
1853        // keeping execution alive after non-passive plans are exhausted.
1854        let mut context = Context::new();
1855        context.add_periodic_plan_with_phase(
1856            1.0,
1857            |context| {
1858                let time = context.get_current_time();
1859                context.get_data_mut(ComponentA).push(time as u32);
1860            },
1861            ExecutionPhase::Last,
1862        );
1863        context.add_plan(1.0, move |_context| {});
1864        context.add_plan(1.5, move |_context| {});
1865        context.execute();
1866        assert_eq!(context.get_current_time(), 1.5);
1867
1868        assert_eq!(*context.get_data(ComponentA), vec![0, 1]); // time 0.0 and 1.0
1869    }
1870
1871    // Tests for negative time handling
1872
1873    #[test]
1874    fn negative_plan_time_allowed() {
1875        let mut context = Context::new();
1876        context.set_start_time(-1.0);
1877        add_plan(&mut context, -1.0, 1);
1878        context.execute();
1879        assert_eq!(context.get_current_time(), -1.0);
1880        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1881    }
1882
1883    #[test]
1884    fn add_plan_get_current_time() {
1885        let mut context = Context::new();
1886        let current_time = context.get_current_time();
1887        add_plan(&mut context, current_time, 1);
1888        context.execute();
1889        assert_eq!(context.get_current_time(), 0.0);
1890        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1891    }
1892
1893    #[test]
1894    fn multiple_negative_plans() {
1895        let mut context = Context::new();
1896        context.set_start_time(-3.0);
1897        add_plan(&mut context, -3.0, 1);
1898        add_plan(&mut context, -1.0, 3);
1899        add_plan(&mut context, -2.0, 2);
1900        context.execute();
1901        assert_eq!(context.get_current_time(), -1.0);
1902        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1903    }
1904
1905    #[test]
1906    fn negative_and_positive_plans() {
1907        let mut context = Context::new();
1908        context.set_start_time(-1.0);
1909        add_plan(&mut context, -1.0, 1);
1910        add_plan(&mut context, 1.0, 3);
1911        add_plan(&mut context, 0.0, 2);
1912        context.execute();
1913        assert_eq!(context.get_current_time(), 1.0);
1914        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1915    }
1916
1917    #[test]
1918    fn get_current_time_before_execute_defaults() {
1919        let mut context = Context::new();
1920        assert_eq!(context.get_current_time(), 0.0);
1921
1922        context.set_start_time(-2.0);
1923        assert_eq!(context.get_current_time(), -2.0);
1924    }
1925
1926    #[test]
1927    fn get_current_time_initializes_to_zero_when_all_positive() {
1928        let mut context = Context::new();
1929        let seen_time = Rc::new(RefCell::new(f64::NAN));
1930        let seen_time_clone = Rc::clone(&seen_time);
1931        context.queue_callback(move |ctx| {
1932            *seen_time_clone.borrow_mut() = ctx.get_current_time();
1933        });
1934        context.execute();
1935        assert_eq!(*seen_time.borrow(), 0.0);
1936    }
1937
1938    #[test]
1939    fn get_current_time_initializes_to_zero_when_empty() {
1940        let mut context = Context::new();
1941        context.execute();
1942        assert_eq!(context.get_current_time(), 0.0);
1943    }
1944
1945    #[test]
1946    fn get_current_time_initializes_to_zero_with_plan() {
1947        let mut context = Context::new();
1948        add_plan(&mut context, 0.0, 1);
1949        context.execute();
1950        assert_eq!(context.get_current_time(), 0.0);
1951        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1952    }
1953
1954    #[test]
1955    #[should_panic(expected = "Time -1 is invalid")]
1956    fn negative_time_from_callback_panics() {
1957        let mut context = Context::new();
1958        context.queue_callback(|context| {
1959            context.get_data_mut(ComponentA).push(1);
1960            add_plan(context, -1.0, 2);
1961        });
1962        add_plan(&mut context, 1.0, 3);
1963        context.execute();
1964    }
1965
1966    #[test]
1967    fn large_negative_time() {
1968        let mut context = Context::new();
1969        context.set_start_time(-1_000_000.0);
1970        add_plan(&mut context, -1_000_000.0, 1);
1971        context.execute();
1972        assert_eq!(context.get_current_time(), -1_000_000.0);
1973        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1974    }
1975
1976    #[test]
1977    fn very_small_negative_time() {
1978        let mut context = Context::new();
1979        context.set_start_time(-1e-10);
1980        add_plan(&mut context, -1e-10, 1);
1981        context.execute();
1982        assert_eq!(context.get_current_time(), -1e-10);
1983        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1984    }
1985
1986    #[test]
1987    fn negative_time_ordering_with_phases() {
1988        let mut context = Context::new();
1989        context.set_start_time(-1.0);
1990        add_plan_with_phase(&mut context, -1.0, 1, ExecutionPhase::Normal);
1991        add_plan_with_phase(&mut context, -1.0, 3, ExecutionPhase::Last);
1992        add_plan_with_phase(&mut context, -1.0, 2, ExecutionPhase::First);
1993        context.execute();
1994        assert_eq!(context.get_current_time(), -1.0);
1995        assert_eq!(*context.get_data_mut(ComponentA), vec![2, 1, 3]);
1996    }
1997
1998    #[test]
1999    #[should_panic(expected = "Time 4 is invalid")]
2000    fn cannot_schedule_plan_before_current_time() {
2001        let mut context = Context::new();
2002        add_plan(&mut context, 5.0, 1);
2003        context.add_plan(5.0, |context| {
2004            // At time 5.0, we cannot schedule a plan at time 4.0
2005            add_plan(context, 4.0, 2);
2006        });
2007        context.execute();
2008    }
2009
2010    #[test]
2011    fn get_current_time_multiple_calls_before_execute() {
2012        let mut context = Context::new();
2013        context.set_start_time(-2.0);
2014        add_plan(&mut context, -2.0, 1);
2015        context.execute();
2016        assert_eq!(context.get_current_time(), -2.0);
2017    }
2018
2019    #[test]
2020    fn negative_plan_can_add_positive_plan() {
2021        let mut context = Context::new();
2022        context.set_start_time(-1.0);
2023        add_plan(&mut context, -1.0, 1);
2024        context.add_plan(-1.0, |context| {
2025            add_plan(context, 2.0, 2);
2026        });
2027        context.execute();
2028        assert_eq!(context.get_current_time(), 2.0);
2029        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
2030    }
2031
2032    #[test]
2033    fn negative_plan_can_schedule_negative_plan() {
2034        let mut context = Context::new();
2035        context.set_start_time(-2.0);
2036        add_plan(&mut context, -2.0, 1);
2037        context.add_plan(-2.0, |context| {
2038            add_plan(context, -1.0, 2);
2039        });
2040        context.execute();
2041        assert_eq!(context.get_current_time(), -1.0);
2042        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
2043    }
2044
2045    #[test]
2046    #[should_panic(expected = "Start time has already been set. It can only be set once.")]
2047    fn set_start_time_only_once() {
2048        let mut context = Context::new();
2049        context.set_start_time(1.0);
2050        context.set_start_time(2.0);
2051    }
2052
2053    // Additional coverage around time and plans
2054
2055    #[test]
2056    #[should_panic(expected = "Start time NaN must be finite")]
2057    fn set_start_time_nan_panics() {
2058        let mut context = Context::new();
2059        context.set_start_time(f64::NAN);
2060    }
2061
2062    #[test]
2063    #[should_panic(expected = "Start time inf must be finite")]
2064    fn set_start_time_inf_panics() {
2065        let mut context = Context::new();
2066        context.set_start_time(f64::INFINITY);
2067    }
2068
2069    #[test]
2070    fn set_start_time_equal_to_earliest_plan_allowed() {
2071        let mut context = Context::new();
2072        context.set_start_time(-2.0);
2073        add_plan(&mut context, -2.0, 1);
2074        context.execute();
2075        assert_eq!(context.get_current_time(), -2.0);
2076        assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
2077    }
2078
2079    // Note: adding a plan earlier than current_time after setting start time
2080    // is already covered by `add_plan_less_than_current_time_panics`.
2081
2082    #[test]
2083    fn set_start_time_with_only_callbacks_keeps_time() {
2084        let mut context = Context::new();
2085        context.set_start_time(5.0);
2086        context.queue_callback(|ctx| {
2087            ctx.get_data_mut(ComponentA).push(42);
2088        });
2089        context.execute();
2090        assert_eq!(context.get_current_time(), 5.0);
2091        assert_eq!(*context.get_data_mut(ComponentA), vec![42]);
2092    }
2093
2094    #[test]
2095    fn multiple_plans_final_time_is_last() {
2096        let mut context = Context::new();
2097        add_plan(&mut context, 1.0, 1);
2098        add_plan(&mut context, 3.0, 3);
2099        add_plan(&mut context, 2.0, 2);
2100        context.execute();
2101        assert_eq!(context.get_current_time(), 3.0);
2102        assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
2103    }
2104
2105    #[test]
2106    fn add_plan_same_time_fifo_and_phases() {
2107        let mut context = Context::new();
2108        add_plan_with_phase(&mut context, 1.0, 3, ExecutionPhase::Last);
2109        add_plan(&mut context, 1.0, 1);
2110        add_plan_with_phase(&mut context, 1.0, 2, ExecutionPhase::First);
2111        add_plan(&mut context, 1.0, 4);
2112        context.execute();
2113        assert_eq!(context.get_current_time(), 1.0);
2114        assert_eq!(*context.get_data_mut(ComponentA), vec![2, 1, 4, 3]);
2115    }
2116
2117    #[test]
2118    #[should_panic(expected = "Time -2 is invalid")]
2119    fn add_plan_less_than_current_time_panics() {
2120        let mut context = Context::new();
2121        context.set_start_time(-1.0);
2122        add_plan(&mut context, -1.0, 1);
2123        // Attempt to schedule before current time
2124        add_plan(&mut context, -2.0, 2);
2125    }
2126
2127    #[test]
2128    #[should_panic(expected = "Period must be greater than 0")]
2129    fn add_periodic_plan_zero_period_panics() {
2130        let mut context = Context::new();
2131        context.add_periodic_plan_with_phase(0.0, |_ctx| {}, ExecutionPhase::Normal);
2132    }
2133
2134    #[test]
2135    #[should_panic(expected = "Period must be greater than 0")]
2136    fn add_periodic_plan_nan_panics() {
2137        let mut context = Context::new();
2138        context.add_periodic_plan_with_phase(f64::NAN, |_ctx| {}, ExecutionPhase::Normal);
2139    }
2140
2141    #[test]
2142    #[should_panic(expected = "Period must be greater than 0")]
2143    fn add_periodic_plan_inf_panics() {
2144        let mut context = Context::new();
2145        context.add_periodic_plan_with_phase(f64::INFINITY, |_ctx| {}, ExecutionPhase::Normal);
2146    }
2147
2148    #[test]
2149    fn shutdown_status_reset() {
2150        // This test verifies that shutdown_status is properly reset after
2151        // being acted upon. This allows the context to be reused after shutdown.
2152        let mut context = Context::new();
2153        let _: PersonId = context.add_entity(with!(Person, Age(50))).unwrap();
2154
2155        // Schedule a plan at time 0.0 that calls shutdown
2156        context.add_plan(0.0, |ctx| {
2157            ctx.shutdown();
2158        });
2159
2160        // First execute - should run until shutdown
2161        context.execute();
2162        assert_eq!(context.get_current_time(), 0.0);
2163        assert_eq!(context.get_entity_count::<Person>(), 1);
2164
2165        // Add a new plan at time 2.0
2166        context.add_plan(2.0, |ctx| {
2167            let _: PersonId = ctx.add_entity(with!(Person, Age(50))).unwrap();
2168        });
2169
2170        // Second execute - should execute the new plan
2171        // If shutdown_status wasn't reset, this would immediately break
2172        // without executing the plan, leaving population at 1.
2173        context.execute();
2174        assert_eq!(context.get_current_time(), 2.0);
2175        assert_eq!(
2176            context.get_entity_count::<Person>(),
2177            2,
2178            "If this fails, shutdown_status was not properly reset"
2179        );
2180    }
2181}