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