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;
23#[cfg(feature = "debugger")]
24use crate::{debugger::enter_debugger, plan::PlanSchedule};
25use crate::{get_data_plugin_count, trace, warn, HashMap, HashMapExt};
26
27type Callback = dyn FnOnce(&mut Context);
29
30type EventHandler<E> = dyn Fn(&mut Context, E);
32
33pub trait IxaEvent {
34 fn on_subscribe(_context: &mut Context) {}
36}
37
38#[derive(PartialEq, Eq, Ord, Clone, Copy, PartialOrd, Hash, Debug)]
46pub enum ExecutionPhase {
47 First,
48 Normal,
49 Last,
50}
51
52impl Display for ExecutionPhase {
53 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
54 write!(f, "{self:?}")
55 }
56}
57
58pub struct Context {
86 plan_queue: Queue<Box<Callback>, ExecutionPhase>,
87 callback_queue: VecDeque<Box<Callback>>,
88 event_handlers: HashMap<TypeId, Box<dyn Any>>,
89 pub(crate) entity_store: EntityStore,
90 data_plugins: Vec<OnceCell<Box<dyn Any>>>,
91 #[cfg(feature = "debugger")]
92 breakpoints_scheduled: Queue<Box<Callback>, ExecutionPhase>,
93 current_time: Option<f64>,
94 start_time: Option<f64>,
95 shutdown_requested: bool,
96 #[cfg(feature = "debugger")]
97 break_requested: bool,
98 #[cfg(feature = "debugger")]
99 breakpoints_enabled: bool,
100 execution_profiler: ExecutionProfilingCollector,
101 pub(crate) print_execution_statistics: bool,
102}
103
104impl Context {
105 #[must_use]
107 pub fn new() -> Context {
108 let data_plugins = std::iter::repeat_with(OnceCell::new)
110 .take(get_data_plugin_count())
111 .collect();
112
113 Context {
114 plan_queue: Queue::new(),
115 callback_queue: VecDeque::new(),
116 event_handlers: HashMap::new(),
117 entity_store: EntityStore::new(),
118 data_plugins,
119 #[cfg(feature = "debugger")]
120 breakpoints_scheduled: Queue::new(),
121 current_time: None,
122 start_time: None,
123 shutdown_requested: false,
124 #[cfg(feature = "debugger")]
125 break_requested: false,
126 #[cfg(feature = "debugger")]
127 breakpoints_enabled: true,
128 execution_profiler: ExecutionProfilingCollector::new(),
129 print_execution_statistics: false,
130 }
131 }
132
133 pub(crate) fn get_property_value_store<E: Entity, P: Property<E>>(
134 &self,
135 ) -> &PropertyValueStoreCore<E, P> {
136 self.entity_store.get_property_store::<E>().get::<P>()
137 }
138 pub(crate) fn get_property_value_store_mut<E: Entity, P: Property<E>>(
139 &mut self,
140 ) -> &mut PropertyValueStoreCore<E, P> {
141 self.entity_store
142 .get_property_store_mut::<E>()
143 .get_mut::<P>()
144 }
145
146 #[cfg(feature = "debugger")]
154 pub fn schedule_debugger(
155 &mut self,
156 time: f64,
157 priority: Option<ExecutionPhase>,
158 callback: Box<Callback>,
159 ) {
160 trace!("scheduling debugger");
161 let priority = priority.unwrap_or(ExecutionPhase::First);
162 self.breakpoints_scheduled
163 .add_plan(time, callback, priority);
164 }
165
166 #[allow(clippy::missing_panics_doc)]
171 pub fn subscribe_to_event<E: IxaEvent + Copy + 'static>(
172 &mut self,
173 handler: impl Fn(&mut Context, E) + 'static,
174 ) {
175 let handler_vec = self
176 .event_handlers
177 .entry(TypeId::of::<E>())
178 .or_insert_with(|| Box::<Vec<Rc<EventHandler<E>>>>::default());
179 let handler_vec: &mut Vec<Rc<EventHandler<E>>> = handler_vec.downcast_mut().unwrap();
180 handler_vec.push(Rc::new(handler));
181 E::on_subscribe(self);
182 }
183
184 #[allow(clippy::missing_panics_doc)]
189 pub fn emit_event<E: IxaEvent + Copy + 'static>(&mut self, event: E) {
190 let Context {
192 event_handlers,
193 callback_queue,
194 ..
195 } = self;
196 if let Some(handler_vec) = event_handlers.get(&TypeId::of::<E>()) {
197 let handler_vec: &Vec<Rc<EventHandler<E>>> = handler_vec.downcast_ref().unwrap();
198 for handler in handler_vec {
199 let handler_clone = Rc::clone(handler);
200 callback_queue.push_back(Box::new(move |context| handler_clone(context, event)));
201 }
202 }
203 }
204
205 pub fn add_plan(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static) -> PlanId {
214 self.add_plan_with_phase(time, callback, ExecutionPhase::Normal)
215 }
216
217 pub fn add_plan_with_phase(
227 &mut self,
228 time: f64,
229 callback: impl FnOnce(&mut Context) + 'static,
230 phase: ExecutionPhase,
231 ) -> PlanId {
232 let current = self.get_current_time();
233 assert!(!time.is_nan(), "Time {time} is invalid: cannot be NaN");
234 assert!(
235 !time.is_infinite(),
236 "Time {time} is invalid: cannot be infinite"
237 );
238 assert!(
239 time >= current,
240 "Time {time} is invalid: cannot be less than the current time ({}). Consider calling set_start_time() before scheduling plans.",
241 current
242 );
243 self.plan_queue.add_plan(time, Box::new(callback), phase)
244 }
245
246 fn evaluate_periodic_and_schedule_next(
247 &mut self,
248 period: f64,
249 callback: impl Fn(&mut Context) + 'static,
250 phase: ExecutionPhase,
251 ) {
252 trace!(
253 "evaluate periodic at {} (period={})",
254 self.get_current_time(),
255 period
256 );
257 callback(self);
258 if !self.plan_queue.is_empty() {
259 let next_time = self.get_current_time() + period;
260 self.add_plan_with_phase(
261 next_time,
262 move |context| context.evaluate_periodic_and_schedule_next(period, callback, phase),
263 phase,
264 );
265 }
266 }
267
268 pub fn add_periodic_plan_with_phase(
281 &mut self,
282 period: f64,
283 callback: impl Fn(&mut Context) + 'static,
284 phase: ExecutionPhase,
285 ) {
286 assert!(
287 period > 0.0 && !period.is_nan() && !period.is_infinite(),
288 "Period must be greater than 0"
289 );
290
291 self.add_plan_with_phase(
292 0.0,
293 move |context| context.evaluate_periodic_and_schedule_next(period, callback, phase),
294 phase,
295 );
296 }
297
298 pub fn cancel_plan(&mut self, plan_id: &PlanId) {
305 trace!("canceling plan {plan_id:?}");
306 let result = self.plan_queue.cancel_plan(plan_id);
307 if result.is_none() {
308 warn!("Tried to cancel nonexistent plan with ID = {plan_id:?}");
309 }
310 }
311
312 #[doc(hidden)]
313 #[allow(dead_code)]
314 pub(crate) fn remaining_plan_count(&self) -> usize {
315 self.plan_queue.remaining_plan_count()
316 }
317
318 pub fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static) {
320 trace!("queuing callback");
321 self.callback_queue.push_back(Box::new(callback));
322 }
323
324 #[must_use]
333 #[allow(clippy::needless_pass_by_value)]
334 pub fn get_data_mut<T: DataPlugin>(&mut self, _data_plugin: T) -> &mut T::DataContainer {
335 let index = T::index_within_context();
336
337 if self.data_plugins[index].get().is_some() {
339 return self.data_plugins[index]
340 .get_mut()
341 .unwrap()
342 .downcast_mut::<T::DataContainer>()
343 .expect("TypeID does not match data plugin type");
344 }
345
346 let data = T::init(self);
348 let cell = self
349 .data_plugins
350 .get_mut(index)
351 .unwrap_or_else(|| panic!("No data plugin found with index = {index:?}. You must use the `define_data_plugin!` macro to create a data plugin."));
352 let _ = cell.set(Box::new(data));
353 cell.get_mut()
354 .unwrap()
355 .downcast_mut::<T::DataContainer>()
356 .expect("TypeID does not match data plugin type. You must use the `define_data_plugin!` macro to create a data plugin.")
357 }
358
359 #[must_use]
364 #[allow(clippy::needless_pass_by_value)]
365 pub fn get_data<T: DataPlugin>(&self, _data_plugin: T) -> &T::DataContainer {
366 let index = T::index_within_context();
367 self.data_plugins
368 .get(index)
369 .unwrap_or_else(|| panic!("No data plugin found with index = {index:?}. You must use the `define_data_plugin!` macro to create a data plugin."))
370 .get_or_init(|| Box::new(T::init(self)))
371 .downcast_ref::<T::DataContainer>()
372 .expect("TypeID does not match data plugin type. You must use the `define_data_plugin!` macro to create a data plugin.")
373 }
374
375 pub fn shutdown(&mut self) {
378 trace!("shutdown context");
379 self.shutdown_requested = true;
380 }
381
382 #[must_use]
390 pub fn get_current_time(&self) -> f64 {
391 self.current_time.or(self.start_time).unwrap_or(0.0)
392 }
393
394 pub fn set_start_time(&mut self, start_time: f64) {
410 assert!(
411 !start_time.is_nan() && !start_time.is_infinite(),
412 "Start time {start_time} must be finite"
413 );
414 assert!(
415 self.start_time.is_none(),
416 "Start time has already been set. It can only be set once."
417 );
418 assert!(
419 self.current_time.is_none(),
420 "Start time cannot be set after execution has begun."
421 );
422 if let Some(next_time) = self.plan_queue.next_time() {
423 assert!(
424 start_time <= next_time,
425 "Start time {} is later than the earliest scheduled plan time {}. Remove or reschedule existing plans first.",
426 start_time,
427 next_time
428 );
429 }
430 self.start_time = Some(start_time);
431 }
432
433 #[must_use]
435 pub fn get_start_time(&self) -> Option<f64> {
436 self.start_time
437 }
438
439 #[cfg(feature = "debugger")]
441 pub fn request_debugger(&mut self) {
442 self.break_requested = true;
443 }
444
445 #[cfg(feature = "debugger")]
447 pub fn cancel_debugger_request(&mut self) {
448 self.break_requested = false;
449 }
450
451 #[cfg(feature = "debugger")]
453 pub fn disable_breakpoints(&mut self) {
454 self.breakpoints_enabled = false;
455 }
456
457 #[cfg(feature = "debugger")]
459 pub fn enable_breakpoints(&mut self) {
460 self.breakpoints_enabled = true;
461 }
462
463 #[must_use]
465 #[cfg(feature = "debugger")]
466 pub fn breakpoints_are_enabled(&self) -> bool {
467 self.breakpoints_enabled
468 }
469
470 #[cfg(feature = "debugger")]
472 pub fn delete_breakpoint(&mut self, breakpoint_id: u64) -> Option<Box<Callback>> {
473 self.breakpoints_scheduled
474 .cancel_plan(&PlanId(breakpoint_id))
475 }
476
477 #[must_use]
480 #[cfg(feature = "debugger")]
481 pub fn list_breakpoints(&self, at_most: usize) -> Vec<&PlanSchedule<ExecutionPhase>> {
482 self.breakpoints_scheduled.list_schedules(at_most)
483 }
484
485 #[cfg(feature = "debugger")]
487 pub fn clear_breakpoints(&mut self) {
488 self.breakpoints_scheduled.clear();
489 }
490
491 pub fn execute(&mut self) {
493 trace!("entering event loop");
494
495 if self.current_time.is_none() {
496 self.current_time = Some(self.start_time.unwrap_or(0.0));
497 }
498
499 loop {
501 #[cfg(feature = "progress_bar")]
502 if crate::progress::MAX_TIME.get().is_some() {
503 update_timeline_progress(self.get_current_time());
504 }
505
506 #[cfg(feature = "debugger")]
507 if self.break_requested {
508 enter_debugger(self);
509 } else if self.shutdown_requested {
510 self.shutdown_requested = false;
511 break;
512 } else {
513 self.execute_single_step();
514 }
515
516 self.execution_profiler.refresh();
517
518 #[cfg(not(feature = "debugger"))]
519 if self.shutdown_requested {
520 self.shutdown_requested = false;
521 break;
522 } else {
523 self.execute_single_step();
524 }
525 }
526
527 let stats = self.get_execution_statistics();
528 if self.print_execution_statistics {
529 print_execution_statistics(&stats);
530 #[cfg(feature = "profiling")]
531 crate::profiling::print_profiling_data();
532 } else {
533 log_execution_statistics(&stats);
534 }
535 }
536
537 pub fn execute_single_step(&mut self) {
543 #[cfg(feature = "debugger")]
548 if let Some((bp, _)) = self.breakpoints_scheduled.peek() {
549 if let Some(plan_time) = self.plan_queue.next_time() {
554 if (bp.priority == ExecutionPhase::First && bp.time <= plan_time)
555 || (bp.priority == ExecutionPhase::Last && bp.time < plan_time)
556 {
557 self.breakpoints_scheduled.get_next_plan(); if self.breakpoints_enabled {
559 self.break_requested = true;
560 return;
561 }
562 }
563 } else {
564 self.breakpoints_scheduled.get_next_plan(); if self.breakpoints_enabled {
566 self.break_requested = true;
567 return;
568 }
569 }
570 }
571
572 if let Some(callback) = self.callback_queue.pop_front() {
574 trace!("calling callback");
575 callback(self);
576 }
577 else if let Some(plan) = self.plan_queue.get_next_plan() {
579 trace!("calling plan at {:.6}", plan.time);
580 self.current_time = Some(plan.time);
581 (plan.data)(self);
582 } else {
583 trace!("No callbacks or plans; exiting event loop");
584 self.shutdown_requested = true;
586 }
587 }
588
589 pub fn get_execution_statistics(&mut self) -> ExecutionStatistics {
590 self.execution_profiler.compute_final_statistics()
591 }
592}
593
594pub trait ContextBase: Sized {
595 fn subscribe_to_event<E: IxaEvent + Copy + 'static>(
596 &mut self,
597 handler: impl Fn(&mut Context, E) + 'static,
598 );
599 fn emit_event<E: IxaEvent + Copy + 'static>(&mut self, event: E);
600 fn add_plan(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static) -> PlanId;
601 fn add_plan_with_phase(
602 &mut self,
603 time: f64,
604 callback: impl FnOnce(&mut Context) + 'static,
605 phase: ExecutionPhase,
606 ) -> PlanId;
607 fn add_periodic_plan_with_phase(
608 &mut self,
609 period: f64,
610 callback: impl Fn(&mut Context) + 'static,
611 phase: ExecutionPhase,
612 );
613 fn cancel_plan(&mut self, plan_id: &PlanId);
614 fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static);
615 fn get_data_mut<T: DataPlugin>(&mut self, plugin: T) -> &mut T::DataContainer;
616 fn get_data<T: DataPlugin>(&self, plugin: T) -> &T::DataContainer;
617 fn get_current_time(&self) -> f64;
618 fn get_execution_statistics(&mut self) -> ExecutionStatistics;
619}
620impl ContextBase for Context {
621 delegate::delegate! {
622 to self {
623 fn subscribe_to_event<E: IxaEvent + Copy + 'static>(&mut self, handler: impl Fn(&mut Context, E) + 'static);
624 fn emit_event<E: IxaEvent + Copy + 'static>(&mut self, event: E);
625 fn add_plan(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static) -> PlanId;
626 fn add_plan_with_phase(&mut self, time: f64, callback: impl FnOnce(&mut Context) + 'static, phase: ExecutionPhase) -> PlanId;
627 fn add_periodic_plan_with_phase(&mut self, period: f64, callback: impl Fn(&mut Context) + 'static, phase: ExecutionPhase);
628 fn cancel_plan(&mut self, plan_id: &PlanId);
629 fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static);
630 fn get_data_mut<T: DataPlugin>(&mut self, plugin: T) -> &mut T::DataContainer;
631 fn get_data<T: DataPlugin>(&self, plugin: T) -> &T::DataContainer;
632 fn get_current_time(&self) -> f64;
633 fn get_execution_statistics(&mut self) -> ExecutionStatistics;
634 }
635 }
636}
637
638impl Default for Context {
639 fn default() -> Self {
640 Self::new()
641 }
642}
643
644#[cfg(test)]
645mod tests {
646 #![allow(dead_code)]
648 use std::cell::RefCell;
649
650 use ixa_derive::IxaEvent;
651
652 use super::*;
653 use crate::{define_data_plugin, define_entity, define_property, ContextEntitiesExt};
654
655 define_data_plugin!(ComponentA, Vec<u32>, vec![]);
656
657 define_entity!(Person);
658
659 define_property!(struct Age(u8), Person);
660
661 define_property!(
662 enum InfectionStatus {
663 Susceptible,
664 Infected,
665 Recovered,
666 },
667 Person,
668 default_const = InfectionStatus::Susceptible
669 );
670
671 define_property!(
672 struct Vaccinated(bool),
673 Person,
674 default_const = Vaccinated(false)
675 );
676
677 #[test]
678 fn empty_context() {
679 let mut context = Context::new();
680 context.execute();
681 assert_eq!(context.get_current_time(), 0.0);
682 }
683
684 #[test]
685 fn get_data() {
686 let mut context = Context::new();
687 context.get_data_mut(ComponentA).push(1);
688 assert_eq!(*context.get_data(ComponentA), vec![1],);
689 }
690
691 fn add_plan(context: &mut Context, time: f64, value: u32) -> PlanId {
692 context.add_plan(time, move |context| {
693 context.get_data_mut(ComponentA).push(value);
694 })
695 }
696
697 fn add_plan_with_phase(
698 context: &mut Context,
699 time: f64,
700 value: u32,
701 phase: ExecutionPhase,
702 ) -> PlanId {
703 context.add_plan_with_phase(
704 time,
705 move |context| {
706 context.get_data_mut(ComponentA).push(value);
707 },
708 phase,
709 )
710 }
711
712 #[test]
713 #[should_panic(expected = "Time inf is invalid")]
714 fn infinite_plan_time() {
715 let mut context = Context::new();
716 add_plan(&mut context, f64::INFINITY, 0);
717 }
718
719 #[test]
720 #[should_panic(expected = "Time NaN is invalid")]
721 fn nan_plan_time() {
722 let mut context = Context::new();
723 add_plan(&mut context, f64::NAN, 0);
724 }
725
726 #[test]
727 fn timed_plan_only() {
728 let mut context = Context::new();
729 add_plan(&mut context, 1.0, 1);
730 context.execute();
731 assert_eq!(context.get_current_time(), 1.0);
732 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
733 }
734
735 #[test]
736 fn callback_only() {
737 let mut context = Context::new();
738 context.queue_callback(|context| {
739 context.get_data_mut(ComponentA).push(1);
740 });
741 context.execute();
742 assert_eq!(context.get_current_time(), 0.0);
743 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
744 }
745
746 #[test]
747 fn callback_before_timed_plan() {
748 let mut context = Context::new();
749 context.queue_callback(|context| {
750 context.get_data_mut(ComponentA).push(1);
751 });
752 add_plan(&mut context, 1.0, 2);
753 context.execute();
754 assert_eq!(context.get_current_time(), 1.0);
755 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
756 }
757
758 #[test]
759 fn callback_adds_timed_plan() {
760 let mut context = Context::new();
761 context.queue_callback(|context| {
762 context.get_data_mut(ComponentA).push(1);
763 add_plan(context, 1.0, 2);
764 context.get_data_mut(ComponentA).push(3);
765 });
766 context.execute();
767 assert_eq!(context.get_current_time(), 1.0);
768 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 2]);
769 }
770
771 #[test]
772 fn callback_adds_callback_and_timed_plan() {
773 let mut context = Context::new();
774 context.queue_callback(|context| {
775 context.get_data_mut(ComponentA).push(1);
776 add_plan(context, 1.0, 2);
777 context.queue_callback(|context| {
778 context.get_data_mut(ComponentA).push(4);
779 });
780 context.get_data_mut(ComponentA).push(3);
781 });
782 context.execute();
783 assert_eq!(context.get_current_time(), 1.0);
784 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 4, 2]);
785 }
786
787 #[test]
788 fn timed_plan_adds_callback_and_timed_plan() {
789 let mut context = Context::new();
790 context.add_plan(1.0, |context| {
791 context.get_data_mut(ComponentA).push(1);
792 add_plan(context, 2.0, 3);
794 context.queue_callback(|context| {
795 context.get_data_mut(ComponentA).push(2);
796 });
797 });
798 context.execute();
799 assert_eq!(context.get_current_time(), 2.0);
800 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
801 }
802
803 #[test]
804 fn cancel_plan() {
805 let mut context = Context::new();
806 let to_cancel = add_plan(&mut context, 2.0, 1);
807 context.add_plan(1.0, move |context| {
808 context.cancel_plan(&to_cancel);
809 });
810 context.execute();
811 assert_eq!(context.get_current_time(), 1.0);
812 let test_vec: Vec<u32> = vec![];
813 assert_eq!(*context.get_data_mut(ComponentA), test_vec);
814 }
815
816 #[test]
817 fn add_plan_with_current_time() {
818 let mut context = Context::new();
819 context.add_plan(1.0, move |context| {
820 context.get_data_mut(ComponentA).push(1);
821 add_plan(context, 1.0, 2);
822 context.queue_callback(|context| {
823 context.get_data_mut(ComponentA).push(3);
824 });
825 });
826 context.execute();
827 assert_eq!(context.get_current_time(), 1.0);
828 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 2]);
829 }
830
831 #[test]
832 fn plans_at_same_time_fire_in_order() {
833 let mut context = Context::new();
834 add_plan(&mut context, 1.0, 1);
835 add_plan(&mut context, 1.0, 2);
836 context.execute();
837 assert_eq!(context.get_current_time(), 1.0);
838 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
839 }
840
841 #[test]
842 fn check_plan_phase_ordering() {
843 assert!(ExecutionPhase::First < ExecutionPhase::Normal);
844 assert!(ExecutionPhase::Normal < ExecutionPhase::Last);
845 }
846
847 #[test]
848 fn plans_at_same_time_follow_phase() {
849 let mut context = Context::new();
850 add_plan(&mut context, 1.0, 1);
851 add_plan_with_phase(&mut context, 1.0, 5, ExecutionPhase::Last);
852 add_plan_with_phase(&mut context, 1.0, 3, ExecutionPhase::First);
853 add_plan(&mut context, 1.0, 2);
854 add_plan_with_phase(&mut context, 1.0, 6, ExecutionPhase::Last);
855 add_plan_with_phase(&mut context, 1.0, 4, ExecutionPhase::First);
856 context.execute();
857 assert_eq!(context.get_current_time(), 1.0);
858 assert_eq!(*context.get_data_mut(ComponentA), vec![3, 4, 1, 2, 5, 6]);
859 }
860
861 #[derive(Copy, Clone, IxaEvent)]
862 struct Event1 {
863 pub data: usize,
864 }
865
866 #[derive(Copy, Clone, IxaEvent)]
867 struct Event2 {
868 pub data: usize,
869 }
870
871 #[test]
872 fn simple_event() {
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
877 context.subscribe_to_event::<Event1>(move |_, event| {
878 *obs_data_clone.borrow_mut() = event.data;
879 });
880
881 context.emit_event(Event1 { data: 1 });
882 context.execute();
883 assert_eq!(*obs_data.borrow(), 1);
884 }
885
886 #[test]
887 fn multiple_events() {
888 let mut context = Context::new();
889 let obs_data = Rc::new(RefCell::new(0));
890 let obs_data_clone = Rc::clone(&obs_data);
891
892 context.subscribe_to_event::<Event1>(move |_, event| {
893 *obs_data_clone.borrow_mut() += event.data;
894 });
895
896 context.emit_event(Event1 { data: 1 });
897 context.emit_event(Event1 { data: 2 });
898 context.execute();
899
900 assert_eq!(*obs_data.borrow(), 3);
902 }
903
904 #[test]
905 fn multiple_event_handlers() {
906 let mut context = Context::new();
907 let obs_data1 = Rc::new(RefCell::new(0));
908 let obs_data1_clone = Rc::clone(&obs_data1);
909 let obs_data2 = Rc::new(RefCell::new(0));
910 let obs_data2_clone = Rc::clone(&obs_data2);
911
912 context.subscribe_to_event::<Event1>(move |_, event| {
913 *obs_data1_clone.borrow_mut() = event.data;
914 });
915 context.subscribe_to_event::<Event1>(move |_, event| {
916 *obs_data2_clone.borrow_mut() = event.data;
917 });
918 context.emit_event(Event1 { data: 1 });
919 context.execute();
920 assert_eq!(*obs_data1.borrow(), 1);
921 assert_eq!(*obs_data2.borrow(), 1);
922 }
923
924 #[test]
925 fn multiple_event_types() {
926 let mut context = Context::new();
927 let obs_data1 = Rc::new(RefCell::new(0));
928 let obs_data1_clone = Rc::clone(&obs_data1);
929 let obs_data2 = Rc::new(RefCell::new(0));
930 let obs_data2_clone = Rc::clone(&obs_data2);
931
932 context.subscribe_to_event::<Event1>(move |_, event| {
933 *obs_data1_clone.borrow_mut() = event.data;
934 });
935 context.subscribe_to_event::<Event2>(move |_, event| {
936 *obs_data2_clone.borrow_mut() = event.data;
937 });
938 context.emit_event(Event1 { data: 1 });
939 context.emit_event(Event2 { data: 2 });
940 context.execute();
941 assert_eq!(*obs_data1.borrow(), 1);
942 assert_eq!(*obs_data2.borrow(), 2);
943 }
944
945 #[test]
946 fn subscribe_after_event() {
947 let mut context = Context::new();
948 let obs_data = Rc::new(RefCell::new(0));
949 let obs_data_clone = Rc::clone(&obs_data);
950
951 context.emit_event(Event1 { data: 1 });
952 context.subscribe_to_event::<Event1>(move |_, event| {
953 *obs_data_clone.borrow_mut() = event.data;
954 });
955
956 context.execute();
957 assert_eq!(*obs_data.borrow(), 0);
958 }
959
960 #[test]
961 fn shutdown_cancels_plans() {
962 let mut context = Context::new();
963 add_plan(&mut context, 1.0, 1);
964 context.add_plan(1.5, Context::shutdown);
965 add_plan(&mut context, 2.0, 2);
966 context.execute();
967 assert_eq!(context.get_current_time(), 1.5);
968 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
969 }
970
971 #[test]
972 fn shutdown_cancels_callbacks() {
973 let mut context = Context::new();
974 add_plan(&mut context, 1.0, 1);
975 context.add_plan(1.5, |context| {
976 context.queue_callback(|context| {
979 context.get_data_mut(ComponentA).push(3);
980 });
981 context.shutdown();
982 });
983 context.execute();
984 assert_eq!(context.get_current_time(), 1.5);
985 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
986 }
987
988 #[test]
989 fn shutdown_cancels_events() {
990 let mut context = Context::new();
991 let obs_data = Rc::new(RefCell::new(0));
992 let obs_data_clone = Rc::clone(&obs_data);
993 context.subscribe_to_event::<Event1>(move |_, event| {
994 *obs_data_clone.borrow_mut() = event.data;
995 });
996 context.emit_event(Event1 { data: 1 });
997 context.shutdown();
998 context.execute();
999 assert_eq!(*obs_data.borrow(), 0);
1000 }
1001
1002 #[test]
1003 #[allow(clippy::cast_sign_loss)]
1004 #[allow(clippy::cast_possible_truncation)]
1005 fn periodic_plan_self_schedules() {
1006 let mut context = Context::new();
1009 context.add_periodic_plan_with_phase(
1010 1.0,
1011 |context| {
1012 let time = context.get_current_time();
1013 context.get_data_mut(ComponentA).push(time as u32);
1014 },
1015 ExecutionPhase::Last,
1016 );
1017 context.add_plan(1.0, move |_context| {});
1018 context.add_plan(1.5, move |_context| {});
1019 context.execute();
1020 assert_eq!(context.get_current_time(), 2.0);
1021
1022 assert_eq!(*context.get_data(ComponentA), vec![0, 1, 2]); }
1024
1025 #[test]
1028 fn negative_plan_time_allowed() {
1029 let mut context = Context::new();
1030 context.set_start_time(-1.0);
1031 add_plan(&mut context, -1.0, 1);
1032 context.execute();
1033 assert_eq!(context.get_current_time(), -1.0);
1034 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1035 }
1036
1037 #[test]
1038 fn add_plan_get_current_time() {
1039 let mut context = Context::new();
1040 let current_time = context.get_current_time();
1041 add_plan(&mut context, current_time, 1);
1042 context.execute();
1043 assert_eq!(context.get_current_time(), 0.0);
1044 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1045 }
1046
1047 #[test]
1048 fn multiple_negative_plans() {
1049 let mut context = Context::new();
1050 context.set_start_time(-3.0);
1051 add_plan(&mut context, -3.0, 1);
1052 add_plan(&mut context, -1.0, 3);
1053 add_plan(&mut context, -2.0, 2);
1054 context.execute();
1055 assert_eq!(context.get_current_time(), -1.0);
1056 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1057 }
1058
1059 #[test]
1060 fn negative_and_positive_plans() {
1061 let mut context = Context::new();
1062 context.set_start_time(-1.0);
1063 add_plan(&mut context, -1.0, 1);
1064 add_plan(&mut context, 1.0, 3);
1065 add_plan(&mut context, 0.0, 2);
1066 context.execute();
1067 assert_eq!(context.get_current_time(), 1.0);
1068 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1069 }
1070
1071 #[test]
1072 fn get_current_time_before_execute_defaults() {
1073 let mut context = Context::new();
1074 assert_eq!(context.get_current_time(), 0.0);
1075
1076 context.set_start_time(-2.0);
1077 assert_eq!(context.get_current_time(), -2.0);
1078 }
1079
1080 #[test]
1081 fn get_current_time_initializes_to_zero_when_all_positive() {
1082 let mut context = Context::new();
1083 let seen_time = Rc::new(RefCell::new(f64::NAN));
1084 let seen_time_clone = Rc::clone(&seen_time);
1085 context.queue_callback(move |ctx| {
1086 *seen_time_clone.borrow_mut() = ctx.get_current_time();
1087 });
1088 context.execute();
1089 assert_eq!(*seen_time.borrow(), 0.0);
1090 }
1091
1092 #[test]
1093 fn get_current_time_initializes_to_zero_when_empty() {
1094 let mut context = Context::new();
1095 context.execute();
1096 assert_eq!(context.get_current_time(), 0.0);
1097 }
1098
1099 #[test]
1100 fn get_current_time_initializes_to_zero_with_plan() {
1101 let mut context = Context::new();
1102 add_plan(&mut context, 0.0, 1);
1103 context.execute();
1104 assert_eq!(context.get_current_time(), 0.0);
1105 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1106 }
1107
1108 #[test]
1109 #[should_panic(expected = "Time -1 is invalid")]
1110 fn negative_time_from_callback_panics() {
1111 let mut context = Context::new();
1112 context.queue_callback(|context| {
1113 context.get_data_mut(ComponentA).push(1);
1114 add_plan(context, -1.0, 2);
1115 });
1116 add_plan(&mut context, 1.0, 3);
1117 context.execute();
1118 }
1119
1120 #[test]
1121 fn large_negative_time() {
1122 let mut context = Context::new();
1123 context.set_start_time(-1_000_000.0);
1124 add_plan(&mut context, -1_000_000.0, 1);
1125 context.execute();
1126 assert_eq!(context.get_current_time(), -1_000_000.0);
1127 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1128 }
1129
1130 #[test]
1131 fn very_small_negative_time() {
1132 let mut context = Context::new();
1133 context.set_start_time(-1e-10);
1134 add_plan(&mut context, -1e-10, 1);
1135 context.execute();
1136 assert_eq!(context.get_current_time(), -1e-10);
1137 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1138 }
1139
1140 #[test]
1141 fn negative_time_ordering_with_phases() {
1142 let mut context = Context::new();
1143 context.set_start_time(-1.0);
1144 add_plan_with_phase(&mut context, -1.0, 1, ExecutionPhase::Normal);
1145 add_plan_with_phase(&mut context, -1.0, 3, ExecutionPhase::Last);
1146 add_plan_with_phase(&mut context, -1.0, 2, ExecutionPhase::First);
1147 context.execute();
1148 assert_eq!(context.get_current_time(), -1.0);
1149 assert_eq!(*context.get_data_mut(ComponentA), vec![2, 1, 3]);
1150 }
1151
1152 #[test]
1153 #[should_panic(expected = "Time 4 is invalid")]
1154 fn cannot_schedule_plan_before_current_time() {
1155 let mut context = Context::new();
1156 add_plan(&mut context, 5.0, 1);
1157 context.add_plan(5.0, |context| {
1158 add_plan(context, 4.0, 2);
1160 });
1161 context.execute();
1162 }
1163
1164 #[test]
1165 fn get_current_time_multiple_calls_before_execute() {
1166 let mut context = Context::new();
1167 context.set_start_time(-2.0);
1168 add_plan(&mut context, -2.0, 1);
1169 context.execute();
1170 assert_eq!(context.get_current_time(), -2.0);
1171 }
1172
1173 #[test]
1174 fn negative_plan_can_add_positive_plan() {
1175 let mut context = Context::new();
1176 context.set_start_time(-1.0);
1177 add_plan(&mut context, -1.0, 1);
1178 context.add_plan(-1.0, |context| {
1179 add_plan(context, 2.0, 2);
1180 });
1181 context.execute();
1182 assert_eq!(context.get_current_time(), 2.0);
1183 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1184 }
1185
1186 #[test]
1187 fn negative_plan_can_schedule_negative_plan() {
1188 let mut context = Context::new();
1189 context.set_start_time(-2.0);
1190 add_plan(&mut context, -2.0, 1);
1191 context.add_plan(-2.0, |context| {
1192 add_plan(context, -1.0, 2);
1193 });
1194 context.execute();
1195 assert_eq!(context.get_current_time(), -1.0);
1196 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1197 }
1198
1199 #[test]
1200 #[should_panic(expected = "Start time has already been set. It can only be set once.")]
1201 fn set_start_time_only_once() {
1202 let mut context = Context::new();
1203 context.set_start_time(1.0);
1204 context.set_start_time(2.0);
1205 }
1206
1207 #[test]
1210 #[should_panic(expected = "Start time NaN must be finite")]
1211 fn set_start_time_nan_panics() {
1212 let mut context = Context::new();
1213 context.set_start_time(f64::NAN);
1214 }
1215
1216 #[test]
1217 #[should_panic(expected = "Start time inf must be finite")]
1218 fn set_start_time_inf_panics() {
1219 let mut context = Context::new();
1220 context.set_start_time(f64::INFINITY);
1221 }
1222
1223 #[test]
1224 fn set_start_time_equal_to_earliest_plan_allowed() {
1225 let mut context = Context::new();
1226 context.set_start_time(-2.0);
1227 add_plan(&mut context, -2.0, 1);
1228 context.execute();
1229 assert_eq!(context.get_current_time(), -2.0);
1230 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1231 }
1232
1233 #[test]
1237 fn set_start_time_with_only_callbacks_keeps_time() {
1238 let mut context = Context::new();
1239 context.set_start_time(5.0);
1240 context.queue_callback(|ctx| {
1241 ctx.get_data_mut(ComponentA).push(42);
1242 });
1243 context.execute();
1244 assert_eq!(context.get_current_time(), 5.0);
1245 assert_eq!(*context.get_data_mut(ComponentA), vec![42]);
1246 }
1247
1248 #[test]
1249 fn multiple_plans_final_time_is_last() {
1250 let mut context = Context::new();
1251 add_plan(&mut context, 1.0, 1);
1252 add_plan(&mut context, 3.0, 3);
1253 add_plan(&mut context, 2.0, 2);
1254 context.execute();
1255 assert_eq!(context.get_current_time(), 3.0);
1256 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1257 }
1258
1259 #[test]
1260 fn add_plan_same_time_fifo_and_phases() {
1261 let mut context = Context::new();
1262 add_plan_with_phase(&mut context, 1.0, 3, ExecutionPhase::Last);
1263 add_plan(&mut context, 1.0, 1);
1264 add_plan_with_phase(&mut context, 1.0, 2, ExecutionPhase::First);
1265 add_plan(&mut context, 1.0, 4);
1266 context.execute();
1267 assert_eq!(context.get_current_time(), 1.0);
1268 assert_eq!(*context.get_data_mut(ComponentA), vec![2, 1, 4, 3]);
1269 }
1270
1271 #[test]
1272 #[should_panic(expected = "Time -2 is invalid")]
1273 fn add_plan_less_than_current_time_panics() {
1274 let mut context = Context::new();
1275 context.set_start_time(-1.0);
1276 add_plan(&mut context, -1.0, 1);
1277 add_plan(&mut context, -2.0, 2);
1279 }
1280
1281 #[test]
1282 #[should_panic(expected = "Period must be greater than 0")]
1283 fn add_periodic_plan_zero_period_panics() {
1284 let mut context = Context::new();
1285 context.add_periodic_plan_with_phase(0.0, |_ctx| {}, ExecutionPhase::Normal);
1286 }
1287
1288 #[test]
1289 #[should_panic(expected = "Period must be greater than 0")]
1290 fn add_periodic_plan_nan_panics() {
1291 let mut context = Context::new();
1292 context.add_periodic_plan_with_phase(f64::NAN, |_ctx| {}, ExecutionPhase::Normal);
1293 }
1294
1295 #[test]
1296 #[should_panic(expected = "Period must be greater than 0")]
1297 fn add_periodic_plan_inf_panics() {
1298 let mut context = Context::new();
1299 context.add_periodic_plan_with_phase(f64::INFINITY, |_ctx| {}, ExecutionPhase::Normal);
1300 }
1301
1302 #[test]
1303 fn shutdown_requested_reset() {
1304 let mut context = Context::new();
1307 let _: PersonId = context.add_entity((Age(50),)).unwrap();
1308
1309 context.add_plan(0.0, |ctx| {
1311 ctx.shutdown();
1312 });
1313
1314 context.execute();
1316 assert_eq!(context.get_current_time(), 0.0);
1317 assert_eq!(context.get_entity_count::<Person>(), 1);
1318
1319 context.add_plan(2.0, |ctx| {
1321 let _: PersonId = ctx.add_entity((Age(50),)).unwrap();
1322 });
1323
1324 context.execute();
1328 assert_eq!(context.get_current_time(), 2.0);
1329 assert_eq!(
1330 context.get_entity_count::<Person>(),
1331 2,
1332 "If this fails, shutdown_requested was not properly reset"
1333 );
1334 }
1335}