1use std::hash::Hash;
2
3use smallvec::SmallVec;
4
5use crate::entity::entity_set::{EntitySet, EntitySetIterator, SourceSet};
6use crate::entity::events::{EntityCreatedEvent, PartialPropertyChangeEventBox};
7use crate::entity::index::{IndexCountResult, IndexSetResult, PropertyIndexType};
8use crate::entity::multi_property::multi_property_id_for_property_type_id;
9use crate::entity::property::{IndexableProperty, Property};
10use crate::entity::property_list::{PropertyInitializationList, PropertyList};
11use crate::entity::query::Query;
12use crate::entity::value_change_counter::StratifiedValueChangeCounter;
13use crate::entity::{Entity, EntityId, PopulationIterator};
14use crate::rand::{Rng, RngExt};
15use crate::random::sample_multiple_from_known_length;
16use crate::{warn, Context, ContextRandomExt, ExecutionPhase, IxaError, RngId};
17
18fn create_property_index<E, P>(context: &mut Context, requested: PropertyIndexType)
19where
20 E: Entity,
21 P: IndexableProperty<E>,
22{
23 debug_assert_ne!(requested, PropertyIndexType::Unindexed);
24
25 if let Some((representative_id, representative_name)) =
26 multi_property_id_for_property_type_id(E::id(), P::type_id())
27 {
28 if representative_id != P::id() {
29 panic!(
30 "Cannot index multi-property {} because it is equivalent to representative \
31 multi-property {}.",
32 P::name(),
33 representative_name,
34 );
35 }
36 }
37
38 let current = context.get_property_value_store::<E, P>().index_type();
39 let current_satisfies_request = current == requested
40 || matches!(
41 (current, requested),
42 (
43 PropertyIndexType::FullIndex,
44 PropertyIndexType::ValueCountIndex,
45 )
46 );
47
48 if current_satisfies_request {
49 return;
50 }
51
52 let mut new_index = requested
53 .new_property_index::<E, P>()
54 .expect("an indexed request must construct an index");
55
56 for entity_id in context.get_entity_iterator::<E>() {
59 let value: P = context.get_property(entity_id);
60 new_index.add_entity(&value, entity_id);
61 }
62
63 context
64 .entity_store
65 .get_property_store_mut::<E>()
66 .install_property_index::<P>(Some(new_index));
67}
68
69fn handle_periodic_value_change_count_event<E, PL, P, F>(
70 context: &mut Context,
71 period: f64,
72 counter_id: usize,
73 handler: F,
74) where
75 E: Entity,
76 PL: PropertyList<E> + Eq + Hash,
77 P: IndexableProperty<E>,
78 F: Fn(&mut Context, &mut StratifiedValueChangeCounter<E, PL, P>) + 'static,
79{
80 let mut counter = {
81 let property_value_store = context.get_property_value_store_mut::<E, P>();
82 let slot = property_value_store
83 .value_change_counters
84 .get_mut(counter_id)
85 .unwrap_or_else(|| {
86 panic!(
87 "No value change counter found for property {} with counter_id {}",
88 P::name(),
89 counter_id
90 )
91 });
92 std::mem::replace(
93 slot.get_mut(),
94 Box::new(StratifiedValueChangeCounter::<E, PL, P>::new()),
95 )
96 };
97
98 {
99 let counter = counter
100 .as_any_mut()
101 .downcast_mut::<StratifiedValueChangeCounter<E, PL, P>>()
102 .unwrap_or_else(|| {
103 panic!(
104 "Value change counter for property {} and counter_id {} had unexpected type",
105 P::name(),
106 counter_id
107 )
108 });
109
110 handler(context, counter);
111 counter.clear();
112 }
113
114 {
115 let property_value_store = context.get_property_value_store_mut::<E, P>();
116 let slot = property_value_store
117 .value_change_counters
118 .get_mut(counter_id)
119 .unwrap_or_else(|| {
120 panic!(
121 "No value change counter found for property {} with counter_id {}",
122 P::name(),
123 counter_id
124 )
125 });
126
127 let _ = std::mem::replace(slot.get_mut(), counter);
129 }
130
131 let next_time = context.get_current_time() + period;
132 context.add_passive_plan_with_phase(
133 next_time,
134 move |context| {
135 handle_periodic_value_change_count_event::<E, PL, P, F>(
136 context, period, counter_id, handler,
137 );
138 },
139 ExecutionPhase::Last,
140 );
141}
142
143pub trait ContextEntitiesExt {
146 fn add_entity<E: Entity, PL: PropertyInitializationList<E>>(
147 &mut self,
148 property_list: PL,
149 ) -> Result<EntityId<E>, IxaError>;
150
151 #[must_use]
158 fn get_property<E: Entity, P: Property<E>>(&self, entity_id: EntityId<E>) -> P;
159
160 fn set_property<E: Entity, P: Property<E>>(
162 &mut self,
163 entity_id: EntityId<E>,
164 property_value: P,
165 );
166
167 fn index_property<E: Entity, P: IndexableProperty<E>>(&mut self);
174
175 fn index_property_counts<E: Entity, P: IndexableProperty<E>>(&mut self);
180
181 fn track_periodic_value_change_counts<E, PL, P, F>(
203 &mut self,
204 period: impl Into<f64>,
205 handler: F,
206 ) where
207 E: Entity,
208 PL: PropertyList<E> + Eq + Hash,
209 P: Property<E> + Eq + Hash,
210 F: Fn(&mut Context, &mut StratifiedValueChangeCounter<E, PL, P>) + 'static;
211
212 #[cfg(test)]
220 #[must_use]
221 fn is_property_indexed<E: Entity, P: Property<E>>(&self) -> bool;
222
223 fn with_query_results<'a, E: Entity, Q: Query<E>>(
227 &'a self,
228 query: Q,
229 callback: &mut dyn FnMut(EntitySet<'a, E>),
230 );
231
232 #[must_use]
237 fn query_entity_count<E: Entity, Q: Query<E>>(&self, query: Q) -> usize;
238
239 #[must_use]
244 fn sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> Option<EntityId<E>>
245 where
246 E: Entity,
247 Q: Query<E>,
248 R: RngId + 'static,
249 R::RngType: Rng;
250
251 #[must_use]
256 fn count_and_sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> (usize, Option<EntityId<E>>)
257 where
258 E: Entity,
259 Q: Query<E>,
260 R: RngId + 'static,
261 R::RngType: Rng;
262
263 #[must_use]
269 fn sample_entities<E, Q, R>(&self, rng_id: R, query: Q, n: usize) -> Vec<EntityId<E>>
270 where
271 E: Entity,
272 Q: Query<E>,
273 R: RngId + 'static,
274 R::RngType: Rng;
275
276 #[must_use]
278 fn get_entity_count<E: Entity>(&self) -> usize;
279
280 #[must_use]
282 fn get_entity_iterator<E: Entity>(&self) -> PopulationIterator<E>;
283
284 #[must_use]
286 fn query<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySet<E>;
287
288 #[must_use]
290 fn query_result_iterator<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySetIterator<E>;
291
292 #[must_use]
294 fn match_entity<E: Entity, Q: Query<E>>(&self, entity_id: EntityId<E>, query: Q) -> bool;
295
296 fn filter_entities<E: Entity, Q: Query<E>>(&self, entities: &mut Vec<EntityId<E>>, query: Q);
298}
299
300impl ContextEntitiesExt for Context {
301 fn add_entity<E: Entity, PL: PropertyInitializationList<E>>(
302 &mut self,
303 property_list: PL,
304 ) -> Result<EntityId<E>, IxaError> {
305 PL::validate()?;
307
308 if !PL::contains_required_properties() {
310 return Err(IxaError::MissingRequiredInitializationProperties);
311 }
312
313 let new_entity_id = self.entity_store.new_entity_id::<E>();
315
316 property_list.set_values_for_new_entity(
319 new_entity_id,
320 self.entity_store.get_property_store_mut::<E>(),
321 );
322
323 let index_count = self
326 .entity_store
327 .get_property_store::<E>()
328 .index_new_entity_fns
329 .len();
330
331 for index in 0..index_count {
332 let dispatch = self
333 .entity_store
334 .get_property_store::<E>()
335 .index_new_entity_fns[index]
336 .1;
337
338 dispatch(self, new_entity_id);
341 }
342
343 self.emit_event(EntityCreatedEvent::<E>::new(new_entity_id));
345
346 Ok(new_entity_id)
347 }
348
349 fn get_property<E: Entity, P: Property<E>>(&self, entity_id: EntityId<E>) -> P {
350 if P::is_derived() {
351 P::compute_derived(self, entity_id)
352 } else {
353 let property_store = self.get_property_value_store::<E, P>();
354 property_store.get(entity_id)
355 }
356 }
357
358 fn set_property<E: Entity, P: Property<E>>(
359 &mut self,
360 entity_id: EntityId<E>,
361 property_value: P,
362 ) {
363 debug_assert!(!P::is_derived(), "cannot set a derived property");
364
365 let mut dependents: SmallVec<[PartialPropertyChangeEventBox; 5]> = SmallVec::new();
398
399 {
401 let property_store = self.entity_store.get_property_store::<E>();
402
403 if property_store.should_create_partial_property_change(P::id(), self) {
405 dependents.push(property_store.create_partial_property_change(
406 P::id(),
407 entity_id,
408 self,
409 ));
410 }
411 for dependent_idx in P::dependents() {
413 if property_store.should_create_partial_property_change(*dependent_idx, self) {
414 dependents.push(property_store.create_partial_property_change(
415 *dependent_idx,
416 entity_id,
417 self,
418 ));
419 }
420 }
421 }
422
423 let property_value_store = self.get_property_value_store_mut::<E, P>();
425 property_value_store.set(entity_id, property_value);
426
427 for mut dependent in dependents {
430 dependent.emit_in_context(self)
431 }
432 }
433
434 fn index_property<E: Entity, P: IndexableProperty<E>>(&mut self) {
435 create_property_index::<E, P>(self, PropertyIndexType::FullIndex);
436 }
437
438 fn index_property_counts<E: Entity, P: IndexableProperty<E>>(&mut self) {
439 create_property_index::<E, P>(self, PropertyIndexType::ValueCountIndex);
440 }
441
442 fn track_periodic_value_change_counts<E, PL, P, F>(
443 &mut self,
444 period: impl Into<f64>,
445 handler: F,
446 ) where
447 E: Entity,
448 PL: PropertyList<E> + Eq + Hash,
449 P: IndexableProperty<E>,
450 F: Fn(&mut Context, &mut StratifiedValueChangeCounter<E, PL, P>) + 'static,
451 {
452 let period = period.into();
453 assert!(
454 period > 0.0 && !period.is_nan() && !period.is_infinite(),
455 "Period must be greater than 0"
456 );
457 let start_time = self.get_start_time().unwrap_or(0.0);
458 self.add_plan_with_phase(
459 start_time,
460 move |context| {
461 let counter_id = context
464 .entity_store
465 .get_property_store_mut::<E>()
466 .create_value_change_counter::<PL, P>();
467
468 context.add_passive_plan_with_phase(
471 context.get_current_time(),
472 move |context| {
473 handle_periodic_value_change_count_event::<E, PL, P, F>(
474 context, period, counter_id, handler,
475 );
476 },
477 ExecutionPhase::Last,
478 );
479 },
480 ExecutionPhase::First,
481 );
482 }
483
484 #[cfg(test)]
485 fn is_property_indexed<E: Entity, P: Property<E>>(&self) -> bool {
486 let property_store = self.entity_store.get_property_store::<E>();
487 property_store.is_property_indexed::<P>()
488 }
489
490 fn with_query_results<'a, E: Entity, Q: Query<E>>(
491 &'a self,
492 query: Q,
493 callback: &mut dyn FnMut(EntitySet<'a, E>),
494 ) {
495 if let Some(multi_property_id) = query.multi_property_id() {
500 let property_store = self.entity_store.get_property_store::<E>();
501 let query_parts = query.query_parts();
502 let lookup_result = property_store
503 .get_index_set_for_query_parts(multi_property_id, query_parts.as_ref());
504 match lookup_result {
505 IndexSetResult::Set(people_set) => {
506 callback(EntitySet::from_source(SourceSet::IndexSet(people_set)));
507 return;
508 }
509 IndexSetResult::Empty => {
510 callback(EntitySet::empty());
511 return;
512 }
513 IndexSetResult::Unsupported => {}
514 }
515 }
517
518 if query.is_empty_query() {
520 warn!("Called Context::with_query_results() with an empty query. Prefer Context::get_entity_iterator::<E>() for working with the entire population.");
521 callback(EntitySet::from_source(SourceSet::PopulationRange(
522 0..self.get_entity_count::<E>(),
523 )));
524 return;
525 }
526
527 warn!("Called Context::with_query_results() with an unindexed query. It's almost always better to use Context::query_result_iterator() for unindexed queries.");
529
530 callback(self.query(query));
532 }
533
534 fn query_entity_count<E: Entity, Q: Query<E>>(&self, query: Q) -> usize {
535 if let Some(multi_property_id) = query.multi_property_id() {
539 let property_store = self.entity_store.get_property_store::<E>();
540 let query_parts = query.query_parts();
541 let lookup_result = property_store
542 .get_index_count_for_query_parts(multi_property_id, query_parts.as_ref());
543 match lookup_result {
544 IndexCountResult::Count(count) => return count,
545 IndexCountResult::Unsupported => {}
546 }
547 }
549
550 self.query_result_iterator(query).count()
551 }
552 fn sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> Option<EntityId<E>>
553 where
554 E: Entity,
555 Q: Query<E>,
556 R: RngId + 'static,
557 R::RngType: Rng,
558 {
559 if query.is_empty_query() {
560 let population = self.get_entity_count::<E>();
561 return self.sample(rng_id, move |rng| {
562 if population == 0 {
563 warn!("Requested a sample entity from an empty population");
564 return None;
565 }
566 let index = if population <= u32::MAX as usize {
567 rng.random_range(0..population as u32) as usize
568 } else {
569 rng.random_range(0..population)
570 };
571 Some(EntityId::new(index))
572 });
573 }
574
575 let query_result = self.query(query);
576 self.sample(rng_id, move |rng| query_result.sample_entity(rng))
577 }
578
579 fn count_and_sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> (usize, Option<EntityId<E>>)
580 where
581 E: Entity,
582 Q: Query<E>,
583 R: RngId + 'static,
584 R::RngType: Rng,
585 {
586 if query.is_empty_query() {
587 let population = self.get_entity_count::<E>();
588 return self.sample(rng_id, move |rng| {
589 if population == 0 {
590 return (0, None);
591 }
592 let index = if population <= u32::MAX as usize {
593 rng.random_range(0..population as u32) as usize
594 } else {
595 rng.random_range(0..population)
596 };
597 (population, Some(EntityId::new(index)))
598 });
599 }
600
601 let query_result = self.query(query);
602 self.sample(rng_id, move |rng| query_result.count_and_sample_entity(rng))
603 }
604
605 fn sample_entities<E, Q, R>(&self, rng_id: R, query: Q, n: usize) -> Vec<EntityId<E>>
606 where
607 E: Entity,
608 Q: Query<E>,
609 R: RngId + 'static,
610 R::RngType: Rng,
611 {
612 if query.is_empty_query() {
613 let population = self.get_entity_count::<E>();
614 return self.sample(rng_id, move |rng| {
615 if population == 0 {
616 warn!("Requested a sample of entities from an empty population");
617 return vec![];
618 }
619 if n >= population {
620 return PopulationIterator::<E>::new(population).collect();
621 }
622 sample_multiple_from_known_length(rng, PopulationIterator::<E>::new(population), n)
623 });
624 }
625
626 let query_result = self.query(query);
627 self.sample(rng_id, move |rng| query_result.sample_entities(rng, n))
628 }
629
630 fn get_entity_count<E: Entity>(&self) -> usize {
631 self.entity_store.get_entity_count::<E>()
632 }
633
634 fn get_entity_iterator<E: Entity>(&self) -> PopulationIterator<E> {
635 self.entity_store.get_entity_iterator::<E>()
636 }
637
638 fn query<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySet<E> {
639 query.new_query_result(self)
640 }
641
642 fn query_result_iterator<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySetIterator<E> {
643 query.new_query_result_iterator(self)
644 }
645
646 fn match_entity<E: Entity, Q: Query<E>>(&self, entity_id: EntityId<E>, query: Q) -> bool {
647 query.match_entity(entity_id, self)
648 }
649
650 fn filter_entities<E: Entity, Q: Query<E>>(&self, entities: &mut Vec<EntityId<E>>, query: Q) {
651 query.filter_entities(entities, self);
652 }
653}
654
655#[cfg(test)]
656mod tests {
657 use std::cell::RefCell;
658 use std::rc::Rc;
659
660 use super::*;
661 use crate::entity::query::QueryInternal;
662 use crate::hashing::IndexSet;
663 use crate::prelude::PropertyChangeEvent;
664 use crate::{
665 define_derived_property, define_entity, define_multi_property, define_property, define_rng,
666 impl_property, with,
667 };
668
669 define_entity!(Animal);
670 define_property!(struct Legs(u8), Animal, default_const = Legs(4));
671 define_rng!(EntityContextTestRng);
672
673 define_entity!(Person);
674
675 define_property!(struct Age(u8), Person);
676
677 #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
678 struct CounterValue(u8);
679 impl_property!(CounterValue, Person, default_const = CounterValue(0));
680
681 #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
682 struct CounterStratum(bool);
683 impl_property!(
684 CounterStratum,
685 Person,
686 default_const = CounterStratum(false)
687 );
688
689 #[derive(Clone, Copy)]
690 struct WrappedF64(f64);
691
692 impl From<WrappedF64> for f64 {
693 fn from(value: WrappedF64) -> Self {
694 value.0
695 }
696 }
697
698 define_property!(
699 enum InfectionStatus {
700 Susceptible,
701 Infected,
702 Recovered,
703 },
704 Person,
705 default_const = InfectionStatus::Susceptible
706 );
707
708 define_property!(
709 struct Vaccinated(bool),
710 Person,
711 default_const = Vaccinated(false)
712 );
713
714 define_derived_property!(
715 enum AgeGroup {
716 Child,
717 Adult,
718 Senior,
719 },
720 Person,
721 [Age],
722 |age| {
723 if age.0 <= 18 {
724 AgeGroup::Child
725 } else if age.0 <= 65 {
726 AgeGroup::Adult
727 } else {
728 AgeGroup::Senior
729 }
730 }
731 );
732
733 define_derived_property!(
734 enum RiskLevel {
735 Low,
736 Medium,
737 High,
738 },
739 Person,
740 [AgeGroup, Vaccinated, InfectionStatus],
741 |age_group, vaccinated, infection_status| {
742 match (age_group, vaccinated, infection_status) {
743 (AgeGroup::Senior, Vaccinated(false), InfectionStatus::Susceptible) => {
744 RiskLevel::High
745 }
746 (_, Vaccinated(false), InfectionStatus::Susceptible) => RiskLevel::Medium,
747 _ => RiskLevel::Low,
748 }
749 }
750 );
751
752 define_property!(struct IsRunner(bool), Person, default_const = IsRunner(false));
766 define_property!(struct IsSwimmer(bool), Person, default_const = IsSwimmer(false));
767 define_derived_property!(
768 struct AdultRunner(bool),
769 Person,
770 [AgeGroup, IsRunner],
771 | age_group, is_runner | {
772 AdultRunner(
773 age_group == AgeGroup::Adult
774 && is_runner.0
775 )
776 }
777 );
778 define_derived_property!(
779 struct AdultSwimmer(bool),
780 Person,
781 [AgeGroup, IsSwimmer],
782 | age_group, is_swimmer | {
783 AdultSwimmer(
784 age_group == AgeGroup::Adult
785 && is_swimmer.0
786 )
787 }
788 );
789 define_derived_property!(
790 struct AdultAthlete(bool),
791 Person,
792 [AdultSwimmer, AdultRunner],
793 | adult_swimmer, adult_runner | {
794 AdultAthlete(
795 adult_swimmer.0 || adult_runner.0
796 )
797 }
798 );
799
800 #[test]
801 fn add_and_count_entities() {
802 let mut context = Context::new();
803
804 let _person1 = context
805 .add_entity(with!(
806 Person,
807 Age(12),
808 InfectionStatus::Susceptible,
809 Vaccinated(true)
810 ))
811 .unwrap();
812 assert_eq!(context.get_entity_count::<Person>(), 1);
813
814 let _person2 = context
815 .add_entity(with!(Person, Age(34), Vaccinated(true)))
816 .unwrap();
817 assert_eq!(context.get_entity_count::<Person>(), 2);
818
819 let _person3 = context.add_entity(with!(Person, Age(120))).unwrap();
821 assert_eq!(context.get_entity_count::<Person>(), 3);
822 }
823
824 #[test]
825 fn add_entity_with_zst() {
826 let mut context = Context::new();
827 let animal = context.add_entity(Animal).unwrap();
828 assert_eq!(context.get_entity_count::<Animal>(), 1);
829 assert_eq!(context.get_property::<Animal, Legs>(animal), Legs(4));
830 }
831
832 #[derive(Copy, Clone, Debug)]
834 enum IndexMode {
835 Unindexed,
836 FullIndex,
837 ValueCountIndex,
838 }
839
840 fn setup_context_for_index_tests(index_mode: IndexMode) -> (Context, Age, Age) {
842 let mut context = Context::new();
843 match index_mode {
844 IndexMode::Unindexed => {}
845 IndexMode::FullIndex => context.index_property::<Person, Age>(),
846 IndexMode::ValueCountIndex => context.index_property_counts::<Person, Age>(),
847 }
848
849 let existing_value = Age(12);
850 let missing_value = Age(99);
851
852 let _ = context.add_entity(with!(Person, existing_value)).unwrap();
853 let _ = context.add_entity(with!(Person, existing_value)).unwrap();
854
855 (context, existing_value, missing_value)
856 }
857
858 #[test]
859 fn query_results_respect_index_modes() {
860 let modes = [
861 IndexMode::Unindexed,
862 IndexMode::FullIndex,
863 IndexMode::ValueCountIndex,
864 ];
865
866 for mode in modes {
867 let (context, existing_value, missing_value) = setup_context_for_index_tests(mode);
868
869 let mut existing_len = 0;
870 context.with_query_results(with!(Person, existing_value), &mut |people_set| {
871 existing_len = people_set.into_iter().count();
872 });
873 assert_eq!(existing_len, 2, "Wrong length for {mode:?}");
874
875 let mut missing_len = 0;
876 context.with_query_results(with!(Person, missing_value), &mut |people_set| {
877 missing_len = people_set.into_iter().count();
878 });
879 assert_eq!(missing_len, 0);
880
881 let existing_count = context
882 .query_result_iterator(with!(Person, existing_value))
883 .count();
884 assert_eq!(existing_count, 2);
885
886 let missing_count = context
887 .query_result_iterator(with!(Person, missing_value))
888 .count();
889 assert_eq!(missing_count, 0);
890
891 assert_eq!(context.query_entity_count(with!(Person, existing_value)), 2);
892 assert_eq!(context.query_entity_count(with!(Person, missing_value)), 0);
893 }
894 }
895
896 #[test]
897 fn add_an_entity_without_required_properties() {
898 let mut context = Context::new();
899 let result = context.add_entity(with!(
900 Person,
901 InfectionStatus::Susceptible,
902 Vaccinated(true)
903 ));
904
905 assert!(matches!(
906 result,
907 Err(crate::IxaError::MissingRequiredInitializationProperties)
908 ));
909 }
910
911 #[test]
912 fn new_entities_have_default_values() {
913 let mut context = Context::new();
914
915 let person = context.add_entity(with!(Person, Age(25))).unwrap();
917
918 let age: Age = context.get_property(person);
920 assert_eq!(age, Age(25));
921 let infection_status: InfectionStatus = context.get_property(person);
922 assert_eq!(infection_status, InfectionStatus::Susceptible);
923 let vaccinated: Vaccinated = context.get_property(person);
924 assert_eq!(vaccinated, Vaccinated(false));
925
926 context.set_property(person, Age(26));
928 context.set_property(person, InfectionStatus::Infected);
929 context.set_property(person, Vaccinated(true));
930
931 let age: Age = context.get_property(person);
933 assert_eq!(age, Age(26));
934 let infection_status: InfectionStatus = context.get_property(person);
935 assert_eq!(infection_status, InfectionStatus::Infected);
936 let vaccinated: Vaccinated = context.get_property(person);
937 assert_eq!(vaccinated, Vaccinated(true));
938 }
939
940 #[test]
941 fn get_and_set_property_explicit() {
942 let mut context = Context::new();
943
944 let person = context
946 .add_entity(with!(
947 Person,
948 Age(25),
949 InfectionStatus::Recovered,
950 Vaccinated(true)
951 ))
952 .unwrap();
953
954 let age: Age = context.get_property(person);
956 assert_eq!(age, Age(25));
957 let infection_status: InfectionStatus = context.get_property(person);
958 assert_eq!(infection_status, InfectionStatus::Recovered);
959 let vaccinated: Vaccinated = context.get_property(person);
960 assert_eq!(vaccinated, Vaccinated(true));
961
962 context.set_property(person, Age(26));
964 context.set_property(person, InfectionStatus::Infected);
965 context.set_property(person, Vaccinated(false));
966
967 let age: Age = context.get_property(person);
969 assert_eq!(age, Age(26));
970 let infection_status: InfectionStatus = context.get_property(person);
971 assert_eq!(infection_status, InfectionStatus::Infected);
972 let vaccinated: Vaccinated = context.get_property(person);
973 assert_eq!(vaccinated, Vaccinated(false));
974 }
975
976 #[test]
977 fn count_entities() {
978 let mut context = Context::new();
979
980 assert_eq!(context.get_entity_count::<Animal>(), 0);
981 assert_eq!(context.get_entity_count::<Person>(), 0);
982
983 for _ in 0..7 {
985 let _: PersonId = context.add_entity(with!(Person, Age(25))).unwrap();
986 }
987 for _ in 0..5 {
988 let _: AnimalId = context.add_entity(with!(Animal, Legs(2))).unwrap();
989 }
990
991 assert_eq!(context.get_entity_count::<Animal>(), 5);
992 assert_eq!(context.get_entity_count::<Person>(), 7);
993
994 let _: PersonId = context.add_entity(with!(Person, Age(30))).unwrap();
995 let _: AnimalId = context.add_entity(with!(Animal, Legs(8))).unwrap();
996
997 assert_eq!(context.get_entity_count::<Animal>(), 6);
998 assert_eq!(context.get_entity_count::<Person>(), 8);
999 }
1000
1001 #[test]
1002 fn count_and_sample_entity_empty_query_fast_path() {
1003 let mut context = Context::new();
1004 context.init_random(42);
1005 for age in [10u8, 20, 30] {
1006 let _: PersonId = context.add_entity(with!(Person, Age(age))).unwrap();
1007 }
1008
1009 let (count, sampled) =
1010 context.count_and_sample_entity::<Person, _, _>(EntityContextTestRng, Person);
1011 assert_eq!(count, 3);
1012 assert!(sampled.is_some());
1013 }
1014
1015 #[test]
1016 fn count_and_sample_entity_unindexed_derived_query() {
1017 let mut context = Context::new();
1018 context.init_random(43);
1019 for age in [10u8, 20, 30, 80] {
1020 let _: PersonId = context.add_entity(with!(Person, Age(age))).unwrap();
1021 }
1022
1023 let query = with!(Person, AgeGroup::Adult);
1024 let expected_count = context.query_entity_count(query);
1025 let (count, sampled) = context.count_and_sample_entity(EntityContextTestRng, query);
1026 assert_eq!(count, expected_count);
1027 assert_eq!(sampled.is_some(), count > 0);
1028 if let Some(entity_id) = sampled {
1029 assert!(context.match_entity(entity_id, query));
1030 }
1031 }
1032
1033 #[test]
1034 fn get_derived_property_multiple_deps() {
1035 let mut context = Context::new();
1036 context.index_property::<Person, RiskLevel>();
1037
1038 let expected_high_id: PersonId = context
1039 .add_entity(with!(
1040 Person,
1041 Age(77),
1042 Vaccinated(false),
1043 InfectionStatus::Susceptible
1044 ))
1045 .unwrap();
1046 let expected_med_id: PersonId = context
1047 .add_entity(with!(
1048 Person,
1049 Age(30),
1050 Vaccinated(false),
1051 InfectionStatus::Susceptible
1052 ))
1053 .unwrap();
1054 let expected_low_id: PersonId = context
1055 .add_entity(with!(
1056 Person,
1057 Age(3),
1058 Vaccinated(true),
1059 InfectionStatus::Recovered
1060 ))
1061 .unwrap();
1062
1063 let actual_high: RiskLevel = context.get_property(expected_high_id);
1064 assert_eq!(actual_high, RiskLevel::High);
1065 let actual_med: RiskLevel = context.get_property(expected_med_id);
1066 assert_eq!(actual_med, RiskLevel::Medium);
1067 let actual_low: RiskLevel = context.get_property(expected_low_id);
1068 assert_eq!(actual_low, RiskLevel::Low);
1069
1070 assert_eq!(
1071 context
1072 .query(with!(Person, RiskLevel::High))
1073 .into_iter()
1074 .collect::<IndexSet<_>>(),
1075 [expected_high_id].into_iter().collect::<IndexSet<_>>(),
1076 );
1077 assert_eq!(
1078 context
1079 .query(with!(Person, RiskLevel::Medium))
1080 .into_iter()
1081 .collect::<IndexSet<_>>(),
1082 [expected_med_id].into_iter().collect::<IndexSet<_>>(),
1083 );
1084 assert_eq!(
1085 context
1086 .query(with!(Person, RiskLevel::Low))
1087 .into_iter()
1088 .collect::<IndexSet<_>>(),
1089 [expected_low_id].into_iter().collect::<IndexSet<_>>(),
1090 );
1091 }
1092
1093 #[test]
1094 fn indexed_constant_default_handles_sparse_creation_paths() {
1095 let mut context = Context::new();
1096 context.index_property::<Person, IsRunner>();
1097
1098 let omitted = context.add_entity(with!(Person, Age(20))).unwrap();
1099 let explicit = context
1100 .add_entity(with!(Person, Age(21), IsRunner(false)))
1101 .unwrap();
1102
1103 let matching = context
1104 .query(with!(Person, IsRunner(false)))
1105 .into_iter()
1106 .collect::<IndexSet<_>>();
1107 assert_eq!(
1108 matching,
1109 [omitted, explicit].into_iter().collect::<IndexSet<_>>()
1110 );
1111 }
1112
1113 #[test]
1114 fn listen_to_derived_property_change_event() {
1115 let mut context = Context::new();
1116
1117 let expected_high_id = PersonId::new(0);
1118
1119 let risk_flag = Rc::new(RefCell::new(0));
1122 let risk_flag_clone = risk_flag.clone();
1123 context.subscribe_to_event(
1124 move |_context, event: PropertyChangeEvent<Person, RiskLevel>| {
1125 assert_eq!(event.entity_id, expected_high_id);
1126 assert_eq!(event.previous, RiskLevel::High);
1127 assert_eq!(event.current, RiskLevel::Medium);
1128 *risk_flag_clone.borrow_mut() += 1;
1129 },
1130 );
1131 let age_group_flag = Rc::new(RefCell::new(0));
1133 let age_group_flag_clone = age_group_flag.clone();
1134 context.subscribe_to_event(
1135 move |_context, event: PropertyChangeEvent<Person, AgeGroup>| {
1136 assert_eq!(event.entity_id, expected_high_id);
1137 assert_eq!(event.previous, AgeGroup::Senior);
1138 assert_eq!(event.current, AgeGroup::Adult);
1139 *age_group_flag_clone.borrow_mut() += 1;
1140 },
1141 );
1142
1143 let expected_high_id: PersonId = context
1145 .add_entity(with!(
1146 Person,
1147 Age(77),
1148 Vaccinated(false),
1149 InfectionStatus::Susceptible
1150 ))
1151 .unwrap();
1152
1153 context.set_property(expected_high_id, Age(20));
1155
1156 context.execute();
1158 assert_eq!(*risk_flag.borrow(), 1);
1160 assert_eq!(*age_group_flag.borrow(), 1);
1161 }
1162
1163 #[test]
1183 fn observe_diamond_property_change() {
1184 let mut context = Context::new();
1185 let person = context
1186 .add_entity(with!(Person, Age(17), IsSwimmer(true)))
1187 .unwrap();
1188
1189 let is_adult_athlete: AdultAthlete = context.get_property(person);
1190 assert!(!is_adult_athlete.0);
1191
1192 let flag = Rc::new(RefCell::new(0));
1193 let flag_clone = flag.clone();
1194 context.subscribe_to_event(
1195 move |_context, event: PropertyChangeEvent<Person, AdultAthlete>| {
1196 assert_eq!(event.entity_id, person);
1197 assert_eq!(event.previous, AdultAthlete(false));
1198 assert_eq!(event.current, AdultAthlete(true));
1199 *flag_clone.borrow_mut() += 1;
1200 },
1201 );
1202
1203 context.set_property(person, Age(20));
1204 let is_adult_athlete: AdultAthlete = context.get_property(person);
1206 assert!(is_adult_athlete.0);
1207
1208 context.execute();
1210 assert_eq!(*flag.borrow(), 1);
1212 }
1213
1214 define_multi_property!(Person, (InfectionStatus, Vaccinated));
1217 define_multi_property!(Person, (Vaccinated, InfectionStatus));
1218
1219 #[test]
1220 fn with_query_results_finds_multi_index() {
1221 use crate::rand::rngs::SmallRng;
1222 use crate::rand::seq::IndexedRandom;
1223 use crate::rand::SeedableRng;
1224
1225 let mut rng = SmallRng::seed_from_u64(42);
1226 let mut context = Context::new();
1227
1228 for _ in 0..10_000usize {
1229 let infection_status = *[
1230 InfectionStatus::Susceptible,
1231 InfectionStatus::Infected,
1232 InfectionStatus::Recovered,
1233 ]
1234 .choose(&mut rng)
1235 .unwrap();
1236 let vaccination_status: bool = rng.random_bool(0.5);
1237 let age: u8 = rng.random_range(0..100);
1238 context
1239 .add_entity(with!(
1240 Person,
1241 Age(age),
1242 infection_status,
1243 Vaccinated(vaccination_status)
1244 ))
1245 .unwrap();
1246 }
1247 context.index_property::<Person, InfectionStatusVaccinated>();
1248 let _ = context.query_result_iterator(with!(
1250 Person,
1251 InfectionStatus::Susceptible,
1252 Vaccinated(true)
1253 ));
1254
1255 let mut result_entities: IndexSet<EntityId<Person>> = IndexSet::default();
1257 context.with_query_results(
1258 with!(Person, InfectionStatus::Susceptible, Vaccinated(true)),
1259 &mut |result_set| {
1260 result_entities = result_set.into_iter().collect::<IndexSet<_>>();
1261 },
1262 );
1263
1264 assert_ne!(
1267 InfectionStatusVaccinated::id(),
1268 VaccinatedInfectionStatus::id()
1269 );
1270 assert_ne!(
1271 InfectionStatusVaccinated::type_id(),
1272 VaccinatedInfectionStatus::type_id()
1273 );
1274 assert_eq!(
1275 InfectionStatusVaccinated::id(),
1276 (InfectionStatus::Susceptible, Vaccinated(true))
1277 .multi_property_id()
1278 .unwrap()
1279 );
1280
1281 let property_id = InfectionStatusVaccinated::id();
1283
1284 let property_store = context.entity_store.get_property_store::<Person>();
1285 let query = (InfectionStatus::Susceptible, Vaccinated(true));
1286 let query_parts = query.query_parts();
1287 let bucket =
1288 match property_store.get_index_set_for_query_parts(property_id, query_parts.as_ref()) {
1289 IndexSetResult::Set(bucket) => bucket,
1290 other => panic!("expected indexed query bucket, found {other:?}"),
1291 };
1292
1293 let expected_entities = bucket.iter().copied().collect::<IndexSet<_>>();
1294 assert_eq!(expected_entities, result_entities);
1295 }
1296
1297 #[test]
1298 fn query_returns_entity_set_and_query_result_iterator_remains_compatible() {
1299 let mut context = Context::new();
1300 let p1 = context
1301 .add_entity(with!(
1302 Person,
1303 Age(21),
1304 InfectionStatus::Susceptible,
1305 Vaccinated(true)
1306 ))
1307 .unwrap();
1308 let _p2 = context
1309 .add_entity(with!(
1310 Person,
1311 Age(22),
1312 InfectionStatus::Susceptible,
1313 Vaccinated(false)
1314 ))
1315 .unwrap();
1316 let p3 = context
1317 .add_entity(with!(
1318 Person,
1319 Age(23),
1320 InfectionStatus::Infected,
1321 Vaccinated(true)
1322 ))
1323 .unwrap();
1324
1325 let query = with!(Person, Vaccinated(true));
1326
1327 let from_set = context
1328 .query::<Person, _>(query)
1329 .into_iter()
1330 .collect::<IndexSet<_>>();
1331 let from_iterator = context
1332 .query_result_iterator(query)
1333 .collect::<IndexSet<_>>();
1334
1335 assert_eq!(from_set, from_iterator);
1336 assert!(from_set.contains(&p1));
1337 assert!(from_set.contains(&p3));
1338 assert_eq!(from_set.len(), 2);
1339 }
1340
1341 #[test]
1342 fn set_property_correctly_maintains_index() {
1343 let mut context = Context::new();
1344 context.index_property::<Person, InfectionStatus>();
1345 context.index_property::<Person, AgeGroup>();
1346
1347 let person1 = context.add_entity(with!(Person, Age(22))).unwrap();
1348 let person2 = context.add_entity(with!(Person, Age(22))).unwrap();
1349 for _ in 0..4 {
1350 let _: PersonId = context.add_entity(with!(Person, Age(22))).unwrap();
1351 }
1352
1353 assert_eq!(
1355 context.query_entity_count(with!(Person, InfectionStatus::Susceptible)),
1356 6
1357 );
1358 assert_eq!(
1359 context.query_entity_count(with!(Person, InfectionStatus::Infected)),
1360 0
1361 );
1362 assert_eq!(
1363 context.query_entity_count(with!(Person, InfectionStatus::Recovered)),
1364 0
1365 );
1366
1367 context.set_property(person1, InfectionStatus::Infected);
1368
1369 assert_eq!(
1370 context.query_entity_count(with!(Person, InfectionStatus::Susceptible)),
1371 5
1372 );
1373 assert_eq!(
1374 context.query_entity_count(with!(Person, InfectionStatus::Infected)),
1375 1
1376 );
1377 assert_eq!(
1378 context.query_entity_count(with!(Person, InfectionStatus::Recovered)),
1379 0
1380 );
1381
1382 context.set_property(person1, InfectionStatus::Recovered);
1383
1384 assert_eq!(
1385 context.query_entity_count(with!(Person, InfectionStatus::Susceptible)),
1386 5
1387 );
1388 assert_eq!(
1389 context.query_entity_count(with!(Person, InfectionStatus::Infected)),
1390 0
1391 );
1392 assert_eq!(
1393 context.query_entity_count(with!(Person, InfectionStatus::Recovered)),
1394 1
1395 );
1396
1397 assert_eq!(
1399 context.query_entity_count(with!(Person, AgeGroup::Child)),
1400 0
1401 );
1402 assert_eq!(
1403 context.query_entity_count(with!(Person, AgeGroup::Adult)),
1404 6
1405 );
1406 assert_eq!(
1407 context.query_entity_count(with!(Person, AgeGroup::Senior)),
1408 0
1409 );
1410
1411 context.set_property(person2, Age(12));
1412
1413 assert_eq!(
1414 context.query_entity_count(with!(Person, AgeGroup::Child)),
1415 1
1416 );
1417 assert_eq!(
1418 context.query_entity_count(with!(Person, AgeGroup::Adult)),
1419 5
1420 );
1421 assert_eq!(
1422 context.query_entity_count(with!(Person, AgeGroup::Senior)),
1423 0
1424 );
1425
1426 context.set_property(person1, Age(75));
1427
1428 assert_eq!(
1429 context.query_entity_count(with!(Person, AgeGroup::Child)),
1430 1
1431 );
1432 assert_eq!(
1433 context.query_entity_count(with!(Person, AgeGroup::Adult)),
1434 4
1435 );
1436 assert_eq!(
1437 context.query_entity_count(with!(Person, AgeGroup::Senior)),
1438 1
1439 );
1440
1441 context.set_property(person2, Age(77));
1442
1443 assert_eq!(
1444 context.query_entity_count(with!(Person, AgeGroup::Child)),
1445 0
1446 );
1447 assert_eq!(
1448 context.query_entity_count(with!(Person, AgeGroup::Adult)),
1449 4
1450 );
1451 assert_eq!(
1452 context.query_entity_count(with!(Person, AgeGroup::Senior)),
1453 2
1454 );
1455 }
1456
1457 #[test]
1458 fn query_unindexed_default_properties() {
1459 let mut context = Context::new();
1460
1461 for idx in 0..10 {
1463 if idx % 2 == 0 {
1464 context.add_entity(with!(Person, Age(22))).unwrap();
1465 } else {
1466 context
1467 .add_entity(with!(Person, Age(22), InfectionStatus::Recovered))
1468 .unwrap();
1469 }
1470 }
1471 for _ in 0..10 {
1473 let _: PersonId = context.add_entity(with!(Person, Age(22))).unwrap();
1474 }
1475
1476 assert_eq!(
1477 context.query_entity_count(with!(Person, InfectionStatus::Recovered)),
1478 5
1479 );
1480 assert_eq!(
1481 context.query_entity_count(with!(Person, InfectionStatus::Susceptible)),
1482 15
1483 );
1484 }
1485
1486 #[test]
1487 fn query_unindexed_derived_properties() {
1488 let mut context = Context::new();
1489
1490 for _ in 0..10 {
1491 let _: PersonId = context.add_entity(with!(Person, Age(22))).unwrap();
1492 }
1493
1494 assert_eq!(
1495 context.query_entity_count(with!(Person, AdultAthlete(false))),
1496 10
1497 );
1498 }
1499
1500 #[test]
1501 fn track_periodic_value_change_counts_uses_distinct_counters() {
1502 let mut context = Context::new();
1503
1504 context.track_periodic_value_change_counts::<Person, (CounterStratum,), CounterValue, _>(
1505 1.0,
1506 move |_context, _counter| {},
1507 );
1508
1509 context.track_periodic_value_change_counts::<Person, (CounterStratum,), CounterValue, _>(
1510 1.0,
1511 move |_context, _counter| {},
1512 );
1513
1514 let property_value_store = context.get_property_value_store::<Person, CounterValue>();
1515 assert_eq!(property_value_store.value_change_counters.len(), 0);
1516
1517 context.add_plan(0.5, Context::shutdown);
1518 context.execute();
1519
1520 let property_value_store = context.get_property_value_store::<Person, CounterValue>();
1521 assert_eq!(property_value_store.value_change_counters.len(), 2);
1522 }
1523
1524 #[test]
1525 fn track_periodic_value_change_counts_accepts_into_f64() {
1526 let mut context = Context::new();
1527 let observed_times = Rc::new(RefCell::new(Vec::new()));
1528 let observed_times_clone = Rc::clone(&observed_times);
1529
1530 context.track_periodic_value_change_counts::<Person, (CounterStratum,), CounterValue, _>(
1531 WrappedF64(1.0),
1532 move |context, _counter| {
1533 observed_times_clone
1534 .borrow_mut()
1535 .push(context.get_current_time());
1536 },
1537 );
1538 context.add_plan(1.0, |_| {});
1539
1540 context.execute();
1541
1542 assert_eq!(*observed_times.borrow(), vec![0.0, 1.0]);
1543 }
1544
1545 #[test]
1546 fn value_change_counter_updates_on_true_transitions() {
1547 let mut context = Context::new();
1548 let observed = Rc::new(RefCell::new(Vec::<(usize, usize)>::new()));
1549 let observed_clone = observed.clone();
1550
1551 context.track_periodic_value_change_counts(1.0, move |_context, counter| {
1552 observed_clone.borrow_mut().push((
1553 counter.get_count((CounterStratum(true),), CounterValue(1)),
1554 counter.get_count((CounterStratum(true),), CounterValue(2)),
1555 ));
1556 });
1557
1558 let person = context
1559 .add_entity(with!(
1560 Person,
1561 Age(10),
1562 CounterValue(0),
1563 CounterStratum(true)
1564 ))
1565 .unwrap();
1566 context.add_plan(0.1, move |context| {
1567 context.set_property(person, CounterValue(1));
1568 context.set_property(person, CounterValue(1));
1569 context.set_property(person, CounterValue(2));
1570 });
1571 context.add_plan(1.0, |_| {});
1572
1573 context.execute();
1574 assert_eq!(*observed.borrow(), vec![(0, 0), (1, 1)]);
1575 }
1576
1577 #[test]
1578 fn periodic_value_change_counts_report_and_clear() {
1579 let mut context = Context::new();
1580 let person = context
1581 .add_entity(with!(
1582 Person,
1583 Age(10),
1584 CounterValue(0),
1585 CounterStratum(true)
1586 ))
1587 .unwrap();
1588
1589 let observed = Rc::new(RefCell::new(Vec::<usize>::new()));
1590 let observed_clone = observed.clone();
1591
1592 context.track_periodic_value_change_counts(1.0, move |_context, counter| {
1593 observed_clone
1594 .borrow_mut()
1595 .push(counter.get_count((CounterStratum(true),), CounterValue(1)));
1596 });
1597
1598 context.add_plan(0.5, move |context| {
1599 context.set_property(person, CounterValue(1));
1600 });
1601 context.add_plan(1.5, move |context| {
1602 context.set_property(person, CounterValue(1));
1603 });
1604 context.add_plan(2.0, |_| {});
1605
1606 context.execute();
1607 assert_eq!(*observed.borrow(), vec![0, 1, 0]);
1608 }
1609
1610 #[test]
1611 fn periodic_value_change_counts_start_time_and_phase_behavior() {
1612 let mut context = Context::new();
1613 context.set_start_time(-2.0);
1614
1615 let person = context
1616 .add_entity(with!(
1617 Person,
1618 Age(10),
1619 CounterValue(0),
1620 CounterStratum(true)
1621 ))
1622 .unwrap();
1623
1624 let observed_times = Rc::new(RefCell::new(Vec::<f64>::new()));
1625 let observed_counts = Rc::new(RefCell::new(Vec::<usize>::new()));
1626 let observed_times_clone = observed_times.clone();
1627 let observed_counts_clone = observed_counts.clone();
1628
1629 context.track_periodic_value_change_counts(1.0, move |context, counter| {
1630 observed_times_clone
1631 .borrow_mut()
1632 .push(context.get_current_time());
1633 observed_counts_clone
1634 .borrow_mut()
1635 .push(counter.get_count((CounterStratum(true),), CounterValue(1)));
1636 });
1637
1638 context.add_plan_with_phase(
1639 -2.0,
1640 move |context| {
1641 context.set_property(person, CounterValue(1));
1642 },
1643 ExecutionPhase::Normal,
1644 );
1645 context.add_plan(0.0, |_| {});
1646
1647 context.execute();
1648
1649 assert_eq!(*observed_times.borrow(), vec![-2.0, -1.0, 0.0]);
1650 assert_eq!(*observed_counts.borrow(), vec![1, 0, 0]);
1651 }
1652}