1use std::any::{Any, TypeId};
6use std::cell::OnceCell;
7use std::collections::VecDeque;
8use std::fmt::{Display, Formatter};
9use std::marker::PhantomData;
10use std::rc::Rc;
11
12use crate::data_plugin::DataPlugin;
13use crate::entity::entity_store::EntityStore;
14use crate::entity::multi_property::emit_pre_main_diagnostics;
15use crate::entity::property::Property;
16use crate::entity::property_value_store_core::PropertyValueStoreCore;
17use crate::entity::Entity;
18use crate::execution_stats::{
19 log_execution_statistics, print_execution_statistics, ExecutionProfilingCollector,
20 ExecutionStatistics,
21};
22use crate::global_properties::get_global_property_count;
23use crate::plan_queue::{PlanId, PlanQueue};
24use crate::{get_data_plugin_count, trace, warn, HashMap, HashMapExt};
25
26type Callback = dyn FnOnce(&mut Context);
28
29type EventHandler<E> = dyn Fn(&mut Context, E);
31
32#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
37pub struct EventListenerId<E: IxaEvent> {
38 id: usize,
39 event_type: PhantomData<fn() -> E>,
40}
41
42impl<E: IxaEvent> EventListenerId<E> {
43 fn new(id: usize) -> Self {
44 Self {
45 id,
46 event_type: PhantomData,
47 }
48 }
49}
50
51struct EventHandlerRegistration<E: IxaEvent> {
52 listener_id: EventListenerId<E>,
53 handler: Rc<EventHandler<E>>,
54}
55
56pub trait IxaEvent: Copy + 'static {
57 fn on_subscribe(_context: &mut Context) {}
60
61 fn on_unsubscribe(_context: &mut Context) {}
64}
65
66#[derive(PartialEq, Eq, Ord, Clone, Copy, PartialOrd, Hash, Debug)]
74pub enum ExecutionPhase {
75 First,
76 Normal,
77 Last,
78}
79
80impl Display for ExecutionPhase {
81 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
82 write!(f, "{self:?}")
83 }
84}
85
86#[derive(Clone, Copy, Debug, Eq, PartialEq)]
93enum ShutdownStatus {
94 None,
96 Normal,
102 ShutdownTimePlans,
108 Stopped,
114}
115
116pub struct Context {
144 plan_queue: PlanQueue,
145 callback_queue: VecDeque<Box<Callback>>,
146 event_handlers: HashMap<TypeId, Box<dyn Any>>,
147 next_event_listener_id: usize,
148 pub(crate) entity_store: EntityStore,
149 data_plugins: Vec<OnceCell<Box<dyn Any>>>,
150 pub(crate) global_properties: Vec<OnceCell<Box<dyn Any>>>,
151 current_time: Option<f64>,
152 start_time: Option<f64>,
153 shutdown_status: ShutdownStatus,
154 execution_profiler: ExecutionProfilingCollector,
155 pub(crate) print_execution_statistics: bool,
156}
157
158impl Context {
159 #[must_use]
161 pub fn new() -> Context {
162 emit_pre_main_diagnostics();
163
164 let data_plugins = std::iter::repeat_with(OnceCell::new)
166 .take(get_data_plugin_count())
167 .collect();
168 let global_properties = std::iter::repeat_with(OnceCell::new)
169 .take(get_global_property_count())
170 .collect();
171
172 Context {
173 plan_queue: PlanQueue::new(),
174 callback_queue: VecDeque::new(),
175 event_handlers: HashMap::new(),
176 next_event_listener_id: 0,
177 entity_store: EntityStore::new(),
178 data_plugins,
179 global_properties,
180 current_time: None,
181 start_time: None,
182 shutdown_status: ShutdownStatus::None,
183 execution_profiler: ExecutionProfilingCollector::new(),
184 print_execution_statistics: false,
185 }
186 }
187
188 pub(crate) fn get_property_value_store<E: Entity, P: Property<E>>(
189 &self,
190 ) -> &PropertyValueStoreCore<E, P> {
191 self.entity_store.get_property_store::<E>().get::<P>()
192 }
193 pub(crate) fn get_property_value_store_mut<E: Entity, P: Property<E>>(
194 &mut self,
195 ) -> &mut PropertyValueStoreCore<E, P> {
196 self.entity_store
197 .get_property_store_mut::<E>()
198 .get_mut::<P>()
199 }
200
201 pub fn subscribe_to_event<E: IxaEvent>(
206 &mut self,
207 handler: impl Fn(&mut Context, E) + 'static,
208 ) -> EventListenerId<E> {
209 let listener_id = EventListenerId::new(self.next_event_listener_id);
210 self.next_event_listener_id = self
211 .next_event_listener_id
212 .checked_add(1)
213 .unwrap_or_else(|| panic!("event listener id overflow"));
214
215 let handler_vec = self
216 .event_handlers
217 .entry(TypeId::of::<E>())
218 .or_insert_with(|| Box::<Vec<EventHandlerRegistration<E>>>::default());
219 let handler_vec: &mut Vec<EventHandlerRegistration<E>> =
220 handler_vec.downcast_mut().unwrap();
221 handler_vec.push(EventHandlerRegistration {
222 listener_id,
223 handler: Rc::new(handler),
224 });
225 E::on_subscribe(self);
226 listener_id
227 }
228
229 #[allow(clippy::missing_panics_doc)]
234 pub fn unsubscribe_from_event<E: IxaEvent>(
235 &mut self,
236 listener_id: &EventListenerId<E>,
237 ) -> bool {
238 {
239 let Some(handler_vec) = self.event_handlers.get_mut(&TypeId::of::<E>()) else {
240 return false;
241 };
242 let handler_vec: &mut Vec<EventHandlerRegistration<E>> =
243 handler_vec.downcast_mut().unwrap();
244 let Some(index) = handler_vec
245 .iter()
246 .position(|entry| entry.listener_id.id == listener_id.id)
247 else {
248 return false;
249 };
250
251 handler_vec.swap_remove(index);
252 }
253
254 E::on_unsubscribe(self);
255 true
256 }
257
258 pub(crate) fn has_event_handlers<E: IxaEvent>(&self) -> bool {
259 self.event_handlers
260 .get(&TypeId::of::<E>())
261 .is_some_and(|handler_vec| {
262 let handler_vec: &Vec<EventHandlerRegistration<E>> =
263 handler_vec.downcast_ref().unwrap();
264 !handler_vec.is_empty()
265 })
266 }
267
268 pub fn emit_event<E: IxaEvent>(&mut self, event: E) {
273 let Context {
275 event_handlers,
276 callback_queue,
277 ..
278 } = self;
279 if let Some(handler_vec) = event_handlers.get(&TypeId::of::<E>()) {
280 let handler_vec: &Vec<EventHandlerRegistration<E>> =
281 handler_vec.downcast_ref().unwrap();
282 for registration in handler_vec {
283 let handler_clone = Rc::clone(®istration.handler);
284 callback_queue.push_back(Box::new(move |context| handler_clone(context, event)));
285 }
286 }
287 }
288
289 pub fn add_plan(
315 &mut self,
316 time: impl Into<f64>,
317 callback: impl FnOnce(&mut Context) + 'static,
318 ) -> PlanId {
319 self.add_plan_with_phase(time, callback, ExecutionPhase::Normal)
320 }
321
322 pub fn add_plan_with_phase(
334 &mut self,
335 time: impl Into<f64>,
336 callback: impl FnOnce(&mut Context) + 'static,
337 phase: ExecutionPhase,
338 ) -> PlanId {
339 self.add_plan_with_phase_and_passivity(time.into(), callback, phase, false)
340 }
341
342 pub fn add_passive_plan(
356 &mut self,
357 time: impl Into<f64>,
358 callback: impl FnOnce(&mut Context) + 'static,
359 ) -> PlanId {
360 self.add_passive_plan_with_phase(time, callback, ExecutionPhase::Normal)
361 }
362
363 pub fn schedule_shutdown(&mut self, time: f64) -> PlanId {
394 self.add_passive_plan(time, Context::shutdown)
395 }
396
397 pub fn add_passive_plan_with_phase(
411 &mut self,
412 time: impl Into<f64>,
413 callback: impl FnOnce(&mut Context) + 'static,
414 phase: ExecutionPhase,
415 ) -> PlanId {
416 self.add_plan_with_phase_and_passivity(time.into(), callback, phase, true)
417 }
418
419 fn add_plan_with_phase_and_passivity(
420 &mut self,
421 time: f64,
422 callback: impl FnOnce(&mut Context) + 'static,
423 phase: ExecutionPhase,
424 is_passive: bool,
425 ) -> PlanId {
426 let current = self.get_current_time();
427 assert!(!time.is_nan(), "Time {time} is invalid: cannot be NaN");
428 assert!(
429 !time.is_infinite(),
430 "Time {time} is invalid: cannot be infinite"
431 );
432 assert!(
433 time >= current,
434 "Time {time} is invalid: cannot be less than the current time ({}). Consider calling set_start_time() before scheduling plans.",
435 current
436 );
437 self.plan_queue
438 .add_plan(time, Box::new(callback), phase, is_passive)
439 }
440
441 pub fn add_shutdown_plan(&mut self, callback: impl FnOnce(&mut Context) + 'static) -> PlanId {
450 self.add_shutdown_plan_with_phase(callback, ExecutionPhase::Normal)
451 }
452
453 pub fn add_shutdown_plan_with_phase(
461 &mut self,
462 callback: impl FnOnce(&mut Context) + 'static,
463 phase: ExecutionPhase,
464 ) -> PlanId {
465 self.plan_queue.add_shutdown_plan(Box::new(callback), phase)
466 }
467
468 pub(crate) fn evaluate_periodic_and_schedule_next(
469 &mut self,
470 period: f64,
471 callback: impl Fn(&mut Context) + 'static,
472 phase: ExecutionPhase,
473 ) {
474 trace!(
475 "evaluate periodic at {} (period={})",
476 self.get_current_time(),
477 period
478 );
479 callback(self);
480 let next_time = self.get_current_time() + period;
481 self.add_passive_plan_with_phase(
482 next_time,
483 move |context| context.evaluate_periodic_and_schedule_next(period, callback, phase),
484 phase,
485 );
486 }
487
488 pub fn add_periodic_plan_with_phase(
508 &mut self,
509 period: impl Into<f64>,
510 callback: impl Fn(&mut Context) + 'static,
511 phase: ExecutionPhase,
512 ) {
513 let period = period.into();
514 assert!(
515 period > 0.0 && !period.is_nan() && !period.is_infinite(),
516 "Period must be greater than 0"
517 );
518
519 self.add_passive_plan_with_phase(
520 0.0,
521 move |context| context.evaluate_periodic_and_schedule_next(period, callback, phase),
522 phase,
523 );
524 }
525
526 pub fn cancel_plan(&mut self, plan_id: &PlanId) {
533 trace!("canceling plan {plan_id:?}");
534 let result = self.plan_queue.cancel_plan(plan_id);
535 if result.is_none() {
536 warn!("Tried to cancel nonexistent plan with ID = {plan_id:?}");
537 }
538 }
539
540 pub fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static) {
542 trace!("queuing callback");
543 self.callback_queue.push_back(Box::new(callback));
544 }
545
546 #[must_use]
555 pub fn get_data_mut<T: DataPlugin>(&mut self, _data_plugin: T) -> &mut T::DataContainer {
556 let index = T::index_within_context();
557
558 if self.data_plugins[index].get().is_some() {
560 return self.data_plugins[index]
561 .get_mut()
562 .unwrap()
563 .downcast_mut::<T::DataContainer>()
564 .expect("TypeID does not match data plugin type");
565 }
566
567 let data = T::init(self);
569 let cell = self
570 .data_plugins
571 .get_mut(index)
572 .unwrap_or_else(|| panic!("No data plugin found with index = {index:?}. You must use the `define_data_plugin!` macro to create a data plugin."));
573 let _ = cell.set(Box::new(data));
574 cell.get_mut()
575 .unwrap()
576 .downcast_mut::<T::DataContainer>()
577 .expect("TypeID does not match data plugin type. You must use the `define_data_plugin!` macro to create a data plugin.")
578 }
579
580 #[must_use]
585 pub fn get_data<T: DataPlugin>(&self, _data_plugin: T) -> &T::DataContainer {
586 let index = T::index_within_context();
587 self.data_plugins
588 .get(index)
589 .unwrap_or_else(|| panic!("No data plugin found with index = {index:?}. You must use the `define_data_plugin!` macro to create a data plugin."))
590 .get_or_init(|| Box::new(T::init(self)))
591 .downcast_ref::<T::DataContainer>()
592 .expect("TypeID does not match data plugin type. You must use the `define_data_plugin!` macro to create a data plugin.")
593 }
594
595 pub fn shutdown(&mut self) {
602 trace!("shutdown context");
603 if self.shutdown_status == ShutdownStatus::None {
604 self.shutdown_status = ShutdownStatus::Normal;
605 }
606 }
607
608 pub fn abort(&mut self) {
613 trace!("abort context");
614 self.shutdown_status = ShutdownStatus::Stopped;
615 }
616
617 #[must_use]
625 pub fn get_current_time(&self) -> f64 {
626 self.current_time.or(self.start_time).unwrap_or(0.0)
627 }
628
629 pub fn set_start_time(&mut self, start_time: impl Into<f64>) {
647 let start_time = start_time.into();
648 assert!(
649 !start_time.is_nan() && !start_time.is_infinite(),
650 "Start time {start_time} must be finite"
651 );
652 assert!(
653 self.start_time.is_none(),
654 "Start time has already been set. It can only be set once."
655 );
656 assert!(
657 self.current_time.is_none(),
658 "Start time cannot be set after execution has begun."
659 );
660 if let Some(next_time) = self.plan_queue.next_time() {
661 assert!(
662 start_time <= next_time,
663 "Start time {} is later than the earliest scheduled plan time {}. Remove or reschedule existing plans first.",
664 start_time,
665 next_time
666 );
667 }
668 self.start_time = Some(start_time);
669 }
670
671 #[must_use]
673 pub fn get_start_time(&self) -> Option<f64> {
674 self.start_time
675 }
676
677 pub fn execute(&mut self) {
680 trace!("entering event loop");
681
682 if self.shutdown_status == ShutdownStatus::Stopped {
683 self.shutdown_status = ShutdownStatus::None;
684 }
685
686 if self.current_time.is_none() {
687 self.current_time = Some(self.start_time.unwrap_or(0.0));
688 }
689
690 loop {
692 if self.shutdown_status == ShutdownStatus::Stopped {
693 self.shutdown_status = ShutdownStatus::None;
694 break;
695 }
696
697 self.execute_single_step();
698 self.execution_profiler.refresh();
699 }
700
701 let stats = self.get_execution_statistics();
702 if self.print_execution_statistics {
703 print_execution_statistics(&stats);
704 #[cfg(feature = "profiling")]
705 crate::profiling::print_profiling_data();
706 } else {
707 log_execution_statistics(&stats);
708 }
709 }
710
711 pub fn execute_single_step(&mut self) {
713 if let Some(callback) = self.callback_queue.pop_front() {
717 trace!("calling callback");
718 callback(self);
719 return;
720 }
721
722 match self.shutdown_status {
725 ShutdownStatus::None => {
726 if let Some(plan) = self.plan_queue.pop_next_if_active() {
731 trace!("calling plan at {:.6}", plan.time);
732 self.current_time = Some(plan.time);
733 (plan.data)(self);
734 } else {
735 self.shutdown_status = ShutdownStatus::Normal;
736 }
737 }
738 ShutdownStatus::Normal => {
739 if let Some(plan) = self.plan_queue.pop_next_at(self.get_current_time()) {
743 trace!("calling plan at {:.6}", plan.time);
744 (plan.data)(self);
745 } else {
746 self.shutdown_status = ShutdownStatus::ShutdownTimePlans;
747 }
748 }
749 ShutdownStatus::ShutdownTimePlans => {
750 if let Some(plan) = self.plan_queue.pop_next_shutdown() {
753 trace!("calling shutdown-time plan");
754 (plan.data)(self);
755 } else {
756 self.shutdown_status = ShutdownStatus::Stopped;
757 }
758 }
759 ShutdownStatus::Stopped => {
760 self.shutdown_status = ShutdownStatus::None;
764 }
765 }
766 }
767
768 #[must_use]
769 pub fn get_execution_statistics(&mut self) -> ExecutionStatistics {
770 #[allow(unused_mut)]
771 let mut stats = self.execution_profiler.compute_final_statistics();
772 #[cfg(feature = "profiling")]
773 {
774 stats.max_plans_in_flight = self.plan_queue.max_plans_in_flight;
775 stats.max_plan_queue_memory_in_use = self.plan_queue.max_memory_in_use;
776 }
777 stats
778 }
779}
780
781pub trait ContextBase: Sized {
782 fn subscribe_to_event<E: IxaEvent>(
783 &mut self,
784 handler: impl Fn(&mut Context, E) + 'static,
785 ) -> EventListenerId<E>;
786 fn unsubscribe_from_event<E: IxaEvent>(&mut self, listener_id: &EventListenerId<E>) -> bool;
787 fn emit_event<E: IxaEvent>(&mut self, event: E);
788 fn add_plan(
789 &mut self,
790 time: impl Into<f64>,
791 callback: impl FnOnce(&mut Context) + 'static,
792 ) -> PlanId;
793 fn add_plan_with_phase(
794 &mut self,
795 time: impl Into<f64>,
796 callback: impl FnOnce(&mut Context) + 'static,
797 phase: ExecutionPhase,
798 ) -> PlanId;
799 fn add_passive_plan(
800 &mut self,
801 time: impl Into<f64>,
802 callback: impl FnOnce(&mut Context) + 'static,
803 ) -> PlanId;
804 fn add_passive_plan_with_phase(
805 &mut self,
806 time: impl Into<f64>,
807 callback: impl FnOnce(&mut Context) + 'static,
808 phase: ExecutionPhase,
809 ) -> PlanId;
810 fn schedule_shutdown(&mut self, time: f64) -> PlanId {
814 self.add_passive_plan(time, Context::shutdown)
815 }
816 fn add_shutdown_plan(&mut self, callback: impl FnOnce(&mut Context) + 'static) -> PlanId;
817 fn add_shutdown_plan_with_phase(
818 &mut self,
819 callback: impl FnOnce(&mut Context) + 'static,
820 phase: ExecutionPhase,
821 ) -> PlanId;
822 fn add_periodic_plan_with_phase(
823 &mut self,
824 period: impl Into<f64>,
825 callback: impl Fn(&mut Context) + 'static,
826 phase: ExecutionPhase,
827 );
828 fn cancel_plan(&mut self, plan_id: &PlanId);
829 fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static);
830 #[must_use]
831 fn get_data_mut<T: DataPlugin>(&mut self, plugin: T) -> &mut T::DataContainer;
832 #[must_use]
833 fn get_data<T: DataPlugin>(&self, plugin: T) -> &T::DataContainer;
834 #[must_use]
835 fn get_current_time(&self) -> f64;
836 #[must_use]
837 fn get_execution_statistics(&mut self) -> ExecutionStatistics;
838 fn abort(&mut self);
839}
840impl ContextBase for Context {
841 delegate::delegate! {
842 to self {
843 fn subscribe_to_event<E: IxaEvent>(&mut self, handler: impl Fn(&mut Context, E) + 'static) -> EventListenerId<E>;
844 fn unsubscribe_from_event<E: IxaEvent>(&mut self, listener_id: &EventListenerId<E>) -> bool;
845 fn emit_event<E: IxaEvent>(&mut self, event: E);
846 fn add_plan(&mut self, time: impl Into<f64>, callback: impl FnOnce(&mut Context) + 'static) -> PlanId;
847 fn add_plan_with_phase(&mut self, time: impl Into<f64>, callback: impl FnOnce(&mut Context) + 'static, phase: ExecutionPhase) -> PlanId;
848 fn add_passive_plan(&mut self, time: impl Into<f64>, callback: impl FnOnce(&mut Context) + 'static) -> PlanId;
849 fn add_passive_plan_with_phase(&mut self, time: impl Into<f64>, callback: impl FnOnce(&mut Context) + 'static, phase: ExecutionPhase) -> PlanId;
850 fn add_shutdown_plan(&mut self, callback: impl FnOnce(&mut Context) + 'static) -> PlanId;
851 fn add_shutdown_plan_with_phase(&mut self, callback: impl FnOnce(&mut Context) + 'static, phase: ExecutionPhase) -> PlanId;
852 fn add_periodic_plan_with_phase(&mut self, period: impl Into<f64>, callback: impl Fn(&mut Context) + 'static, phase: ExecutionPhase);
853 fn cancel_plan(&mut self, plan_id: &PlanId);
854 fn queue_callback(&mut self, callback: impl FnOnce(&mut Context) + 'static);
855 fn get_data_mut<T: DataPlugin>(&mut self, plugin: T) -> &mut T::DataContainer;
856 fn get_data<T: DataPlugin>(&self, plugin: T) -> &T::DataContainer;
857 fn get_current_time(&self) -> f64;
858 fn get_execution_statistics(&mut self) -> ExecutionStatistics;
859 fn abort(&mut self);
860 }
861 }
862}
863
864impl Default for Context {
865 fn default() -> Self {
866 Self::new()
867 }
868}
869
870#[cfg(test)]
871mod tests {
872 #![allow(dead_code)]
874 use std::cell::RefCell;
875 use std::marker::PhantomData;
876
877 use super::*;
878 use crate::{
879 define_data_plugin, define_entity, define_property, with, ContextEntitiesExt, IxaEvent,
880 };
881
882 #[derive(Clone, Copy)]
883 struct WrappedF64(f64);
884
885 impl From<WrappedF64> for f64 {
886 fn from(value: WrappedF64) -> Self {
887 value.0
888 }
889 }
890
891 define_data_plugin!(ComponentA, Vec<u32>, vec![]);
892 define_data_plugin!(UnsubscribeHookObservations, Vec<bool>, vec![]);
893
894 define_entity!(Person);
895
896 define_property!(struct Age(u8), Person);
897
898 define_property!(
899 enum InfectionStatus {
900 Susceptible,
901 Infected,
902 Recovered,
903 },
904 Person,
905 default_const = InfectionStatus::Susceptible
906 );
907
908 define_property!(
909 struct Vaccinated(bool),
910 Person,
911 default_const = Vaccinated(false)
912 );
913
914 #[test]
915 fn empty_context() {
916 let mut context = Context::new();
917 context.execute();
918 assert_eq!(context.get_current_time(), 0.0);
919 }
920
921 #[test]
922 fn get_data() {
923 let mut context = Context::new();
924 context.get_data_mut(ComponentA).push(1);
925 assert_eq!(*context.get_data(ComponentA), vec![1],);
926 }
927
928 fn add_plan(context: &mut Context, time: f64, value: u32) -> PlanId {
929 context.add_plan(time, move |context| {
930 context.get_data_mut(ComponentA).push(value);
931 })
932 }
933
934 fn add_plan_with_phase(
935 context: &mut Context,
936 time: f64,
937 value: u32,
938 phase: ExecutionPhase,
939 ) -> PlanId {
940 context.add_plan_with_phase(
941 time,
942 move |context| {
943 context.get_data_mut(ComponentA).push(value);
944 },
945 phase,
946 )
947 }
948
949 fn add_passive_plan(context: &mut Context, time: f64, value: u32) -> PlanId {
950 context.add_passive_plan(time, move |context| {
951 context.get_data_mut(ComponentA).push(value);
952 })
953 }
954
955 fn add_passive_plan_with_phase(
956 context: &mut Context,
957 time: f64,
958 value: u32,
959 phase: ExecutionPhase,
960 ) -> PlanId {
961 context.add_passive_plan_with_phase(
962 time,
963 move |context| {
964 context.get_data_mut(ComponentA).push(value);
965 },
966 phase,
967 )
968 }
969
970 fn add_wrapped_plan_through_context_base(context: &mut impl ContextBase, value: u32) {
971 context.add_plan(WrappedF64(0.0), move |context| {
972 context.get_data_mut(ComponentA).push(value);
973 });
974 }
975
976 #[test]
977 fn time_boundaries_accept_into_f64() {
978 let mut context = Context::new();
979 context.set_start_time(WrappedF64(0.0));
980
981 add_wrapped_plan_through_context_base(&mut context, 1);
982 context.add_plan_with_phase(
983 WrappedF64(0.0),
984 |context| context.get_data_mut(ComponentA).push(2),
985 ExecutionPhase::First,
986 );
987 context.add_passive_plan(WrappedF64(0.0), |context| {
988 context.get_data_mut(ComponentA).push(3);
989 });
990 context.add_passive_plan_with_phase(
991 WrappedF64(0.0),
992 |context| context.get_data_mut(ComponentA).push(4),
993 ExecutionPhase::Last,
994 );
995 context.add_periodic_plan_with_phase(
996 WrappedF64(1.0),
997 |context| context.get_data_mut(ComponentA).push(5),
998 ExecutionPhase::Normal,
999 );
1000
1001 context.execute();
1002
1003 let mut observed = context.get_data(ComponentA).clone();
1004 observed.sort_unstable();
1005 assert_eq!(observed, vec![1, 2, 3, 4, 5]);
1006 assert_eq!(context.get_start_time(), Some(0.0));
1007 }
1008
1009 #[test]
1010 #[should_panic(expected = "Time inf is invalid")]
1011 fn infinite_plan_time() {
1012 let mut context = Context::new();
1013 add_plan(&mut context, f64::INFINITY, 0);
1014 }
1015
1016 #[test]
1017 #[should_panic(expected = "Time NaN is invalid")]
1018 fn nan_plan_time() {
1019 let mut context = Context::new();
1020 add_plan(&mut context, f64::NAN, 0);
1021 }
1022
1023 #[test]
1024 #[should_panic(expected = "Time NaN is invalid")]
1025 fn wrapped_nan_plan_time_panics() {
1026 let mut context = Context::new();
1027 context.add_plan(WrappedF64(f64::NAN), |_| {});
1028 }
1029
1030 #[test]
1031 fn timed_plan_only() {
1032 let mut context = Context::new();
1033 add_plan(&mut context, 1.0, 1);
1034 context.execute();
1035 assert_eq!(context.get_current_time(), 1.0);
1036 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1037 }
1038
1039 #[test]
1040 fn callback_only() {
1041 let mut context = Context::new();
1042 context.queue_callback(|context| {
1043 context.get_data_mut(ComponentA).push(1);
1044 });
1045 context.execute();
1046 assert_eq!(context.get_current_time(), 0.0);
1047 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1048 }
1049
1050 #[test]
1051 fn callback_before_timed_plan() {
1052 let mut context = Context::new();
1053 context.queue_callback(|context| {
1054 context.get_data_mut(ComponentA).push(1);
1055 });
1056 add_plan(&mut context, 1.0, 2);
1057 context.execute();
1058 assert_eq!(context.get_current_time(), 1.0);
1059 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1060 }
1061
1062 #[test]
1063 fn callback_adds_timed_plan() {
1064 let mut context = Context::new();
1065 context.queue_callback(|context| {
1066 context.get_data_mut(ComponentA).push(1);
1067 add_plan(context, 1.0, 2);
1068 context.get_data_mut(ComponentA).push(3);
1069 });
1070 context.execute();
1071 assert_eq!(context.get_current_time(), 1.0);
1072 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 2]);
1073 }
1074
1075 #[test]
1076 fn callback_adds_callback_and_timed_plan() {
1077 let mut context = Context::new();
1078 context.queue_callback(|context| {
1079 context.get_data_mut(ComponentA).push(1);
1080 add_plan(context, 1.0, 2);
1081 context.queue_callback(|context| {
1082 context.get_data_mut(ComponentA).push(4);
1083 });
1084 context.get_data_mut(ComponentA).push(3);
1085 });
1086 context.execute();
1087 assert_eq!(context.get_current_time(), 1.0);
1088 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 4, 2]);
1089 }
1090
1091 #[test]
1092 fn timed_plan_adds_callback_and_timed_plan() {
1093 let mut context = Context::new();
1094 context.add_plan(1.0, |context| {
1095 context.get_data_mut(ComponentA).push(1);
1096 add_plan(context, 2.0, 3);
1098 context.queue_callback(|context| {
1099 context.get_data_mut(ComponentA).push(2);
1100 });
1101 });
1102 context.execute();
1103 assert_eq!(context.get_current_time(), 2.0);
1104 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1105 }
1106
1107 #[test]
1108 fn cancel_plan() {
1109 let mut context = Context::new();
1110 let to_cancel = add_plan(&mut context, 2.0, 1);
1111 context.add_plan(1.0, move |context| {
1112 context.cancel_plan(&to_cancel);
1113 });
1114 context.execute();
1115 assert_eq!(context.get_current_time(), 1.0);
1116 let test_vec: Vec<u32> = vec![];
1117 assert_eq!(*context.get_data_mut(ComponentA), test_vec);
1118 }
1119
1120 #[test]
1121 fn add_plan_with_current_time() {
1122 let mut context = Context::new();
1123 context.add_plan(1.0, move |context| {
1124 context.get_data_mut(ComponentA).push(1);
1125 add_plan(context, 1.0, 2);
1126 context.queue_callback(|context| {
1127 context.get_data_mut(ComponentA).push(3);
1128 });
1129 });
1130 context.execute();
1131 assert_eq!(context.get_current_time(), 1.0);
1132 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3, 2]);
1133 }
1134
1135 #[test]
1136 fn plans_at_same_time_fire_in_order() {
1137 let mut context = Context::new();
1138 add_plan(&mut context, 1.0, 1);
1139 add_plan(&mut context, 1.0, 2);
1140 context.execute();
1141 assert_eq!(context.get_current_time(), 1.0);
1142 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1143 }
1144
1145 #[test]
1146 fn check_plan_phase_ordering() {
1147 assert!(ExecutionPhase::First < ExecutionPhase::Normal);
1148 assert!(ExecutionPhase::Normal < ExecutionPhase::Last);
1149 }
1150
1151 #[test]
1152 fn plans_at_same_time_follow_phase() {
1153 let mut context = Context::new();
1154 add_plan(&mut context, 1.0, 1);
1155 add_plan_with_phase(&mut context, 1.0, 5, ExecutionPhase::Last);
1156 add_plan_with_phase(&mut context, 1.0, 3, ExecutionPhase::First);
1157 add_plan(&mut context, 1.0, 2);
1158 add_plan_with_phase(&mut context, 1.0, 6, ExecutionPhase::Last);
1159 add_plan_with_phase(&mut context, 1.0, 4, ExecutionPhase::First);
1160 context.execute();
1161 assert_eq!(context.get_current_time(), 1.0);
1162 assert_eq!(*context.get_data_mut(ComponentA), vec![3, 4, 1, 2, 5, 6]);
1163 }
1164
1165 #[derive(IxaEvent)]
1166 struct Event1 {
1167 pub data: usize,
1168 }
1169
1170 #[derive(IxaEvent)]
1171 struct Event2 {
1172 pub data: usize,
1173 }
1174
1175 #[derive(Clone, Copy)]
1176 struct EventWithOnUnsubscribe;
1177
1178 impl IxaEvent for EventWithOnUnsubscribe {
1179 fn on_unsubscribe(context: &mut Context) {
1180 let has_event_handlers = context.has_event_handlers::<Self>();
1181 context
1182 .get_data_mut(UnsubscribeHookObservations)
1183 .push(has_event_handlers);
1184 }
1185 }
1186
1187 struct NotCopy;
1188
1189 #[derive(IxaEvent)]
1190 struct GenericEvent<T> {
1191 pub data: usize,
1192 _marker: PhantomData<T>,
1193 }
1194
1195 #[test]
1196 fn simple_event() {
1197 let mut context = Context::new();
1198 let obs_data = Rc::new(RefCell::new(0));
1199 let obs_data_clone = Rc::clone(&obs_data);
1200
1201 context.subscribe_to_event::<Event1>(move |_, event| {
1202 *obs_data_clone.borrow_mut() = event.data;
1203 });
1204
1205 context.emit_event(Event1 { data: 1 });
1206 context.execute();
1207 assert_eq!(*obs_data.borrow(), 1);
1208 }
1209
1210 #[test]
1211 fn derive_ixa_event_implements_copy_for_generic_events() {
1212 fn assert_clone<T: Clone>() {}
1213 fn assert_copy<T: Copy>() {}
1214 assert_clone::<GenericEvent<NotCopy>>();
1215 assert_copy::<GenericEvent<NotCopy>>();
1216
1217 let mut context = Context::new();
1218 let obs_data = Rc::new(RefCell::new(0));
1219 let obs_data_clone = Rc::clone(&obs_data);
1220
1221 context.subscribe_to_event::<GenericEvent<NotCopy>>(move |_, event| {
1222 *obs_data_clone.borrow_mut() = event.data;
1223 });
1224
1225 let event = GenericEvent::<NotCopy> {
1226 data: 5,
1227 _marker: PhantomData,
1228 };
1229 let copied_event = event;
1230
1231 assert_eq!(copied_event.data, 5);
1232 context.emit_event(copied_event);
1233 context.execute();
1234 assert_eq!(*obs_data.borrow(), 5);
1235 }
1236
1237 #[test]
1238 fn multiple_events() {
1239 let mut context = Context::new();
1240 let obs_data = Rc::new(RefCell::new(0));
1241 let obs_data_clone = Rc::clone(&obs_data);
1242
1243 context.subscribe_to_event::<Event1>(move |_, event| {
1244 *obs_data_clone.borrow_mut() += event.data;
1245 });
1246
1247 context.emit_event(Event1 { data: 1 });
1248 context.emit_event(Event1 { data: 2 });
1249 context.execute();
1250
1251 assert_eq!(*obs_data.borrow(), 3);
1253 }
1254
1255 #[test]
1256 fn multiple_event_handlers() {
1257 let mut context = Context::new();
1258 let obs_data1 = Rc::new(RefCell::new(0));
1259 let obs_data1_clone = Rc::clone(&obs_data1);
1260 let obs_data2 = Rc::new(RefCell::new(0));
1261 let obs_data2_clone = Rc::clone(&obs_data2);
1262
1263 context.subscribe_to_event::<Event1>(move |_, event| {
1264 *obs_data1_clone.borrow_mut() = event.data;
1265 });
1266 context.subscribe_to_event::<Event1>(move |_, event| {
1267 *obs_data2_clone.borrow_mut() = event.data;
1268 });
1269 context.emit_event(Event1 { data: 1 });
1270 context.execute();
1271 assert_eq!(*obs_data1.borrow(), 1);
1272 assert_eq!(*obs_data2.borrow(), 1);
1273 }
1274
1275 #[test]
1276 fn multiple_event_types() {
1277 let mut context = Context::new();
1278 let obs_data1 = Rc::new(RefCell::new(0));
1279 let obs_data1_clone = Rc::clone(&obs_data1);
1280 let obs_data2 = Rc::new(RefCell::new(0));
1281 let obs_data2_clone = Rc::clone(&obs_data2);
1282
1283 context.subscribe_to_event::<Event1>(move |_, event| {
1284 *obs_data1_clone.borrow_mut() = event.data;
1285 });
1286 context.subscribe_to_event::<Event2>(move |_, event| {
1287 *obs_data2_clone.borrow_mut() = event.data;
1288 });
1289 context.emit_event(Event1 { data: 1 });
1290 context.emit_event(Event2 { data: 2 });
1291 context.execute();
1292 assert_eq!(*obs_data1.borrow(), 1);
1293 assert_eq!(*obs_data2.borrow(), 2);
1294 }
1295
1296 #[test]
1297 fn unsubscribe_from_event_before_emit() {
1298 let mut context = Context::new();
1299 let obs_data = Rc::new(RefCell::new(0));
1300 let obs_data_clone = Rc::clone(&obs_data);
1301
1302 let listener_id = context.subscribe_to_event::<Event1>(move |_, event| {
1303 *obs_data_clone.borrow_mut() = event.data;
1304 });
1305
1306 assert!(context.has_event_handlers::<Event1>());
1307 assert!(context.unsubscribe_from_event(&listener_id));
1308 assert!(!context.has_event_handlers::<Event1>());
1309
1310 context.emit_event(Event1 { data: 1 });
1311 context.execute();
1312 assert_eq!(*obs_data.borrow(), 0);
1313 }
1314
1315 #[test]
1316 fn unsubscribe_from_event_calls_hook_after_removal() {
1317 let mut context = Context::new();
1318 let listener_id = context.subscribe_to_event::<EventWithOnUnsubscribe>(|_, _| {});
1319
1320 assert!(context.unsubscribe_from_event(&listener_id));
1321
1322 assert_eq!(context.get_data(UnsubscribeHookObservations), &vec![false]);
1323 }
1324
1325 #[test]
1326 fn unsubscribe_from_event_calls_hook_only_for_successful_removals() {
1327 let mut context = Context::new();
1328 let first_listener = context.subscribe_to_event::<EventWithOnUnsubscribe>(|_, _| {});
1329 let second_listener = context.subscribe_to_event::<EventWithOnUnsubscribe>(|_, _| {});
1330 let unknown_listener = EventListenerId::<EventWithOnUnsubscribe>::new(usize::MAX);
1331
1332 assert!(context.unsubscribe_from_event(&first_listener));
1333 assert!(!context.unsubscribe_from_event(&first_listener));
1334 assert!(!context.unsubscribe_from_event(&unknown_listener));
1335 assert!(context.unsubscribe_from_event(&second_listener));
1336
1337 assert_eq!(
1338 context.get_data(UnsubscribeHookObservations),
1339 &vec![true, false]
1340 );
1341 }
1342
1343 #[test]
1344 fn unsubscribe_from_event_preserves_other_listeners_in_order() {
1345 let mut context = Context::new();
1346 let observed = Rc::new(RefCell::new(Vec::new()));
1347
1348 let observed_clone = Rc::clone(&observed);
1349 context.subscribe_to_event::<Event1>(move |_, event| {
1350 observed_clone.borrow_mut().push(event.data);
1351 });
1352 let observed_clone = Rc::clone(&observed);
1353 let listener_to_unsubscribe = context.subscribe_to_event::<Event1>(move |_, event| {
1354 observed_clone.borrow_mut().push(event.data + 10);
1355 });
1356 let observed_clone = Rc::clone(&observed);
1357 context.subscribe_to_event::<Event1>(move |_, event| {
1358 observed_clone.borrow_mut().push(event.data + 20);
1359 });
1360
1361 assert!(context.unsubscribe_from_event(&listener_to_unsubscribe));
1362
1363 context.emit_event(Event1 { data: 1 });
1364 context.execute();
1365 assert_eq!(*observed.borrow(), vec![1, 21]);
1366 }
1367
1368 #[test]
1369 fn unsubscribe_from_event_does_not_affect_other_event_types() {
1370 let mut context = Context::new();
1371 let obs_data1 = Rc::new(RefCell::new(0));
1372 let obs_data1_clone = Rc::clone(&obs_data1);
1373 let obs_data2 = Rc::new(RefCell::new(0));
1374 let obs_data2_clone = Rc::clone(&obs_data2);
1375
1376 let listener_id = context.subscribe_to_event::<Event1>(move |_, event| {
1377 *obs_data1_clone.borrow_mut() = event.data;
1378 });
1379 context.subscribe_to_event::<Event2>(move |_, event| {
1380 *obs_data2_clone.borrow_mut() = event.data;
1381 });
1382
1383 assert!(context.unsubscribe_from_event(&listener_id));
1384
1385 context.emit_event(Event1 { data: 1 });
1386 context.emit_event(Event2 { data: 2 });
1387 context.execute();
1388 assert_eq!(*obs_data1.borrow(), 0);
1389 assert_eq!(*obs_data2.borrow(), 2);
1390 }
1391
1392 #[test]
1393 fn unsubscribe_from_event_does_not_cancel_already_queued_callback() {
1394 let mut context = Context::new();
1395 let obs_data = Rc::new(RefCell::new(0));
1396 let obs_data_clone = Rc::clone(&obs_data);
1397
1398 let listener_id = context.subscribe_to_event::<Event1>(move |_, event| {
1399 *obs_data_clone.borrow_mut() = event.data;
1400 });
1401
1402 context.emit_event(Event1 { data: 1 });
1403 assert!(context.unsubscribe_from_event(&listener_id));
1404 context.execute();
1405 assert_eq!(*obs_data.borrow(), 1);
1406 }
1407
1408 #[test]
1409 fn unsubscribe_from_event_returns_false_when_already_unsubscribed() {
1410 let mut context = Context::new();
1411 let listener_id = context.subscribe_to_event::<Event1>(move |_, _| {});
1412
1413 assert!(context.unsubscribe_from_event(&listener_id));
1414 assert!(!context.unsubscribe_from_event(&listener_id));
1415 }
1416
1417 #[test]
1418 fn unsubscribe_from_event_returns_false_for_unknown_listener_id() {
1419 let mut context1 = Context::new();
1420 let mut context2 = Context::new();
1421 let obs_data = Rc::new(RefCell::new(0));
1422 let obs_data_clone = Rc::clone(&obs_data);
1423
1424 context1.subscribe_to_event::<Event1>(move |_, _| {});
1425 let unknown_listener_id = context1.subscribe_to_event::<Event1>(move |_, _| {});
1426 context2.subscribe_to_event::<Event1>(move |_, event| {
1427 *obs_data_clone.borrow_mut() = event.data;
1428 });
1429
1430 assert!(!context2.unsubscribe_from_event(&unknown_listener_id));
1431
1432 context2.emit_event(Event1 { data: 1 });
1433 context2.execute();
1434 assert_eq!(*obs_data.borrow(), 1);
1435 }
1436
1437 #[test]
1438 fn subscribe_after_event() {
1439 let mut context = Context::new();
1440 let obs_data = Rc::new(RefCell::new(0));
1441 let obs_data_clone = Rc::clone(&obs_data);
1442
1443 context.emit_event(Event1 { data: 1 });
1444 context.subscribe_to_event::<Event1>(move |_, event| {
1445 *obs_data_clone.borrow_mut() = event.data;
1446 });
1447
1448 context.execute();
1449 assert_eq!(*obs_data.borrow(), 0);
1450 }
1451
1452 #[test]
1453 fn shutdown_runs_current_time_plans_and_preserves_future_plan() {
1454 let mut context = Context::new();
1455 add_plan(&mut context, 1.0, 1);
1456 context.add_plan(1.5, |context| {
1457 context.get_data_mut(ComponentA).push(2);
1458 context.shutdown();
1459 });
1460 add_plan(&mut context, 1.5, 3);
1461 add_plan(&mut context, 2.0, 2);
1462 context.execute();
1463 assert_eq!(context.get_current_time(), 1.5);
1464 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1465
1466 context.execute();
1467 assert_eq!(context.get_current_time(), 2.0);
1468 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3, 2]);
1469 }
1470
1471 #[test]
1472 fn shutdown_runs_queued_callbacks() {
1473 let mut context = Context::new();
1474 add_plan(&mut context, 1.0, 1);
1475 context.add_plan(1.5, |context| {
1476 context.queue_callback(|context| {
1477 context.get_data_mut(ComponentA).push(3);
1478 });
1479 context.shutdown();
1480 });
1481 context.execute();
1482 assert_eq!(context.get_current_time(), 1.5);
1483 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 3]);
1484 }
1485
1486 #[test]
1487 fn shutdown_runs_queued_events() {
1488 let mut context = Context::new();
1489 let obs_data = Rc::new(RefCell::new(0));
1490 let obs_data_clone = Rc::clone(&obs_data);
1491 context.subscribe_to_event::<Event1>(move |_, event| {
1492 *obs_data_clone.borrow_mut() = event.data;
1493 });
1494 context.emit_event(Event1 { data: 1 });
1495 context.shutdown();
1496 context.execute();
1497 assert_eq!(*obs_data.borrow(), 1);
1498 }
1499
1500 #[test]
1501 fn shutdown_runs_regular_plans_at_current_time_all_phases() {
1502 let mut context = Context::new();
1503 context.add_plan_with_phase(
1504 1.0,
1505 |context| {
1506 context.get_data_mut(ComponentA).push(1);
1507 },
1508 ExecutionPhase::First,
1509 );
1510 context.add_plan(1.0, |context| {
1511 context.get_data_mut(ComponentA).push(2);
1512 context.shutdown();
1513 });
1514 add_plan(&mut context, 1.0, 3);
1515 add_plan_with_phase(&mut context, 1.0, 4, ExecutionPhase::Last);
1516 add_plan(&mut context, 2.0, 5);
1517
1518 context.execute();
1519
1520 assert_eq!(context.get_current_time(), 1.0);
1521 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3, 4]);
1522 }
1523
1524 #[test]
1525 fn shutdown_time_plans_run_after_current_time_plans() {
1526 let mut context = Context::new();
1527 context.add_shutdown_plan(|context| {
1528 context.get_data_mut(ComponentA).push(3);
1529 });
1530 context.add_plan(1.0, |context| {
1531 context.get_data_mut(ComponentA).push(1);
1532 context.shutdown();
1533 });
1534 add_plan(&mut context, 1.0, 2);
1535
1536 context.execute();
1537
1538 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1539 }
1540
1541 #[test]
1542 fn shutdown_plan_phase_order_is_respected() {
1543 let mut context = Context::new();
1544 context.add_shutdown_plan_with_phase(
1545 |context| {
1546 context.get_data_mut(ComponentA).push(3);
1547 },
1548 ExecutionPhase::Last,
1549 );
1550 context.add_shutdown_plan_with_phase(
1551 |context| {
1552 context.get_data_mut(ComponentA).push(1);
1553 },
1554 ExecutionPhase::First,
1555 );
1556 context.add_shutdown_plan(|context| {
1557 context.get_data_mut(ComponentA).push(2);
1558 });
1559 context.execute();
1560
1561 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1562 }
1563
1564 #[test]
1565 fn shutdown_plan_callbacks_are_drained() {
1566 let mut context = Context::new();
1567 context.add_shutdown_plan(|context| {
1568 context.get_data_mut(ComponentA).push(1);
1569 context.queue_callback(|context| {
1570 context.get_data_mut(ComponentA).push(2);
1571 });
1572 });
1573 context.add_shutdown_plan(|context| {
1574 context.get_data_mut(ComponentA).push(3);
1575 });
1576
1577 context.execute();
1578
1579 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1580 }
1581
1582 #[test]
1583 fn shutdown_time_does_not_return_to_regular_queue() {
1584 let mut context = Context::new();
1585 context.add_shutdown_plan(|context| {
1586 context.get_data_mut(ComponentA).push(1);
1587 context.add_plan(context.get_current_time(), |context| {
1588 context.get_data_mut(ComponentA).push(3);
1589 });
1590 });
1591 context.add_shutdown_plan(|context| {
1592 context.get_data_mut(ComponentA).push(2);
1593 });
1594
1595 context.execute();
1596 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1597
1598 context.execute();
1599 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1600 }
1601
1602 #[test]
1603 fn passive_only_initial_time_runs_during_normal_shutdown() {
1604 let mut context = Context::new();
1605 add_passive_plan(&mut context, 0.0, 1);
1606 add_passive_plan(&mut context, 1.0, 2);
1607
1608 context.execute();
1609
1610 assert_eq!(context.get_current_time(), 0.0);
1611 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1612 }
1613
1614 #[test]
1615 fn passive_plans_at_final_non_passive_time_run_across_phases() {
1616 let mut context = Context::new();
1617 add_passive_plan_with_phase(&mut context, 1.0, 1, ExecutionPhase::First);
1618 add_plan(&mut context, 1.0, 2);
1619 add_passive_plan_with_phase(&mut context, 1.0, 3, ExecutionPhase::Last);
1620
1621 context.execute();
1622
1623 assert_eq!(context.get_current_time(), 1.0);
1624 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1625 }
1626
1627 #[test]
1628 fn passive_future_plan_survives_until_later_non_passive_work() {
1629 let mut context = Context::new();
1630 add_plan(&mut context, 1.0, 1);
1631 add_passive_plan(&mut context, 2.0, 2);
1632
1633 context.execute();
1634
1635 assert_eq!(context.get_current_time(), 1.0);
1636 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1637
1638 add_plan(&mut context, 2.0, 3);
1639 context.execute();
1640
1641 assert_eq!(context.get_current_time(), 2.0);
1642 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1643 }
1644
1645 #[test]
1646 fn scheduled_shutdown_stops_at_requested_time_and_drains_current_time_work() {
1647 let mut context = Context::new();
1648 add_plan(&mut context, 1.0, 1);
1649 context.schedule_shutdown(2.0);
1650 add_plan(&mut context, 2.0, 2);
1651 add_plan(&mut context, 3.0, 3);
1652
1653 context.execute();
1654
1655 assert_eq!(context.get_current_time(), 2.0);
1656 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1657
1658 context.execute();
1659
1660 assert_eq!(context.get_current_time(), 3.0);
1661 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1662 }
1663
1664 #[test]
1665 fn scheduled_shutdown_does_not_keep_the_timeline_alive() {
1666 let mut context = Context::new();
1667 context.schedule_shutdown(5.0);
1668
1669 context.execute();
1670
1671 assert_eq!(context.get_current_time(), 0.0);
1672
1673 add_plan(&mut context, 10.0, 1);
1674 context.execute();
1675
1676 assert_eq!(context.get_current_time(), 5.0);
1677 assert_eq!(*context.get_data_mut(ComponentA), Vec::<u32>::new());
1678 }
1679
1680 #[test]
1681 fn scheduled_shutdown_can_be_cancelled() {
1682 let mut context = Context::new();
1683 let shutdown_plan = context.schedule_shutdown(2.0);
1684 add_plan(&mut context, 3.0, 1);
1685 context.cancel_plan(&shutdown_plan);
1686
1687 context.execute();
1688
1689 assert_eq!(context.get_current_time(), 3.0);
1690 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1691 }
1692
1693 #[test]
1694 fn cancel_plan_can_cancel_shutdown_plan() {
1695 let mut context = Context::new();
1696 let to_cancel = context.add_shutdown_plan(|context| {
1697 context.get_data_mut(ComponentA).push(1);
1698 });
1699 context.cancel_plan(&to_cancel);
1700
1701 context.execute();
1702
1703 assert_eq!(*context.get_data_mut(ComponentA), Vec::<u32>::new());
1704 }
1705
1706 #[test]
1707 fn abort_inside_plan_stops_execute_loop() {
1708 let mut context = Context::new();
1709 context.add_plan(1.0, |context| {
1710 context.get_data_mut(ComponentA).push(1);
1711 context.queue_callback(|context| {
1712 context.get_data_mut(ComponentA).push(2);
1713 });
1714 context.abort();
1715 });
1716 add_plan(&mut context, 2.0, 3);
1717
1718 context.execute();
1719 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1720
1721 context.execute();
1722 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1723 }
1724
1725 #[test]
1726 fn abort_before_execute_does_not_poison_later_execute() {
1727 let mut context = Context::new();
1728 context.abort();
1729 add_plan(&mut context, 1.0, 1);
1730
1731 context.execute();
1732
1733 assert_eq!(context.get_current_time(), 1.0);
1734 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1735 }
1736
1737 #[test]
1738 fn abort_during_normal_shutdown_exits_immediately() {
1739 let mut context = Context::new();
1740 context.add_plan(1.0, |context| {
1741 context.get_data_mut(ComponentA).push(1);
1742 context.shutdown();
1743 });
1744 context.add_plan(1.0, |context| {
1745 context.get_data_mut(ComponentA).push(2);
1746 context.abort();
1747 });
1748 add_plan(&mut context, 1.0, 3);
1749
1750 context.execute();
1751 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1752
1753 context.execute();
1754 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1755 }
1756
1757 #[test]
1758 fn shutdown_does_not_restart_stopped_status() {
1759 let mut context = Context::new();
1760 context.abort();
1761 context.shutdown();
1762 add_plan(&mut context, 1.0, 1);
1763
1764 context.execute();
1765
1766 assert_eq!(context.get_current_time(), 1.0);
1767 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1768 }
1769
1770 #[test]
1771 fn execute_single_step_runs_one_status_transition() {
1772 let mut context = Context::new();
1773 context.add_shutdown_plan(|context| {
1774 context.get_data_mut(ComponentA).push(1);
1775 });
1776
1777 context.execute_single_step();
1778 assert_eq!(*context.get_data_mut(ComponentA), Vec::<u32>::new());
1779
1780 context.execute_single_step();
1781 assert_eq!(*context.get_data_mut(ComponentA), Vec::<u32>::new());
1782
1783 context.execute_single_step();
1784 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1785 }
1786
1787 #[test]
1788 fn execute_single_step_stopped_runs_callback_then_resets() {
1789 let mut context = Context::new();
1790 context.abort();
1791 context.queue_callback(|context| {
1792 context.get_data_mut(ComponentA).push(1);
1793 });
1794 add_plan(&mut context, 0.0, 2);
1795
1796 context.execute_single_step();
1797 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1798
1799 context.execute_single_step();
1800 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1801
1802 context.execute_single_step();
1803 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1804 }
1805
1806 #[test]
1807 fn periodic_plan_self_schedules() {
1808 let mut context = Context::new();
1811 context.add_periodic_plan_with_phase(
1812 1.0,
1813 |context| {
1814 let time = context.get_current_time();
1815 context.get_data_mut(ComponentA).push(time as u32);
1816 },
1817 ExecutionPhase::Last,
1818 );
1819 context.add_plan(1.0, move |_context| {});
1820 context.add_plan(1.5, move |_context| {});
1821 context.execute();
1822 assert_eq!(context.get_current_time(), 1.5);
1823
1824 assert_eq!(*context.get_data(ComponentA), vec![0, 1]); }
1826
1827 #[test]
1830 fn negative_plan_time_allowed() {
1831 let mut context = Context::new();
1832 context.set_start_time(-1.0);
1833 add_plan(&mut context, -1.0, 1);
1834 context.execute();
1835 assert_eq!(context.get_current_time(), -1.0);
1836 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1837 }
1838
1839 #[test]
1840 fn add_plan_get_current_time() {
1841 let mut context = Context::new();
1842 let current_time = context.get_current_time();
1843 add_plan(&mut context, current_time, 1);
1844 context.execute();
1845 assert_eq!(context.get_current_time(), 0.0);
1846 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1847 }
1848
1849 #[test]
1850 fn multiple_negative_plans() {
1851 let mut context = Context::new();
1852 context.set_start_time(-3.0);
1853 add_plan(&mut context, -3.0, 1);
1854 add_plan(&mut context, -1.0, 3);
1855 add_plan(&mut context, -2.0, 2);
1856 context.execute();
1857 assert_eq!(context.get_current_time(), -1.0);
1858 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1859 }
1860
1861 #[test]
1862 fn negative_and_positive_plans() {
1863 let mut context = Context::new();
1864 context.set_start_time(-1.0);
1865 add_plan(&mut context, -1.0, 1);
1866 add_plan(&mut context, 1.0, 3);
1867 add_plan(&mut context, 0.0, 2);
1868 context.execute();
1869 assert_eq!(context.get_current_time(), 1.0);
1870 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
1871 }
1872
1873 #[test]
1874 fn get_current_time_before_execute_defaults() {
1875 let mut context = Context::new();
1876 assert_eq!(context.get_current_time(), 0.0);
1877
1878 context.set_start_time(-2.0);
1879 assert_eq!(context.get_current_time(), -2.0);
1880 }
1881
1882 #[test]
1883 fn get_current_time_initializes_to_zero_when_all_positive() {
1884 let mut context = Context::new();
1885 let seen_time = Rc::new(RefCell::new(f64::NAN));
1886 let seen_time_clone = Rc::clone(&seen_time);
1887 context.queue_callback(move |ctx| {
1888 *seen_time_clone.borrow_mut() = ctx.get_current_time();
1889 });
1890 context.execute();
1891 assert_eq!(*seen_time.borrow(), 0.0);
1892 }
1893
1894 #[test]
1895 fn get_current_time_initializes_to_zero_when_empty() {
1896 let mut context = Context::new();
1897 context.execute();
1898 assert_eq!(context.get_current_time(), 0.0);
1899 }
1900
1901 #[test]
1902 fn get_current_time_initializes_to_zero_with_plan() {
1903 let mut context = Context::new();
1904 add_plan(&mut context, 0.0, 1);
1905 context.execute();
1906 assert_eq!(context.get_current_time(), 0.0);
1907 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1908 }
1909
1910 #[test]
1911 #[should_panic(expected = "Time -1 is invalid")]
1912 fn negative_time_from_callback_panics() {
1913 let mut context = Context::new();
1914 context.queue_callback(|context| {
1915 context.get_data_mut(ComponentA).push(1);
1916 add_plan(context, -1.0, 2);
1917 });
1918 add_plan(&mut context, 1.0, 3);
1919 context.execute();
1920 }
1921
1922 #[test]
1923 fn large_negative_time() {
1924 let mut context = Context::new();
1925 context.set_start_time(-1_000_000.0);
1926 add_plan(&mut context, -1_000_000.0, 1);
1927 context.execute();
1928 assert_eq!(context.get_current_time(), -1_000_000.0);
1929 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1930 }
1931
1932 #[test]
1933 fn very_small_negative_time() {
1934 let mut context = Context::new();
1935 context.set_start_time(-1e-10);
1936 add_plan(&mut context, -1e-10, 1);
1937 context.execute();
1938 assert_eq!(context.get_current_time(), -1e-10);
1939 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
1940 }
1941
1942 #[test]
1943 fn negative_time_ordering_with_phases() {
1944 let mut context = Context::new();
1945 context.set_start_time(-1.0);
1946 add_plan_with_phase(&mut context, -1.0, 1, ExecutionPhase::Normal);
1947 add_plan_with_phase(&mut context, -1.0, 3, ExecutionPhase::Last);
1948 add_plan_with_phase(&mut context, -1.0, 2, ExecutionPhase::First);
1949 context.execute();
1950 assert_eq!(context.get_current_time(), -1.0);
1951 assert_eq!(*context.get_data_mut(ComponentA), vec![2, 1, 3]);
1952 }
1953
1954 #[test]
1955 #[should_panic(expected = "Time 4 is invalid")]
1956 fn cannot_schedule_plan_before_current_time() {
1957 let mut context = Context::new();
1958 add_plan(&mut context, 5.0, 1);
1959 context.add_plan(5.0, |context| {
1960 add_plan(context, 4.0, 2);
1962 });
1963 context.execute();
1964 }
1965
1966 #[test]
1967 fn get_current_time_multiple_calls_before_execute() {
1968 let mut context = Context::new();
1969 context.set_start_time(-2.0);
1970 add_plan(&mut context, -2.0, 1);
1971 context.execute();
1972 assert_eq!(context.get_current_time(), -2.0);
1973 }
1974
1975 #[test]
1976 fn negative_plan_can_add_positive_plan() {
1977 let mut context = Context::new();
1978 context.set_start_time(-1.0);
1979 add_plan(&mut context, -1.0, 1);
1980 context.add_plan(-1.0, |context| {
1981 add_plan(context, 2.0, 2);
1982 });
1983 context.execute();
1984 assert_eq!(context.get_current_time(), 2.0);
1985 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1986 }
1987
1988 #[test]
1989 fn negative_plan_can_schedule_negative_plan() {
1990 let mut context = Context::new();
1991 context.set_start_time(-2.0);
1992 add_plan(&mut context, -2.0, 1);
1993 context.add_plan(-2.0, |context| {
1994 add_plan(context, -1.0, 2);
1995 });
1996 context.execute();
1997 assert_eq!(context.get_current_time(), -1.0);
1998 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2]);
1999 }
2000
2001 #[test]
2002 #[should_panic(expected = "Start time has already been set. It can only be set once.")]
2003 fn set_start_time_only_once() {
2004 let mut context = Context::new();
2005 context.set_start_time(1.0);
2006 context.set_start_time(2.0);
2007 }
2008
2009 #[test]
2012 #[should_panic(expected = "Start time NaN must be finite")]
2013 fn set_start_time_nan_panics() {
2014 let mut context = Context::new();
2015 context.set_start_time(f64::NAN);
2016 }
2017
2018 #[test]
2019 #[should_panic(expected = "Start time inf must be finite")]
2020 fn set_start_time_inf_panics() {
2021 let mut context = Context::new();
2022 context.set_start_time(f64::INFINITY);
2023 }
2024
2025 #[test]
2026 fn set_start_time_equal_to_earliest_plan_allowed() {
2027 let mut context = Context::new();
2028 context.set_start_time(-2.0);
2029 add_plan(&mut context, -2.0, 1);
2030 context.execute();
2031 assert_eq!(context.get_current_time(), -2.0);
2032 assert_eq!(*context.get_data_mut(ComponentA), vec![1]);
2033 }
2034
2035 #[test]
2039 fn set_start_time_with_only_callbacks_keeps_time() {
2040 let mut context = Context::new();
2041 context.set_start_time(5.0);
2042 context.queue_callback(|ctx| {
2043 ctx.get_data_mut(ComponentA).push(42);
2044 });
2045 context.execute();
2046 assert_eq!(context.get_current_time(), 5.0);
2047 assert_eq!(*context.get_data_mut(ComponentA), vec![42]);
2048 }
2049
2050 #[test]
2051 fn multiple_plans_final_time_is_last() {
2052 let mut context = Context::new();
2053 add_plan(&mut context, 1.0, 1);
2054 add_plan(&mut context, 3.0, 3);
2055 add_plan(&mut context, 2.0, 2);
2056 context.execute();
2057 assert_eq!(context.get_current_time(), 3.0);
2058 assert_eq!(*context.get_data_mut(ComponentA), vec![1, 2, 3]);
2059 }
2060
2061 #[test]
2062 fn add_plan_same_time_fifo_and_phases() {
2063 let mut context = Context::new();
2064 add_plan_with_phase(&mut context, 1.0, 3, ExecutionPhase::Last);
2065 add_plan(&mut context, 1.0, 1);
2066 add_plan_with_phase(&mut context, 1.0, 2, ExecutionPhase::First);
2067 add_plan(&mut context, 1.0, 4);
2068 context.execute();
2069 assert_eq!(context.get_current_time(), 1.0);
2070 assert_eq!(*context.get_data_mut(ComponentA), vec![2, 1, 4, 3]);
2071 }
2072
2073 #[test]
2074 #[should_panic(expected = "Time -2 is invalid")]
2075 fn add_plan_less_than_current_time_panics() {
2076 let mut context = Context::new();
2077 context.set_start_time(-1.0);
2078 add_plan(&mut context, -1.0, 1);
2079 add_plan(&mut context, -2.0, 2);
2081 }
2082
2083 #[test]
2084 #[should_panic(expected = "Period must be greater than 0")]
2085 fn add_periodic_plan_zero_period_panics() {
2086 let mut context = Context::new();
2087 context.add_periodic_plan_with_phase(0.0, |_ctx| {}, ExecutionPhase::Normal);
2088 }
2089
2090 #[test]
2091 #[should_panic(expected = "Period must be greater than 0")]
2092 fn add_periodic_plan_nan_panics() {
2093 let mut context = Context::new();
2094 context.add_periodic_plan_with_phase(f64::NAN, |_ctx| {}, ExecutionPhase::Normal);
2095 }
2096
2097 #[test]
2098 #[should_panic(expected = "Period must be greater than 0")]
2099 fn add_periodic_plan_inf_panics() {
2100 let mut context = Context::new();
2101 context.add_periodic_plan_with_phase(f64::INFINITY, |_ctx| {}, ExecutionPhase::Normal);
2102 }
2103
2104 #[test]
2105 fn shutdown_status_reset() {
2106 let mut context = Context::new();
2109 let _: PersonId = context.add_entity(with!(Person, Age(50))).unwrap();
2110
2111 context.add_plan(0.0, |ctx| {
2113 ctx.shutdown();
2114 });
2115
2116 context.execute();
2118 assert_eq!(context.get_current_time(), 0.0);
2119 assert_eq!(context.get_entity_count::<Person>(), 1);
2120
2121 context.add_plan(2.0, |ctx| {
2123 let _: PersonId = ctx.add_entity(with!(Person, Age(50))).unwrap();
2124 });
2125
2126 context.execute();
2130 assert_eq!(context.get_current_time(), 2.0);
2131 assert_eq!(
2132 context.get_entity_count::<Person>(),
2133 2,
2134 "If this fails, shutdown_status was not properly reset"
2135 );
2136 }
2137}