1use 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
25type Callback = dyn FnOnce(&mut Context);
27
28type EventHandler<E> = dyn Fn(&mut Context, E);
30
31pub trait IxaEvent: Copy + 'static {
32 fn on_subscribe(_context: &mut Context) {}
34}
35
36#[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
56pub 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 #[must_use]
100 pub fn new() -> Context {
101 emit_pre_main_diagnostics();
102
103 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 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 pub fn emit_event<E: IxaEvent>(&mut self, event: E) {
162 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 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 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 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 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 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 #[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 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 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 #[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 pub fn shutdown(&mut self) {
348 trace!("shutdown context");
349 self.shutdown_requested = true;
350 }
351
352 #[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 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 #[must_use]
405 pub fn get_start_time(&self) -> Option<f64> {
406 self.start_time
407 }
408
409 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 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 pub fn execute_single_step(&mut self) {
444 if let Some(callback) = self.callback_queue.pop_front() {
446 trace!("calling callback");
447 callback(self);
448 }
449 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 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 #![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 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 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 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 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]); }
939
940 #[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 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 #[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 #[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 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 let mut context = Context::new();
1222 let _: PersonId = context.add_entity(with!(Person, Age(50))).unwrap();
1223
1224 context.add_plan(0.0, |ctx| {
1226 ctx.shutdown();
1227 });
1228
1229 context.execute();
1231 assert_eq!(context.get_current_time(), 0.0);
1232 assert_eq!(context.get_entity_count::<Person>(), 1);
1233
1234 context.add_plan(2.0, |ctx| {
1236 let _: PersonId = ctx.add_entity(with!(Person, Age(50))).unwrap();
1237 });
1238
1239 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}