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::property::Property;
14use crate::entity::property_value_store_core::PropertyValueStoreCore;
15use crate::entity::Entity;
16use crate::execution_stats::{
17 log_execution_statistics, print_execution_statistics, ExecutionProfilingCollector,
18 ExecutionStatistics,
19};
20use crate::global_properties::get_global_property_count;
21use crate::plan::{PlanId, Queue};
22use crate::{get_data_plugin_count, trace, warn, HashMap, HashMapExt};
23
24type Callback = dyn FnOnce(&mut Context);
26
27type EventHandler<E> = dyn Fn(&mut Context, E);
29
30pub trait IxaEvent {
31 fn on_subscribe(_context: &mut Context) {}
33}
34
35#[derive(PartialEq, Eq, Ord, Clone, Copy, PartialOrd, Hash, Debug)]
43pub enum ExecutionPhase {
44 First,
45 Normal,
46 Last,
47}
48
49impl Display for ExecutionPhase {
50 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
51 write!(f, "{self:?}")
52 }
53}
54
55pub struct Context {
83 plan_queue: Queue<Box<Callback>, ExecutionPhase>,
84 callback_queue: VecDeque<Box<Callback>>,
85 event_handlers: HashMap<TypeId, Box<dyn Any>>,
86 pub(crate) entity_store: EntityStore,
87 data_plugins: Vec<OnceCell<Box<dyn Any>>>,
88 pub(crate) global_properties: Vec<OnceCell<Box<dyn Any>>>,
89 current_time: Option<f64>,
90 start_time: Option<f64>,
91 shutdown_requested: bool,
92 execution_profiler: ExecutionProfilingCollector,
93 pub(crate) print_execution_statistics: bool,
94}
95
96impl Context {
97 #[must_use]
99 pub fn new() -> Context {
100 let data_plugins = std::iter::repeat_with(OnceCell::new)
102 .take(get_data_plugin_count())
103 .collect();
104 let global_properties = std::iter::repeat_with(OnceCell::new)
105 .take(get_global_property_count())
106 .collect();
107
108 Context {
109 plan_queue: Queue::new(),
110 callback_queue: VecDeque::new(),
111 event_handlers: HashMap::new(),
112 entity_store: EntityStore::new(),
113 data_plugins,
114 global_properties,
115 current_time: None,
116 start_time: None,
117 shutdown_requested: false,
118 execution_profiler: ExecutionProfilingCollector::new(),
119 print_execution_statistics: false,
120 }
121 }
122
123 pub(crate) fn get_property_value_store<E: Entity, P: Property<E>>(
124 &self,
125 ) -> &PropertyValueStoreCore<E, P> {
126 self.entity_store.get_property_store::<E>().get::<P>()
127 }
128 pub(crate) fn get_property_value_store_mut<E: Entity, P: Property<E>>(
129 &mut self,
130 ) -> &mut PropertyValueStoreCore<E, P> {
131 self.entity_store
132 .get_property_store_mut::<E>()
133 .get_mut::<P>()
134 }
135
136 #[allow(clippy::missing_panics_doc)]
141 pub fn subscribe_to_event<E: IxaEvent + Copy + 'static>(
142 &mut self,
143 handler: impl Fn(&mut Context, E) + 'static,
144 ) {
145 let handler_vec = self
146 .event_handlers
147 .entry(TypeId::of::<E>())
148 .or_insert_with(|| Box::<Vec<Rc<EventHandler<E>>>>::default());
149 let handler_vec: &mut Vec<Rc<EventHandler<E>>> = handler_vec.downcast_mut().unwrap();
150 handler_vec.push(Rc::new(handler));
151 E::on_subscribe(self);
152 }
153
154 pub(crate) fn has_event_handlers<E: IxaEvent + 'static>(&self) -> bool {
155 self.event_handlers.contains_key(&TypeId::of::<E>())
156 }
157
158 #[allow(clippy::missing_panics_doc)]
163 pub fn emit_event<E: IxaEvent + Copy + 'static>(&mut self, event: E) {
164 let Context {
166 event_handlers,
167 callback_queue,
168 ..
169 } = self;
170 if let Some(handler_vec) = event_handlers.get(&TypeId::of::<E>()) {
171 let handler_vec: &Vec<Rc<EventHandler<E>>> = handler_vec.downcast_ref().unwrap();
172 for handler in handler_vec {
173 let handler_clone = Rc::clone(handler);
174 callback_queue.push_back(Box::new(move |context| handler_clone(context, event)));
175 }
176 }
177 }
178
179 pub fn add_plan(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static) -> PlanId {
188 self.add_plan_with_phase(time, callback, ExecutionPhase::Normal)
189 }
190
191 pub fn add_plan_with_phase(
201 &mut self,
202 time: f64,
203 callback: impl FnOnce(&mut Context) + 'static,
204 phase: ExecutionPhase,
205 ) -> PlanId {
206 let current = self.get_current_time();
207 assert!(!time.is_nan(), "Time {time} is invalid: cannot be NaN");
208 assert!(
209 !time.is_infinite(),
210 "Time {time} is invalid: cannot be infinite"
211 );
212 assert!(
213 time >= current,
214 "Time {time} is invalid: cannot be less than the current time ({}). Consider calling set_start_time() before scheduling plans.",
215 current
216 );
217 self.plan_queue.add_plan(time, Box::new(callback), phase)
218 }
219
220 fn evaluate_periodic_and_schedule_next(
221 &mut self,
222 period: f64,
223 callback: impl Fn(&mut Context) + 'static,
224 phase: ExecutionPhase,
225 ) {
226 trace!(
227 "evaluate periodic at {} (period={})",
228 self.get_current_time(),
229 period
230 );
231 callback(self);
232 if !self.plan_queue.is_empty() {
233 let next_time = self.get_current_time() + period;
234 self.add_plan_with_phase(
235 next_time,
236 move |context| context.evaluate_periodic_and_schedule_next(period, callback, phase),
237 phase,
238 );
239 }
240 }
241
242 pub fn add_periodic_plan_with_phase(
255 &mut self,
256 period: f64,
257 callback: impl Fn(&mut Context) + 'static,
258 phase: ExecutionPhase,
259 ) {
260 assert!(
261 period > 0.0 && !period.is_nan() && !period.is_infinite(),
262 "Period must be greater than 0"
263 );
264
265 self.add_plan_with_phase(
266 0.0,
267 move |context| context.evaluate_periodic_and_schedule_next(period, callback, phase),
268 phase,
269 );
270 }
271
272 pub fn cancel_plan(&mut self, plan_id: &PlanId) {
279 trace!("canceling plan {plan_id:?}");
280 let result = self.plan_queue.cancel_plan(plan_id);
281 if result.is_none() {
282 warn!("Tried to cancel nonexistent plan with ID = {plan_id:?}");
283 }
284 }
285
286 #[doc(hidden)]
287 #[allow(dead_code)]
288 pub(crate) fn remaining_plan_count(&self) -> usize {
289 self.plan_queue.remaining_plan_count()
290 }
291
292 pub fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static) {
294 trace!("queuing callback");
295 self.callback_queue.push_back(Box::new(callback));
296 }
297
298 #[must_use]
307 #[allow(clippy::needless_pass_by_value)]
308 pub fn get_data_mut<T: DataPlugin>(&mut self, _data_plugin: T) -> &mut T::DataContainer {
309 let index = T::index_within_context();
310
311 if self.data_plugins[index].get().is_some() {
313 return self.data_plugins[index]
314 .get_mut()
315 .unwrap()
316 .downcast_mut::<T::DataContainer>()
317 .expect("TypeID does not match data plugin type");
318 }
319
320 let data = T::init(self);
322 let cell = self
323 .data_plugins
324 .get_mut(index)
325 .unwrap_or_else(|| panic!("No data plugin found with index = {index:?}. You must use the `define_data_plugin!` macro to create a data plugin."));
326 let _ = cell.set(Box::new(data));
327 cell.get_mut()
328 .unwrap()
329 .downcast_mut::<T::DataContainer>()
330 .expect("TypeID does not match data plugin type. You must use the `define_data_plugin!` macro to create a data plugin.")
331 }
332
333 #[must_use]
338 #[allow(clippy::needless_pass_by_value)]
339 pub fn get_data<T: DataPlugin>(&self, _data_plugin: T) -> &T::DataContainer {
340 let index = T::index_within_context();
341 self.data_plugins
342 .get(index)
343 .unwrap_or_else(|| panic!("No data plugin found with index = {index:?}. You must use the `define_data_plugin!` macro to create a data plugin."))
344 .get_or_init(|| Box::new(T::init(self)))
345 .downcast_ref::<T::DataContainer>()
346 .expect("TypeID does not match data plugin type. You must use the `define_data_plugin!` macro to create a data plugin.")
347 }
348
349 pub fn shutdown(&mut self) {
352 trace!("shutdown context");
353 self.shutdown_requested = true;
354 }
355
356 #[must_use]
364 pub fn get_current_time(&self) -> f64 {
365 self.current_time.or(self.start_time).unwrap_or(0.0)
366 }
367
368 pub fn set_start_time(&mut self, start_time: f64) {
384 assert!(
385 !start_time.is_nan() && !start_time.is_infinite(),
386 "Start time {start_time} must be finite"
387 );
388 assert!(
389 self.start_time.is_none(),
390 "Start time has already been set. It can only be set once."
391 );
392 assert!(
393 self.current_time.is_none(),
394 "Start time cannot be set after execution has begun."
395 );
396 if let Some(next_time) = self.plan_queue.next_time() {
397 assert!(
398 start_time <= next_time,
399 "Start time {} is later than the earliest scheduled plan time {}. Remove or reschedule existing plans first.",
400 start_time,
401 next_time
402 );
403 }
404 self.start_time = Some(start_time);
405 }
406
407 #[must_use]
409 pub fn get_start_time(&self) -> Option<f64> {
410 self.start_time
411 }
412
413 pub fn execute(&mut self) {
415 trace!("entering event loop");
416
417 if self.current_time.is_none() {
418 self.current_time = Some(self.start_time.unwrap_or(0.0));
419 }
420
421 loop {
423 if self.shutdown_requested {
424 self.shutdown_requested = false;
425 break;
426 } else {
427 self.execute_single_step();
428 }
429
430 self.execution_profiler.refresh();
431 }
432
433 let stats = self.get_execution_statistics();
434 if self.print_execution_statistics {
435 print_execution_statistics(&stats);
436 #[cfg(feature = "profiling")]
437 crate::profiling::print_profiling_data();
438 } else {
439 log_execution_statistics(&stats);
440 }
441 }
442
443 pub fn execute_single_step(&mut self) {
448 if let Some(callback) = self.callback_queue.pop_front() {
450 trace!("calling callback");
451 callback(self);
452 }
453 else if let Some(plan) = self.plan_queue.get_next_plan() {
455 trace!("calling plan at {:.6}", plan.time);
456 self.current_time = Some(plan.time);
457 (plan.data)(self);
458 } else {
459 trace!("No callbacks or plans; exiting event loop");
460 self.shutdown_requested = true;
462 }
463 }
464
465 pub fn get_execution_statistics(&mut self) -> ExecutionStatistics {
466 #[allow(unused_mut)]
467 let mut stats = self.execution_profiler.compute_final_statistics();
468 #[cfg(feature = "profiling")]
469 {
470 stats.max_plans_in_flight = self.plan_queue.max_plans_in_flight;
471 stats.max_plan_queue_memory_in_use = self.plan_queue.max_memory_in_use;
472 }
473 stats
474 }
475}
476
477pub trait ContextBase: Sized {
478 fn subscribe_to_event<E: IxaEvent + Copy + 'static>(
479 &mut self,
480 handler: impl Fn(&mut Context, E) + 'static,
481 );
482 fn emit_event<E: IxaEvent + Copy + 'static>(&mut self, event: E);
483 fn add_plan(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static) -> PlanId;
484 fn add_plan_with_phase(
485 &mut self,
486 time: f64,
487 callback: impl FnOnce(&mut Context) + 'static,
488 phase: ExecutionPhase,
489 ) -> PlanId;
490 fn add_periodic_plan_with_phase(
491 &mut self,
492 period: f64,
493 callback: impl Fn(&mut Context) + 'static,
494 phase: ExecutionPhase,
495 );
496 fn cancel_plan(&mut self, plan_id: &PlanId);
497 fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static);
498 fn get_data_mut<T: DataPlugin>(&mut self, plugin: T) -> &mut T::DataContainer;
499 fn get_data<T: DataPlugin>(&self, plugin: T) -> &T::DataContainer;
500 fn get_current_time(&self) -> f64;
501 fn get_execution_statistics(&mut self) -> ExecutionStatistics;
502}
503impl ContextBase for Context {
504 delegate::delegate! {
505 to self {
506 fn subscribe_to_event<E: IxaEvent + Copy + 'static>(&mut self, handler: impl Fn(&mut Context, E) + 'static);
507 fn emit_event<E: IxaEvent + Copy + 'static>(&mut self, event: E);
508 fn add_plan(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static) -> PlanId;
509 fn add_plan_with_phase(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static, phase: ExecutionPhase) -> PlanId;
510 fn add_periodic_plan_with_phase(&mut self, period: f64, callback: impl Fn(&mut Context) + 'static, phase: ExecutionPhase);
511 fn cancel_plan(&mut self, plan_id: &PlanId);
512 fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static);
513 fn get_data_mut<T: DataPlugin>(&mut self, plugin: T) -> &mut T::DataContainer;
514 fn get_data<T: DataPlugin>(&self, plugin: T) -> &T::DataContainer;
515 fn get_current_time(&self) -> f64;
516 fn get_execution_statistics(&mut self) -> ExecutionStatistics;
517 }
518 }
519}
520
521impl Default for Context {
522 fn default() -> Self {
523 Self::new()
524 }
525}
526
527#[cfg(test)]
528mod tests {
529 #![allow(dead_code)]
531 use std::cell::RefCell;
532
533 use ixa_derive::IxaEvent;
534
535 use super::*;
536 use crate::{define_data_plugin, define_entity, define_property, with, ContextEntitiesExt};
537
538 define_data_plugin!(ComponentA, Vec<u32>, vec![]);
539
540 define_entity!(Person);
541
542 define_property!(struct Age(u8), Person);
543
544 define_property!(
545 enum InfectionStatus {
546 Susceptible,
547 Infected,
548 Recovered,
549 },
550 Person,
551 default_const = InfectionStatus::Susceptible
552 );
553
554 define_property!(
555 struct Vaccinated(bool),
556 Person,
557 default_const = Vaccinated(false)
558 );
559
560 #[test]
561 fn empty_context() {
562 let mut context = Context::new();
563 context.execute();
564 assert_eq!(context.get_current_time(), 0.0);
565 }
566
567 #[test]
568 fn get_data() {
569 let mut context = Context::new();
570 context.get_data_mut(ComponentA).push(1);
571 assert_eq!(*context.get_data(ComponentA), vec![1],);
572 }
573
574 fn add_plan(context: &mut Context, time: f64, value: u32) -> PlanId {
575 context.add_plan(time, move |context| {
576 context.get_data_mut(ComponentA).push(value);
577 })
578 }
579
580 fn add_plan_with_phase(
581 context: &mut Context,
582 time: f64,
583 value: u32,
584 phase: ExecutionPhase,
585 ) -> PlanId {
586 context.add_plan_with_phase(
587 time,
588 move |context| {
589 context.get_data_mut(ComponentA).push(value);
590 },
591 phase,
592 )
593 }
594
595 #[test]
596 #[should_panic(expected = "Time inf is invalid")]
597 fn infinite_plan_time() {
598 let mut context = Context::new();
599 add_plan(&mut context, f64::INFINITY, 0);
600 }
601
602 #[test]
603 #[should_panic(expected = "Time NaN is invalid")]
604 fn nan_plan_time() {
605 let mut context = Context::new();
606 add_plan(&mut context, f64::NAN, 0);
607 }
608
609 #[test]
610 fn timed_plan_only() {
611 let mut context = Context::new();
612 add_plan(&mut context, 1.0, 1);
613 context.execute();
614 assert_eq!(context.get_current_time(), 1.0);
615 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
616 }
617
618 #[test]
619 fn callback_only() {
620 let mut context = Context::new();
621 context.queue_callback(|context| {
622 context.get_data_mut(ComponentA).push(1);
623 });
624 context.execute();
625 assert_eq!(context.get_current_time(), 0.0);
626 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
627 }
628
629 #[test]
630 fn callback_before_timed_plan() {
631 let mut context = Context::new();
632 context.queue_callback(|context| {
633 context.get_data_mut(ComponentA).push(1);
634 });
635 add_plan(&mut context, 1.0, 2);
636 context.execute();
637 assert_eq!(context.get_current_time(), 1.0);
638 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
639 }
640
641 #[test]
642 fn callback_adds_timed_plan() {
643 let mut context = Context::new();
644 context.queue_callback(|context| {
645 context.get_data_mut(ComponentA).push(1);
646 add_plan(context, 1.0, 2);
647 context.get_data_mut(ComponentA).push(3);
648 });
649 context.execute();
650 assert_eq!(context.get_current_time(), 1.0);
651 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 2]);
652 }
653
654 #[test]
655 fn callback_adds_callback_and_timed_plan() {
656 let mut context = Context::new();
657 context.queue_callback(|context| {
658 context.get_data_mut(ComponentA).push(1);
659 add_plan(context, 1.0, 2);
660 context.queue_callback(|context| {
661 context.get_data_mut(ComponentA).push(4);
662 });
663 context.get_data_mut(ComponentA).push(3);
664 });
665 context.execute();
666 assert_eq!(context.get_current_time(), 1.0);
667 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 4, 2]);
668 }
669
670 #[test]
671 fn timed_plan_adds_callback_and_timed_plan() {
672 let mut context = Context::new();
673 context.add_plan(1.0, |context| {
674 context.get_data_mut(ComponentA).push(1);
675 add_plan(context, 2.0, 3);
677 context.queue_callback(|context| {
678 context.get_data_mut(ComponentA).push(2);
679 });
680 });
681 context.execute();
682 assert_eq!(context.get_current_time(), 2.0);
683 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
684 }
685
686 #[test]
687 fn cancel_plan() {
688 let mut context = Context::new();
689 let to_cancel = add_plan(&mut context, 2.0, 1);
690 context.add_plan(1.0, move |context| {
691 context.cancel_plan(&to_cancel);
692 });
693 context.execute();
694 assert_eq!(context.get_current_time(), 1.0);
695 let test_vec: Vec<u32> = vec![];
696 assert_eq!(*context.get_data_mut(ComponentA), test_vec);
697 }
698
699 #[test]
700 fn add_plan_with_current_time() {
701 let mut context = Context::new();
702 context.add_plan(1.0, move |context| {
703 context.get_data_mut(ComponentA).push(1);
704 add_plan(context, 1.0, 2);
705 context.queue_callback(|context| {
706 context.get_data_mut(ComponentA).push(3);
707 });
708 });
709 context.execute();
710 assert_eq!(context.get_current_time(), 1.0);
711 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 2]);
712 }
713
714 #[test]
715 fn plans_at_same_time_fire_in_order() {
716 let mut context = Context::new();
717 add_plan(&mut context, 1.0, 1);
718 add_plan(&mut context, 1.0, 2);
719 context.execute();
720 assert_eq!(context.get_current_time(), 1.0);
721 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
722 }
723
724 #[test]
725 fn check_plan_phase_ordering() {
726 assert!(ExecutionPhase::First < ExecutionPhase::Normal);
727 assert!(ExecutionPhase::Normal < ExecutionPhase::Last);
728 }
729
730 #[test]
731 fn plans_at_same_time_follow_phase() {
732 let mut context = Context::new();
733 add_plan(&mut context, 1.0, 1);
734 add_plan_with_phase(&mut context, 1.0, 5, ExecutionPhase::Last);
735 add_plan_with_phase(&mut context, 1.0, 3, ExecutionPhase::First);
736 add_plan(&mut context, 1.0, 2);
737 add_plan_with_phase(&mut context, 1.0, 6, ExecutionPhase::Last);
738 add_plan_with_phase(&mut context, 1.0, 4, ExecutionPhase::First);
739 context.execute();
740 assert_eq!(context.get_current_time(), 1.0);
741 assert_eq!(*context.get_data_mut(ComponentA), vec![3, 4, 1, 2, 5, 6]);
742 }
743
744 #[derive(Copy, Clone, IxaEvent)]
745 struct Event1 {
746 pub data: usize,
747 }
748
749 #[derive(Copy, Clone, IxaEvent)]
750 struct Event2 {
751 pub data: usize,
752 }
753
754 #[test]
755 fn simple_event() {
756 let mut context = Context::new();
757 let obs_data = Rc::new(RefCell::new(0));
758 let obs_data_clone = Rc::clone(&obs_data);
759
760 context.subscribe_to_event::<Event1>(move |_, event| {
761 *obs_data_clone.borrow_mut() = event.data;
762 });
763
764 context.emit_event(Event1 { data: 1 });
765 context.execute();
766 assert_eq!(*obs_data.borrow(), 1);
767 }
768
769 #[test]
770 fn multiple_events() {
771 let mut context = Context::new();
772 let obs_data = Rc::new(RefCell::new(0));
773 let obs_data_clone = Rc::clone(&obs_data);
774
775 context.subscribe_to_event::<Event1>(move |_, event| {
776 *obs_data_clone.borrow_mut() += event.data;
777 });
778
779 context.emit_event(Event1 { data: 1 });
780 context.emit_event(Event1 { data: 2 });
781 context.execute();
782
783 assert_eq!(*obs_data.borrow(), 3);
785 }
786
787 #[test]
788 fn multiple_event_handlers() {
789 let mut context = Context::new();
790 let obs_data1 = Rc::new(RefCell::new(0));
791 let obs_data1_clone = Rc::clone(&obs_data1);
792 let obs_data2 = Rc::new(RefCell::new(0));
793 let obs_data2_clone = Rc::clone(&obs_data2);
794
795 context.subscribe_to_event::<Event1>(move |_, event| {
796 *obs_data1_clone.borrow_mut() = event.data;
797 });
798 context.subscribe_to_event::<Event1>(move |_, event| {
799 *obs_data2_clone.borrow_mut() = event.data;
800 });
801 context.emit_event(Event1 { data: 1 });
802 context.execute();
803 assert_eq!(*obs_data1.borrow(), 1);
804 assert_eq!(*obs_data2.borrow(), 1);
805 }
806
807 #[test]
808 fn multiple_event_types() {
809 let mut context = Context::new();
810 let obs_data1 = Rc::new(RefCell::new(0));
811 let obs_data1_clone = Rc::clone(&obs_data1);
812 let obs_data2 = Rc::new(RefCell::new(0));
813 let obs_data2_clone = Rc::clone(&obs_data2);
814
815 context.subscribe_to_event::<Event1>(move |_, event| {
816 *obs_data1_clone.borrow_mut() = event.data;
817 });
818 context.subscribe_to_event::<Event2>(move |_, event| {
819 *obs_data2_clone.borrow_mut() = event.data;
820 });
821 context.emit_event(Event1 { data: 1 });
822 context.emit_event(Event2 { data: 2 });
823 context.execute();
824 assert_eq!(*obs_data1.borrow(), 1);
825 assert_eq!(*obs_data2.borrow(), 2);
826 }
827
828 #[test]
829 fn subscribe_after_event() {
830 let mut context = Context::new();
831 let obs_data = Rc::new(RefCell::new(0));
832 let obs_data_clone = Rc::clone(&obs_data);
833
834 context.emit_event(Event1 { data: 1 });
835 context.subscribe_to_event::<Event1>(move |_, event| {
836 *obs_data_clone.borrow_mut() = event.data;
837 });
838
839 context.execute();
840 assert_eq!(*obs_data.borrow(), 0);
841 }
842
843 #[test]
844 fn shutdown_cancels_plans() {
845 let mut context = Context::new();
846 add_plan(&mut context, 1.0, 1);
847 context.add_plan(1.5, Context::shutdown);
848 add_plan(&mut context, 2.0, 2);
849 context.execute();
850 assert_eq!(context.get_current_time(), 1.5);
851 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
852 }
853
854 #[test]
855 fn shutdown_cancels_callbacks() {
856 let mut context = Context::new();
857 add_plan(&mut context, 1.0, 1);
858 context.add_plan(1.5, |context| {
859 context.queue_callback(|context| {
862 context.get_data_mut(ComponentA).push(3);
863 });
864 context.shutdown();
865 });
866 context.execute();
867 assert_eq!(context.get_current_time(), 1.5);
868 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
869 }
870
871 #[test]
872 fn shutdown_cancels_events() {
873 let mut context = Context::new();
874 let obs_data = Rc::new(RefCell::new(0));
875 let obs_data_clone = Rc::clone(&obs_data);
876 context.subscribe_to_event::<Event1>(move |_, event| {
877 *obs_data_clone.borrow_mut() = event.data;
878 });
879 context.emit_event(Event1 { data: 1 });
880 context.shutdown();
881 context.execute();
882 assert_eq!(*obs_data.borrow(), 0);
883 }
884
885 #[test]
886 #[allow(clippy::cast_sign_loss)]
887 #[allow(clippy::cast_possible_truncation)]
888 fn periodic_plan_self_schedules() {
889 let mut context = Context::new();
892 context.add_periodic_plan_with_phase(
893 1.0,
894 |context| {
895 let time = context.get_current_time();
896 context.get_data_mut(ComponentA).push(time as u32);
897 },
898 ExecutionPhase::Last,
899 );
900 context.add_plan(1.0, move |_context| {});
901 context.add_plan(1.5, move |_context| {});
902 context.execute();
903 assert_eq!(context.get_current_time(), 2.0);
904
905 assert_eq!(*context.get_data(ComponentA), vec![0, 1, 2]); }
907
908 #[test]
911 fn negative_plan_time_allowed() {
912 let mut context = Context::new();
913 context.set_start_time(-1.0);
914 add_plan(&mut context, -1.0, 1);
915 context.execute();
916 assert_eq!(context.get_current_time(), -1.0);
917 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
918 }
919
920 #[test]
921 fn add_plan_get_current_time() {
922 let mut context = Context::new();
923 let current_time = context.get_current_time();
924 add_plan(&mut context, current_time, 1);
925 context.execute();
926 assert_eq!(context.get_current_time(), 0.0);
927 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
928 }
929
930 #[test]
931 fn multiple_negative_plans() {
932 let mut context = Context::new();
933 context.set_start_time(-3.0);
934 add_plan(&mut context, -3.0, 1);
935 add_plan(&mut context, -1.0, 3);
936 add_plan(&mut context, -2.0, 2);
937 context.execute();
938 assert_eq!(context.get_current_time(), -1.0);
939 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
940 }
941
942 #[test]
943 fn negative_and_positive_plans() {
944 let mut context = Context::new();
945 context.set_start_time(-1.0);
946 add_plan(&mut context, -1.0, 1);
947 add_plan(&mut context, 1.0, 3);
948 add_plan(&mut context, 0.0, 2);
949 context.execute();
950 assert_eq!(context.get_current_time(), 1.0);
951 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
952 }
953
954 #[test]
955 fn get_current_time_before_execute_defaults() {
956 let mut context = Context::new();
957 assert_eq!(context.get_current_time(), 0.0);
958
959 context.set_start_time(-2.0);
960 assert_eq!(context.get_current_time(), -2.0);
961 }
962
963 #[test]
964 fn get_current_time_initializes_to_zero_when_all_positive() {
965 let mut context = Context::new();
966 let seen_time = Rc::new(RefCell::new(f64::NAN));
967 let seen_time_clone = Rc::clone(&seen_time);
968 context.queue_callback(move |ctx| {
969 *seen_time_clone.borrow_mut() = ctx.get_current_time();
970 });
971 context.execute();
972 assert_eq!(*seen_time.borrow(), 0.0);
973 }
974
975 #[test]
976 fn get_current_time_initializes_to_zero_when_empty() {
977 let mut context = Context::new();
978 context.execute();
979 assert_eq!(context.get_current_time(), 0.0);
980 }
981
982 #[test]
983 fn get_current_time_initializes_to_zero_with_plan() {
984 let mut context = Context::new();
985 add_plan(&mut context, 0.0, 1);
986 context.execute();
987 assert_eq!(context.get_current_time(), 0.0);
988 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
989 }
990
991 #[test]
992 #[should_panic(expected = "Time -1 is invalid")]
993 fn negative_time_from_callback_panics() {
994 let mut context = Context::new();
995 context.queue_callback(|context| {
996 context.get_data_mut(ComponentA).push(1);
997 add_plan(context, -1.0, 2);
998 });
999 add_plan(&mut context, 1.0, 3);
1000 context.execute();
1001 }
1002
1003 #[test]
1004 fn large_negative_time() {
1005 let mut context = Context::new();
1006 context.set_start_time(-1_000_000.0);
1007 add_plan(&mut context, -1_000_000.0, 1);
1008 context.execute();
1009 assert_eq!(context.get_current_time(), -1_000_000.0);
1010 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1011 }
1012
1013 #[test]
1014 fn very_small_negative_time() {
1015 let mut context = Context::new();
1016 context.set_start_time(-1e-10);
1017 add_plan(&mut context, -1e-10, 1);
1018 context.execute();
1019 assert_eq!(context.get_current_time(), -1e-10);
1020 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1021 }
1022
1023 #[test]
1024 fn negative_time_ordering_with_phases() {
1025 let mut context = Context::new();
1026 context.set_start_time(-1.0);
1027 add_plan_with_phase(&mut context, -1.0, 1, ExecutionPhase::Normal);
1028 add_plan_with_phase(&mut context, -1.0, 3, ExecutionPhase::Last);
1029 add_plan_with_phase(&mut context, -1.0, 2, ExecutionPhase::First);
1030 context.execute();
1031 assert_eq!(context.get_current_time(), -1.0);
1032 assert_eq!(*context.get_data_mut(ComponentA), vec![2, 1, 3]);
1033 }
1034
1035 #[test]
1036 #[should_panic(expected = "Time 4 is invalid")]
1037 fn cannot_schedule_plan_before_current_time() {
1038 let mut context = Context::new();
1039 add_plan(&mut context, 5.0, 1);
1040 context.add_plan(5.0, |context| {
1041 add_plan(context, 4.0, 2);
1043 });
1044 context.execute();
1045 }
1046
1047 #[test]
1048 fn get_current_time_multiple_calls_before_execute() {
1049 let mut context = Context::new();
1050 context.set_start_time(-2.0);
1051 add_plan(&mut context, -2.0, 1);
1052 context.execute();
1053 assert_eq!(context.get_current_time(), -2.0);
1054 }
1055
1056 #[test]
1057 fn negative_plan_can_add_positive_plan() {
1058 let mut context = Context::new();
1059 context.set_start_time(-1.0);
1060 add_plan(&mut context, -1.0, 1);
1061 context.add_plan(-1.0, |context| {
1062 add_plan(context, 2.0, 2);
1063 });
1064 context.execute();
1065 assert_eq!(context.get_current_time(), 2.0);
1066 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1067 }
1068
1069 #[test]
1070 fn negative_plan_can_schedule_negative_plan() {
1071 let mut context = Context::new();
1072 context.set_start_time(-2.0);
1073 add_plan(&mut context, -2.0, 1);
1074 context.add_plan(-2.0, |context| {
1075 add_plan(context, -1.0, 2);
1076 });
1077 context.execute();
1078 assert_eq!(context.get_current_time(), -1.0);
1079 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1080 }
1081
1082 #[test]
1083 #[should_panic(expected = "Start time has already been set. It can only be set once.")]
1084 fn set_start_time_only_once() {
1085 let mut context = Context::new();
1086 context.set_start_time(1.0);
1087 context.set_start_time(2.0);
1088 }
1089
1090 #[test]
1093 #[should_panic(expected = "Start time NaN must be finite")]
1094 fn set_start_time_nan_panics() {
1095 let mut context = Context::new();
1096 context.set_start_time(f64::NAN);
1097 }
1098
1099 #[test]
1100 #[should_panic(expected = "Start time inf must be finite")]
1101 fn set_start_time_inf_panics() {
1102 let mut context = Context::new();
1103 context.set_start_time(f64::INFINITY);
1104 }
1105
1106 #[test]
1107 fn set_start_time_equal_to_earliest_plan_allowed() {
1108 let mut context = Context::new();
1109 context.set_start_time(-2.0);
1110 add_plan(&mut context, -2.0, 1);
1111 context.execute();
1112 assert_eq!(context.get_current_time(), -2.0);
1113 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1114 }
1115
1116 #[test]
1120 fn set_start_time_with_only_callbacks_keeps_time() {
1121 let mut context = Context::new();
1122 context.set_start_time(5.0);
1123 context.queue_callback(|ctx| {
1124 ctx.get_data_mut(ComponentA).push(42);
1125 });
1126 context.execute();
1127 assert_eq!(context.get_current_time(), 5.0);
1128 assert_eq!(*context.get_data_mut(ComponentA), vec![42]);
1129 }
1130
1131 #[test]
1132 fn multiple_plans_final_time_is_last() {
1133 let mut context = Context::new();
1134 add_plan(&mut context, 1.0, 1);
1135 add_plan(&mut context, 3.0, 3);
1136 add_plan(&mut context, 2.0, 2);
1137 context.execute();
1138 assert_eq!(context.get_current_time(), 3.0);
1139 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1140 }
1141
1142 #[test]
1143 fn add_plan_same_time_fifo_and_phases() {
1144 let mut context = Context::new();
1145 add_plan_with_phase(&mut context, 1.0, 3, ExecutionPhase::Last);
1146 add_plan(&mut context, 1.0, 1);
1147 add_plan_with_phase(&mut context, 1.0, 2, ExecutionPhase::First);
1148 add_plan(&mut context, 1.0, 4);
1149 context.execute();
1150 assert_eq!(context.get_current_time(), 1.0);
1151 assert_eq!(*context.get_data_mut(ComponentA), vec![2, 1, 4, 3]);
1152 }
1153
1154 #[test]
1155 #[should_panic(expected = "Time -2 is invalid")]
1156 fn add_plan_less_than_current_time_panics() {
1157 let mut context = Context::new();
1158 context.set_start_time(-1.0);
1159 add_plan(&mut context, -1.0, 1);
1160 add_plan(&mut context, -2.0, 2);
1162 }
1163
1164 #[test]
1165 #[should_panic(expected = "Period must be greater than 0")]
1166 fn add_periodic_plan_zero_period_panics() {
1167 let mut context = Context::new();
1168 context.add_periodic_plan_with_phase(0.0, |_ctx| {}, ExecutionPhase::Normal);
1169 }
1170
1171 #[test]
1172 #[should_panic(expected = "Period must be greater than 0")]
1173 fn add_periodic_plan_nan_panics() {
1174 let mut context = Context::new();
1175 context.add_periodic_plan_with_phase(f64::NAN, |_ctx| {}, ExecutionPhase::Normal);
1176 }
1177
1178 #[test]
1179 #[should_panic(expected = "Period must be greater than 0")]
1180 fn add_periodic_plan_inf_panics() {
1181 let mut context = Context::new();
1182 context.add_periodic_plan_with_phase(f64::INFINITY, |_ctx| {}, ExecutionPhase::Normal);
1183 }
1184
1185 #[test]
1186 fn shutdown_requested_reset() {
1187 let mut context = Context::new();
1190 let _: PersonId = context.add_entity(with!(Person, Age(50))).unwrap();
1191
1192 context.add_plan(0.0, |ctx| {
1194 ctx.shutdown();
1195 });
1196
1197 context.execute();
1199 assert_eq!(context.get_current_time(), 0.0);
1200 assert_eq!(context.get_entity_count::<Person>(), 1);
1201
1202 context.add_plan(2.0, |ctx| {
1204 let _: PersonId = ctx.add_entity(with!(Person, Age(50))).unwrap();
1205 });
1206
1207 context.execute();
1211 assert_eq!(context.get_current_time(), 2.0);
1212 assert_eq!(
1213 context.get_entity_count::<Person>(),
1214 2,
1215 "If this fails, shutdown_requested was not properly reset"
1216 );
1217 }
1218}