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