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::property::{IndexableProperty, Property};
9use crate::entity::property_list::{PropertyInitializationList, PropertyList};
10use crate::entity::query::Query;
11use crate::entity::value_change_counter::StratifiedValueChangeCounter;
12use crate::entity::{Entity, EntityId, PopulationIterator};
13use crate::rand::Rng;
14use crate::random::sample_multiple_from_known_length;
15use crate::{warn, Context, ContextRandomExt, ExecutionPhase, IxaError, RngId};
16
17fn handle_periodic_value_change_count_event<E, PL, P, F>(
18 context: &mut Context,
19 period: f64,
20 counter_id: usize,
21 handler: F,
22) where
23 E: Entity,
24 PL: PropertyList<E> + Eq + Hash,
25 P: IndexableProperty<E>,
26 F: Fn(&mut Context, &mut StratifiedValueChangeCounter<E, PL, P>) + 'static,
27{
28 let mut counter = {
29 let property_value_store = context.get_property_value_store_mut::<E, P>();
30 let slot = property_value_store
31 .value_change_counters
32 .get_mut(counter_id)
33 .unwrap_or_else(|| {
34 panic!(
35 "No value change counter found for property {} with counter_id {}",
36 P::name(),
37 counter_id
38 )
39 });
40 std::mem::replace(
41 slot.get_mut(),
42 Box::new(StratifiedValueChangeCounter::<E, PL, P>::new()),
43 )
44 };
45
46 {
47 let counter = counter
48 .as_any_mut()
49 .downcast_mut::<StratifiedValueChangeCounter<E, PL, P>>()
50 .unwrap_or_else(|| {
51 panic!(
52 "Value change counter for property {} and counter_id {} had unexpected type",
53 P::name(),
54 counter_id
55 )
56 });
57
58 handler(context, counter);
59 counter.clear();
60 }
61
62 {
63 let property_value_store = context.get_property_value_store_mut::<E, P>();
64 let slot = property_value_store
65 .value_change_counters
66 .get_mut(counter_id)
67 .unwrap_or_else(|| {
68 panic!(
69 "No value change counter found for property {} with counter_id {}",
70 P::name(),
71 counter_id
72 )
73 });
74
75 let _ = std::mem::replace(slot.get_mut(), counter);
77 }
78
79 if context.remaining_plan_count() == 0 {
80 return;
81 }
82
83 let next_time = context.get_current_time() + period;
84 context.add_plan_with_phase(
85 next_time,
86 move |context| {
87 handle_periodic_value_change_count_event::<E, PL, P, F>(
88 context, period, counter_id, handler,
89 );
90 },
91 ExecutionPhase::Last,
92 );
93}
94
95pub trait ContextEntitiesExt {
98 fn add_entity<E: Entity, PL: PropertyInitializationList<E>>(
99 &mut self,
100 property_list: PL,
101 ) -> Result<EntityId<E>, IxaError>;
102
103 #[must_use]
110 fn get_property<E: Entity, P: Property<E>>(&self, entity_id: EntityId<E>) -> P;
111
112 fn set_property<E: Entity, P: Property<E>>(
114 &mut self,
115 entity_id: EntityId<E>,
116 property_value: P,
117 );
118
119 fn index_property<E: Entity, P: IndexableProperty<E>>(&mut self);
126
127 fn index_property_counts<E: Entity, P: IndexableProperty<E>>(&mut self);
132
133 fn track_periodic_value_change_counts<E, PL, P, F>(&mut self, period: f64, handler: F)
151 where
152 E: Entity,
153 PL: PropertyList<E> + Eq + Hash,
154 P: Property<E> + Eq + Hash,
155 F: Fn(&mut Context, &mut StratifiedValueChangeCounter<E, PL, P>) + 'static;
156
157 #[cfg(test)]
165 #[must_use]
166 fn is_property_indexed<E: Entity, P: Property<E>>(&self) -> bool;
167
168 fn with_query_results<'a, E: Entity, Q: Query<E>>(
172 &'a self,
173 query: Q,
174 callback: &mut dyn FnMut(EntitySet<'a, E>),
175 );
176
177 #[must_use]
182 fn query_entity_count<E: Entity, Q: Query<E>>(&self, query: Q) -> usize;
183
184 #[must_use]
189 fn sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> Option<EntityId<E>>
190 where
191 E: Entity,
192 Q: Query<E>,
193 R: RngId + 'static,
194 R::RngType: Rng;
195
196 #[must_use]
201 fn count_and_sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> (usize, Option<EntityId<E>>)
202 where
203 E: Entity,
204 Q: Query<E>,
205 R: RngId + 'static,
206 R::RngType: Rng;
207
208 #[must_use]
214 fn sample_entities<E, Q, R>(&self, rng_id: R, query: Q, n: usize) -> Vec<EntityId<E>>
215 where
216 E: Entity,
217 Q: Query<E>,
218 R: RngId + 'static,
219 R::RngType: Rng;
220
221 #[must_use]
223 fn get_entity_count<E: Entity>(&self) -> usize;
224
225 #[must_use]
227 fn get_entity_iterator<E: Entity>(&self) -> PopulationIterator<E>;
228
229 #[must_use]
231 fn query<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySet<E>;
232
233 #[must_use]
235 fn query_result_iterator<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySetIterator<E>;
236
237 #[must_use]
239 fn match_entity<E: Entity, Q: Query<E>>(&self, entity_id: EntityId<E>, query: Q) -> bool;
240
241 fn filter_entities<E: Entity, Q: Query<E>>(&self, entities: &mut Vec<EntityId<E>>, query: Q);
243}
244
245impl ContextEntitiesExt for Context {
246 fn add_entity<E: Entity, PL: PropertyInitializationList<E>>(
247 &mut self,
248 property_list: PL,
249 ) -> Result<EntityId<E>, IxaError> {
250 PL::validate()?;
252
253 if !PL::contains_required_properties() {
255 return Err(IxaError::MissingRequiredInitializationProperties);
256 }
257
258 let new_entity_id = self.entity_store.new_entity_id::<E>();
260
261 property_list.set_values_for_new_entity(
264 new_entity_id,
265 self.entity_store.get_property_store_mut::<E>(),
266 );
267
268 let context_ptr: *const Context = self;
270 let property_store = self.entity_store.get_property_store_mut::<E>();
271 unsafe {
274 property_store.index_unindexed_entities_for_all_properties(&*context_ptr);
275 }
276
277 self.emit_event(EntityCreatedEvent::<E>::new(new_entity_id));
279
280 Ok(new_entity_id)
281 }
282
283 fn get_property<E: Entity, P: Property<E>>(&self, entity_id: EntityId<E>) -> P {
284 if P::is_derived() {
285 P::compute_derived(self, entity_id)
286 } else {
287 let property_store = self.get_property_value_store::<E, P>();
288 property_store.get(entity_id)
289 }
290 }
291
292 fn set_property<E: Entity, P: Property<E>>(
293 &mut self,
294 entity_id: EntityId<E>,
295 property_value: P,
296 ) {
297 debug_assert!(!P::is_derived(), "cannot set a derived property");
298
299 let mut dependents: SmallVec<[PartialPropertyChangeEventBox; 5]> = SmallVec::new();
332
333 {
335 let property_store = self.entity_store.get_property_store::<E>();
336
337 if property_store.should_create_partial_property_change(P::id(), self) {
339 dependents.push(property_store.create_partial_property_change(
340 P::id(),
341 entity_id,
342 self,
343 ));
344 }
345 for dependent_idx in P::dependents() {
347 if property_store.should_create_partial_property_change(*dependent_idx, self) {
348 dependents.push(property_store.create_partial_property_change(
349 *dependent_idx,
350 entity_id,
351 self,
352 ));
353 }
354 }
355 }
356
357 let property_value_store = self.get_property_value_store_mut::<E, P>();
359 property_value_store.set(entity_id, property_value);
360
361 for mut dependent in dependents {
364 dependent.emit_in_context(self)
365 }
366 }
367
368 fn index_property<E: Entity, P: IndexableProperty<E>>(&mut self) {
369 let property_id = P::id();
370 let context_ptr: *const Context = self;
371 let property_store = self.entity_store.get_property_store_mut::<E>();
372 property_store.set_property_indexed::<P>(PropertyIndexType::FullIndex);
373 unsafe {
376 property_store.index_unindexed_entities_for_property_id(&*context_ptr, property_id);
377 }
378 }
379
380 fn index_property_counts<E: Entity, P: IndexableProperty<E>>(&mut self) {
381 let property_store = self.entity_store.get_property_store_mut::<E>();
382 let current_index_type = property_store.get::<P>().index_type();
383 if current_index_type != PropertyIndexType::FullIndex {
384 property_store.set_property_indexed::<P>(PropertyIndexType::ValueCountIndex);
385 }
386 }
387
388 fn track_periodic_value_change_counts<E, PL, P, F>(&mut self, period: f64, handler: F)
389 where
390 E: Entity,
391 PL: PropertyList<E> + Eq + Hash,
392 P: IndexableProperty<E>,
393 F: Fn(&mut Context, &mut StratifiedValueChangeCounter<E, PL, P>) + 'static,
394 {
395 assert!(
396 period > 0.0 && !period.is_nan() && !period.is_infinite(),
397 "Period must be greater than 0"
398 );
399 let start_time = self.get_start_time().unwrap_or(0.0);
400 self.add_plan_with_phase(
401 start_time,
402 move |context| {
403 let counter_id = context
406 .entity_store
407 .get_property_store_mut::<E>()
408 .create_value_change_counter::<PL, P>();
409
410 context.add_plan_with_phase(
413 context.get_current_time(),
414 move |context| {
415 handle_periodic_value_change_count_event::<E, PL, P, F>(
416 context, period, counter_id, handler,
417 );
418 },
419 ExecutionPhase::Last,
420 );
421 },
422 ExecutionPhase::First,
423 );
424 }
425
426 #[cfg(test)]
427 fn is_property_indexed<E: Entity, P: Property<E>>(&self) -> bool {
428 let property_store = self.entity_store.get_property_store::<E>();
429 property_store.is_property_indexed::<P>()
430 }
431
432 fn with_query_results<'a, E: Entity, Q: Query<E>>(
433 &'a self,
434 query: Q,
435 callback: &mut dyn FnMut(EntitySet<'a, E>),
436 ) {
437 if let Some(multi_property_id) = query.multi_property_id() {
442 let property_store = self.entity_store.get_property_store::<E>();
443 let query_parts = query.query_parts();
444 let lookup_result = property_store
445 .get_index_set_for_query_parts(multi_property_id, query_parts.as_ref());
446 match lookup_result {
447 IndexSetResult::Set(people_set) => {
448 callback(EntitySet::from_source(SourceSet::IndexSet(people_set)));
449 return;
450 }
451 IndexSetResult::Empty => {
452 callback(EntitySet::empty());
453 return;
454 }
455 IndexSetResult::Unsupported => {}
456 }
457 }
459
460 if query.is_empty_query() {
462 warn!("Called Context::with_query_results() with an empty query. Prefer Context::get_entity_iterator::<E>() for working with the entire population.");
463 callback(EntitySet::from_source(SourceSet::PopulationRange(
464 0..self.get_entity_count::<E>(),
465 )));
466 return;
467 }
468
469 warn!("Called Context::with_query_results() with an unindexed query. It's almost always better to use Context::query_result_iterator() for unindexed queries.");
471
472 callback(self.query(query));
474 }
475
476 fn query_entity_count<E: Entity, Q: Query<E>>(&self, query: Q) -> usize {
477 if let Some(multi_property_id) = query.multi_property_id() {
481 let property_store = self.entity_store.get_property_store::<E>();
482 let query_parts = query.query_parts();
483 let lookup_result = property_store
484 .get_index_count_for_query_parts(multi_property_id, query_parts.as_ref());
485 match lookup_result {
486 IndexCountResult::Count(count) => return count,
487 IndexCountResult::Unsupported => {}
488 }
489 }
491
492 self.query_result_iterator(query).count()
493 }
494 fn sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> Option<EntityId<E>>
495 where
496 E: Entity,
497 Q: Query<E>,
498 R: RngId + 'static,
499 R::RngType: Rng,
500 {
501 if query.is_empty_query() {
502 let population = self.get_entity_count::<E>();
503 return self.sample(rng_id, move |rng| {
504 if population == 0 {
505 warn!("Requested a sample entity from an empty population");
506 return None;
507 }
508 let index = if population <= u32::MAX as usize {
509 rng.random_range(0..population as u32) as usize
510 } else {
511 rng.random_range(0..population)
512 };
513 Some(EntityId::new(index))
514 });
515 }
516
517 let query_result = self.query(query);
518 self.sample(rng_id, move |rng| query_result.sample_entity(rng))
519 }
520
521 fn count_and_sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> (usize, Option<EntityId<E>>)
522 where
523 E: Entity,
524 Q: Query<E>,
525 R: RngId + 'static,
526 R::RngType: Rng,
527 {
528 if query.is_empty_query() {
529 let population = self.get_entity_count::<E>();
530 return self.sample(rng_id, move |rng| {
531 if population == 0 {
532 return (0, None);
533 }
534 let index = if population <= u32::MAX as usize {
535 rng.random_range(0..population as u32) as usize
536 } else {
537 rng.random_range(0..population)
538 };
539 (population, Some(EntityId::new(index)))
540 });
541 }
542
543 let query_result = self.query(query);
544 self.sample(rng_id, move |rng| query_result.count_and_sample_entity(rng))
545 }
546
547 fn sample_entities<E, Q, R>(&self, rng_id: R, query: Q, n: usize) -> Vec<EntityId<E>>
548 where
549 E: Entity,
550 Q: Query<E>,
551 R: RngId + 'static,
552 R::RngType: Rng,
553 {
554 if query.is_empty_query() {
555 let population = self.get_entity_count::<E>();
556 return self.sample(rng_id, move |rng| {
557 if population == 0 {
558 warn!("Requested a sample of entities from an empty population");
559 return vec![];
560 }
561 if n >= population {
562 return PopulationIterator::<E>::new(population).collect();
563 }
564 sample_multiple_from_known_length(rng, PopulationIterator::<E>::new(population), n)
565 });
566 }
567
568 let query_result = self.query(query);
569 self.sample(rng_id, move |rng| query_result.sample_entities(rng, n))
570 }
571
572 fn get_entity_count<E: Entity>(&self) -> usize {
573 self.entity_store.get_entity_count::<E>()
574 }
575
576 fn get_entity_iterator<E: Entity>(&self) -> PopulationIterator<E> {
577 self.entity_store.get_entity_iterator::<E>()
578 }
579
580 fn query<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySet<E> {
581 query.new_query_result(self)
582 }
583
584 fn query_result_iterator<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySetIterator<E> {
585 query.new_query_result_iterator(self)
586 }
587
588 fn match_entity<E: Entity, Q: Query<E>>(&self, entity_id: EntityId<E>, query: Q) -> bool {
589 query.match_entity(entity_id, self)
590 }
591
592 fn filter_entities<E: Entity, Q: Query<E>>(&self, entities: &mut Vec<EntityId<E>>, query: Q) {
593 query.filter_entities(entities, self);
594 }
595}
596
597#[cfg(test)]
598mod tests {
599 use std::cell::RefCell;
600 use std::rc::Rc;
601
602 use super::*;
603 use crate::entity::query::QueryInternal;
604 use crate::hashing::IndexSet;
605 use crate::prelude::PropertyChangeEvent;
606 use crate::{
607 define_derived_property, define_entity, define_multi_property, define_property, define_rng,
608 impl_property, with,
609 };
610
611 define_entity!(Animal);
612 define_property!(struct Legs(u8), Animal, default_const = Legs(4));
613 define_rng!(EntityContextTestRng);
614
615 define_entity!(Person);
616
617 define_property!(struct Age(u8), Person);
618
619 #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
620 struct CounterValue(u8);
621 impl_property!(CounterValue, Person, default_const = CounterValue(0));
622
623 #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
624 struct CounterStratum(bool);
625 impl_property!(
626 CounterStratum,
627 Person,
628 default_const = CounterStratum(false)
629 );
630
631 define_property!(
632 enum InfectionStatus {
633 Susceptible,
634 Infected,
635 Recovered,
636 },
637 Person,
638 default_const = InfectionStatus::Susceptible
639 );
640
641 define_property!(
642 struct Vaccinated(bool),
643 Person,
644 default_const = Vaccinated(false)
645 );
646
647 define_derived_property!(
648 enum AgeGroup {
649 Child,
650 Adult,
651 Senior,
652 },
653 Person,
654 [Age],
655 |age| {
656 if age.0 <= 18 {
657 AgeGroup::Child
658 } else if age.0 <= 65 {
659 AgeGroup::Adult
660 } else {
661 AgeGroup::Senior
662 }
663 }
664 );
665
666 define_derived_property!(
667 enum RiskLevel {
668 Low,
669 Medium,
670 High,
671 },
672 Person,
673 [AgeGroup, Vaccinated, InfectionStatus],
674 |age_group, vaccinated, infection_status| {
675 match (age_group, vaccinated, infection_status) {
676 (AgeGroup::Senior, Vaccinated(false), InfectionStatus::Susceptible) => {
677 RiskLevel::High
678 }
679 (_, Vaccinated(false), InfectionStatus::Susceptible) => RiskLevel::Medium,
680 _ => RiskLevel::Low,
681 }
682 }
683 );
684
685 define_property!(struct IsRunner(bool), Person, default_const = IsRunner(false));
699 define_property!(struct IsSwimmer(bool), Person, default_const = IsSwimmer(false));
700 define_derived_property!(
701 struct AdultRunner(bool),
702 Person,
703 [AgeGroup, IsRunner],
704 | age_group, is_runner | {
705 AdultRunner(
706 age_group == AgeGroup::Adult
707 && is_runner.0
708 )
709 }
710 );
711 define_derived_property!(
712 struct AdultSwimmer(bool),
713 Person,
714 [AgeGroup, IsSwimmer],
715 | age_group, is_swimmer | {
716 AdultSwimmer(
717 age_group == AgeGroup::Adult
718 && is_swimmer.0
719 )
720 }
721 );
722 define_derived_property!(
723 struct AdultAthlete(bool),
724 Person,
725 [AdultSwimmer, AdultRunner],
726 | adult_swimmer, adult_runner | {
727 AdultAthlete(
728 adult_swimmer.0 || adult_runner.0
729 )
730 }
731 );
732
733 #[test]
734 fn add_and_count_entities() {
735 let mut context = Context::new();
736
737 let _person1 = context
738 .add_entity(with!(
739 Person,
740 Age(12),
741 InfectionStatus::Susceptible,
742 Vaccinated(true)
743 ))
744 .unwrap();
745 assert_eq!(context.get_entity_count::<Person>(), 1);
746
747 let _person2 = context
748 .add_entity(with!(Person, Age(34), Vaccinated(true)))
749 .unwrap();
750 assert_eq!(context.get_entity_count::<Person>(), 2);
751
752 let _person3 = context.add_entity(with!(Person, Age(120))).unwrap();
754 assert_eq!(context.get_entity_count::<Person>(), 3);
755 }
756
757 #[test]
758 fn add_entity_with_zst() {
759 let mut context = Context::new();
760 let animal = context.add_entity(Animal).unwrap();
761 assert_eq!(context.get_entity_count::<Animal>(), 1);
762 assert_eq!(context.get_property::<Animal, Legs>(animal), Legs(4));
763 }
764
765 #[derive(Copy, Clone, Debug)]
767 enum IndexMode {
768 Unindexed,
769 FullIndex,
770 ValueCountIndex,
771 }
772
773 fn setup_context_for_index_tests(index_mode: IndexMode) -> (Context, Age, Age) {
775 let mut context = Context::new();
776 match index_mode {
777 IndexMode::Unindexed => {}
778 IndexMode::FullIndex => context.index_property::<Person, Age>(),
779 IndexMode::ValueCountIndex => context.index_property_counts::<Person, Age>(),
780 }
781
782 let existing_value = Age(12);
783 let missing_value = Age(99);
784
785 let _ = context.add_entity(with!(Person, existing_value)).unwrap();
786 let _ = context.add_entity(with!(Person, existing_value)).unwrap();
787
788 (context, existing_value, missing_value)
789 }
790
791 #[test]
792 fn query_results_respect_index_modes() {
793 let modes = [
794 IndexMode::Unindexed,
795 IndexMode::FullIndex,
796 IndexMode::ValueCountIndex,
797 ];
798
799 for mode in modes {
800 let (context, existing_value, missing_value) = setup_context_for_index_tests(mode);
801
802 let mut existing_len = 0;
803 context.with_query_results(with!(Person, existing_value), &mut |people_set| {
804 existing_len = people_set.into_iter().count();
805 });
806 assert_eq!(existing_len, 2, "Wrong length for {mode:?}");
807
808 let mut missing_len = 0;
809 context.with_query_results(with!(Person, missing_value), &mut |people_set| {
810 missing_len = people_set.into_iter().count();
811 });
812 assert_eq!(missing_len, 0);
813
814 let existing_count = context
815 .query_result_iterator(with!(Person, existing_value))
816 .count();
817 assert_eq!(existing_count, 2);
818
819 let missing_count = context
820 .query_result_iterator(with!(Person, missing_value))
821 .count();
822 assert_eq!(missing_count, 0);
823
824 assert_eq!(context.query_entity_count(with!(Person, existing_value)), 2);
825 assert_eq!(context.query_entity_count(with!(Person, missing_value)), 0);
826 }
827 }
828
829 #[test]
830 fn add_an_entity_without_required_properties() {
831 let mut context = Context::new();
832 let result = context.add_entity(with!(
833 Person,
834 InfectionStatus::Susceptible,
835 Vaccinated(true)
836 ));
837
838 assert!(matches!(
839 result,
840 Err(crate::IxaError::MissingRequiredInitializationProperties)
841 ));
842 }
843
844 #[test]
845 fn new_entities_have_default_values() {
846 let mut context = Context::new();
847
848 let person = context.add_entity(with!(Person, Age(25))).unwrap();
850
851 let age: Age = context.get_property(person);
853 assert_eq!(age, Age(25));
854 let infection_status: InfectionStatus = context.get_property(person);
855 assert_eq!(infection_status, InfectionStatus::Susceptible);
856 let vaccinated: Vaccinated = context.get_property(person);
857 assert_eq!(vaccinated, Vaccinated(false));
858
859 context.set_property(person, Age(26));
861 context.set_property(person, InfectionStatus::Infected);
862 context.set_property(person, Vaccinated(true));
863
864 let age: Age = context.get_property(person);
866 assert_eq!(age, Age(26));
867 let infection_status: InfectionStatus = context.get_property(person);
868 assert_eq!(infection_status, InfectionStatus::Infected);
869 let vaccinated: Vaccinated = context.get_property(person);
870 assert_eq!(vaccinated, Vaccinated(true));
871 }
872
873 #[test]
874 fn get_and_set_property_explicit() {
875 let mut context = Context::new();
876
877 let person = context
879 .add_entity(with!(
880 Person,
881 Age(25),
882 InfectionStatus::Recovered,
883 Vaccinated(true)
884 ))
885 .unwrap();
886
887 let age: Age = context.get_property(person);
889 assert_eq!(age, Age(25));
890 let infection_status: InfectionStatus = context.get_property(person);
891 assert_eq!(infection_status, InfectionStatus::Recovered);
892 let vaccinated: Vaccinated = context.get_property(person);
893 assert_eq!(vaccinated, Vaccinated(true));
894
895 context.set_property(person, Age(26));
897 context.set_property(person, InfectionStatus::Infected);
898 context.set_property(person, Vaccinated(false));
899
900 let age: Age = context.get_property(person);
902 assert_eq!(age, Age(26));
903 let infection_status: InfectionStatus = context.get_property(person);
904 assert_eq!(infection_status, InfectionStatus::Infected);
905 let vaccinated: Vaccinated = context.get_property(person);
906 assert_eq!(vaccinated, Vaccinated(false));
907 }
908
909 #[test]
910 fn count_entities() {
911 let mut context = Context::new();
912
913 assert_eq!(context.get_entity_count::<Animal>(), 0);
914 assert_eq!(context.get_entity_count::<Person>(), 0);
915
916 for _ in 0..7 {
918 let _: PersonId = context.add_entity(with!(Person, Age(25))).unwrap();
919 }
920 for _ in 0..5 {
921 let _: AnimalId = context.add_entity(with!(Animal, Legs(2))).unwrap();
922 }
923
924 assert_eq!(context.get_entity_count::<Animal>(), 5);
925 assert_eq!(context.get_entity_count::<Person>(), 7);
926
927 let _: PersonId = context.add_entity(with!(Person, Age(30))).unwrap();
928 let _: AnimalId = context.add_entity(with!(Animal, Legs(8))).unwrap();
929
930 assert_eq!(context.get_entity_count::<Animal>(), 6);
931 assert_eq!(context.get_entity_count::<Person>(), 8);
932 }
933
934 #[test]
935 fn count_and_sample_entity_empty_query_fast_path() {
936 let mut context = Context::new();
937 context.init_random(42);
938 for age in [10u8, 20, 30] {
939 let _: PersonId = context.add_entity(with!(Person, Age(age))).unwrap();
940 }
941
942 let (count, sampled) =
943 context.count_and_sample_entity::<Person, _, _>(EntityContextTestRng, Person);
944 assert_eq!(count, 3);
945 assert!(sampled.is_some());
946 }
947
948 #[test]
949 fn count_and_sample_entity_unindexed_derived_query() {
950 let mut context = Context::new();
951 context.init_random(43);
952 for age in [10u8, 20, 30, 80] {
953 let _: PersonId = context.add_entity(with!(Person, Age(age))).unwrap();
954 }
955
956 let query = with!(Person, AgeGroup::Adult);
957 let expected_count = context.query_entity_count(query);
958 let (count, sampled) = context.count_and_sample_entity(EntityContextTestRng, query);
959 assert_eq!(count, expected_count);
960 assert_eq!(sampled.is_some(), count > 0);
961 if let Some(entity_id) = sampled {
962 assert!(context.match_entity(entity_id, query));
963 }
964 }
965
966 #[test]
967 fn get_derived_property_multiple_deps() {
968 let mut context = Context::new();
969
970 let expected_high_id: PersonId = context
971 .add_entity(with!(
972 Person,
973 Age(77),
974 Vaccinated(false),
975 InfectionStatus::Susceptible
976 ))
977 .unwrap();
978 let expected_med_id: PersonId = context
979 .add_entity(with!(
980 Person,
981 Age(30),
982 Vaccinated(false),
983 InfectionStatus::Susceptible
984 ))
985 .unwrap();
986 let expected_low_id: PersonId = context
987 .add_entity(with!(
988 Person,
989 Age(3),
990 Vaccinated(true),
991 InfectionStatus::Recovered
992 ))
993 .unwrap();
994
995 let actual_high: RiskLevel = context.get_property(expected_high_id);
996 assert_eq!(actual_high, RiskLevel::High);
997 let actual_med: RiskLevel = context.get_property(expected_med_id);
998 assert_eq!(actual_med, RiskLevel::Medium);
999 let actual_low: RiskLevel = context.get_property(expected_low_id);
1000 assert_eq!(actual_low, RiskLevel::Low);
1001 }
1002
1003 #[test]
1004 fn listen_to_derived_property_change_event() {
1005 let mut context = Context::new();
1006
1007 let expected_high_id = PersonId::new(0);
1008
1009 let risk_flag = Rc::new(RefCell::new(0));
1012 let risk_flag_clone = risk_flag.clone();
1013 context.subscribe_to_event(
1014 move |_context, event: PropertyChangeEvent<Person, RiskLevel>| {
1015 assert_eq!(event.entity_id, expected_high_id);
1016 assert_eq!(event.previous, RiskLevel::High);
1017 assert_eq!(event.current, RiskLevel::Medium);
1018 *risk_flag_clone.borrow_mut() += 1;
1019 },
1020 );
1021 let age_group_flag = Rc::new(RefCell::new(0));
1023 let age_group_flag_clone = age_group_flag.clone();
1024 context.subscribe_to_event(
1025 move |_context, event: PropertyChangeEvent<Person, AgeGroup>| {
1026 assert_eq!(event.entity_id, expected_high_id);
1027 assert_eq!(event.previous, AgeGroup::Senior);
1028 assert_eq!(event.current, AgeGroup::Adult);
1029 *age_group_flag_clone.borrow_mut() += 1;
1030 },
1031 );
1032
1033 let expected_high_id: PersonId = context
1035 .add_entity(with!(
1036 Person,
1037 Age(77),
1038 Vaccinated(false),
1039 InfectionStatus::Susceptible
1040 ))
1041 .unwrap();
1042
1043 context.set_property(expected_high_id, Age(20));
1045
1046 context.execute();
1048 assert_eq!(*risk_flag.borrow(), 1);
1050 assert_eq!(*age_group_flag.borrow(), 1);
1051 }
1052
1053 #[test]
1073 fn observe_diamond_property_change() {
1074 let mut context = Context::new();
1075 let person = context
1076 .add_entity(with!(Person, Age(17), IsSwimmer(true)))
1077 .unwrap();
1078
1079 let is_adult_athlete: AdultAthlete = context.get_property(person);
1080 assert!(!is_adult_athlete.0);
1081
1082 let flag = Rc::new(RefCell::new(0));
1083 let flag_clone = flag.clone();
1084 context.subscribe_to_event(
1085 move |_context, event: PropertyChangeEvent<Person, AdultAthlete>| {
1086 assert_eq!(event.entity_id, person);
1087 assert_eq!(event.previous, AdultAthlete(false));
1088 assert_eq!(event.current, AdultAthlete(true));
1089 *flag_clone.borrow_mut() += 1;
1090 },
1091 );
1092
1093 context.set_property(person, Age(20));
1094 let is_adult_athlete: AdultAthlete = context.get_property(person);
1096 assert!(is_adult_athlete.0);
1097
1098 context.execute();
1100 assert_eq!(*flag.borrow(), 1);
1102 }
1103
1104 define_multi_property!(Person, (InfectionStatus, Vaccinated));
1107 define_multi_property!(Person, (Vaccinated, InfectionStatus));
1108
1109 #[test]
1110 fn with_query_results_finds_multi_index() {
1111 use crate::rand::rngs::SmallRng;
1112 use crate::rand::seq::IndexedRandom;
1113 use crate::rand::SeedableRng;
1114
1115 let mut rng = SmallRng::seed_from_u64(42);
1116 let mut context = Context::new();
1117
1118 for _ in 0..10_000usize {
1119 let infection_status = *[
1120 InfectionStatus::Susceptible,
1121 InfectionStatus::Infected,
1122 InfectionStatus::Recovered,
1123 ]
1124 .choose(&mut rng)
1125 .unwrap();
1126 let vaccination_status: bool = rng.random_bool(0.5);
1127 let age: u8 = rng.random_range(0..100);
1128 context
1129 .add_entity(with!(
1130 Person,
1131 Age(age),
1132 infection_status,
1133 Vaccinated(vaccination_status)
1134 ))
1135 .unwrap();
1136 }
1137 context.index_property::<Person, InfectionStatusVaccinated>();
1138 let _ = context.query_result_iterator(with!(
1140 Person,
1141 InfectionStatus::Susceptible,
1142 Vaccinated(true)
1143 ));
1144
1145 let mut result_entities: IndexSet<EntityId<Person>> = IndexSet::default();
1147 context.with_query_results(
1148 with!(Person, InfectionStatus::Susceptible, Vaccinated(true)),
1149 &mut |result_set| {
1150 result_entities = result_set.into_iter().collect::<IndexSet<_>>();
1151 },
1152 );
1153
1154 assert_ne!(
1157 InfectionStatusVaccinated::id(),
1158 VaccinatedInfectionStatus::id()
1159 );
1160 assert_ne!(
1161 InfectionStatusVaccinated::type_id(),
1162 VaccinatedInfectionStatus::type_id()
1163 );
1164 assert_eq!(
1165 InfectionStatusVaccinated::id(),
1166 (InfectionStatus::Susceptible, Vaccinated(true))
1167 .multi_property_id()
1168 .unwrap()
1169 );
1170
1171 let property_id = InfectionStatusVaccinated::id();
1173
1174 let property_store = context.entity_store.get_property_store::<Person>();
1175 let query = (InfectionStatus::Susceptible, Vaccinated(true));
1176 let query_parts = query.query_parts();
1177 let bucket =
1178 match property_store.get_index_set_for_query_parts(property_id, query_parts.as_ref()) {
1179 IndexSetResult::Set(bucket) => bucket,
1180 other => panic!("expected indexed query bucket, found {other:?}"),
1181 };
1182
1183 let expected_entities = bucket.iter().copied().collect::<IndexSet<_>>();
1184 assert_eq!(expected_entities, result_entities);
1185 }
1186
1187 #[test]
1188 fn query_returns_entity_set_and_query_result_iterator_remains_compatible() {
1189 let mut context = Context::new();
1190 let p1 = context
1191 .add_entity(with!(
1192 Person,
1193 Age(21),
1194 InfectionStatus::Susceptible,
1195 Vaccinated(true)
1196 ))
1197 .unwrap();
1198 let _p2 = context
1199 .add_entity(with!(
1200 Person,
1201 Age(22),
1202 InfectionStatus::Susceptible,
1203 Vaccinated(false)
1204 ))
1205 .unwrap();
1206 let p3 = context
1207 .add_entity(with!(
1208 Person,
1209 Age(23),
1210 InfectionStatus::Infected,
1211 Vaccinated(true)
1212 ))
1213 .unwrap();
1214
1215 let query = with!(Person, Vaccinated(true));
1216
1217 let from_set = context
1218 .query::<Person, _>(query)
1219 .into_iter()
1220 .collect::<IndexSet<_>>();
1221 let from_iterator = context
1222 .query_result_iterator(query)
1223 .collect::<IndexSet<_>>();
1224
1225 assert_eq!(from_set, from_iterator);
1226 assert!(from_set.contains(&p1));
1227 assert!(from_set.contains(&p3));
1228 assert_eq!(from_set.len(), 2);
1229 }
1230
1231 #[test]
1232 fn set_property_correctly_maintains_index() {
1233 let mut context = Context::new();
1234 context.index_property::<Person, InfectionStatus>();
1235 context.index_property::<Person, AgeGroup>();
1236
1237 let person1 = context.add_entity(with!(Person, Age(22))).unwrap();
1238 let person2 = context.add_entity(with!(Person, Age(22))).unwrap();
1239 for _ in 0..4 {
1240 let _: PersonId = context.add_entity(with!(Person, Age(22))).unwrap();
1241 }
1242
1243 assert_eq!(
1245 context.query_entity_count(with!(Person, InfectionStatus::Susceptible)),
1246 6
1247 );
1248 assert_eq!(
1249 context.query_entity_count(with!(Person, InfectionStatus::Infected)),
1250 0
1251 );
1252 assert_eq!(
1253 context.query_entity_count(with!(Person, InfectionStatus::Recovered)),
1254 0
1255 );
1256
1257 context.set_property(person1, InfectionStatus::Infected);
1258
1259 assert_eq!(
1260 context.query_entity_count(with!(Person, InfectionStatus::Susceptible)),
1261 5
1262 );
1263 assert_eq!(
1264 context.query_entity_count(with!(Person, InfectionStatus::Infected)),
1265 1
1266 );
1267 assert_eq!(
1268 context.query_entity_count(with!(Person, InfectionStatus::Recovered)),
1269 0
1270 );
1271
1272 context.set_property(person1, InfectionStatus::Recovered);
1273
1274 assert_eq!(
1275 context.query_entity_count(with!(Person, InfectionStatus::Susceptible)),
1276 5
1277 );
1278 assert_eq!(
1279 context.query_entity_count(with!(Person, InfectionStatus::Infected)),
1280 0
1281 );
1282 assert_eq!(
1283 context.query_entity_count(with!(Person, InfectionStatus::Recovered)),
1284 1
1285 );
1286
1287 assert_eq!(
1289 context.query_entity_count(with!(Person, AgeGroup::Child)),
1290 0
1291 );
1292 assert_eq!(
1293 context.query_entity_count(with!(Person, AgeGroup::Adult)),
1294 6
1295 );
1296 assert_eq!(
1297 context.query_entity_count(with!(Person, AgeGroup::Senior)),
1298 0
1299 );
1300
1301 context.set_property(person2, Age(12));
1302
1303 assert_eq!(
1304 context.query_entity_count(with!(Person, AgeGroup::Child)),
1305 1
1306 );
1307 assert_eq!(
1308 context.query_entity_count(with!(Person, AgeGroup::Adult)),
1309 5
1310 );
1311 assert_eq!(
1312 context.query_entity_count(with!(Person, AgeGroup::Senior)),
1313 0
1314 );
1315
1316 context.set_property(person1, Age(75));
1317
1318 assert_eq!(
1319 context.query_entity_count(with!(Person, AgeGroup::Child)),
1320 1
1321 );
1322 assert_eq!(
1323 context.query_entity_count(with!(Person, AgeGroup::Adult)),
1324 4
1325 );
1326 assert_eq!(
1327 context.query_entity_count(with!(Person, AgeGroup::Senior)),
1328 1
1329 );
1330
1331 context.set_property(person2, Age(77));
1332
1333 assert_eq!(
1334 context.query_entity_count(with!(Person, AgeGroup::Child)),
1335 0
1336 );
1337 assert_eq!(
1338 context.query_entity_count(with!(Person, AgeGroup::Adult)),
1339 4
1340 );
1341 assert_eq!(
1342 context.query_entity_count(with!(Person, AgeGroup::Senior)),
1343 2
1344 );
1345 }
1346
1347 #[test]
1348 fn query_unindexed_default_properties() {
1349 let mut context = Context::new();
1350
1351 for idx in 0..10 {
1353 if idx % 2 == 0 {
1354 context.add_entity(with!(Person, Age(22))).unwrap();
1355 } else {
1356 context
1357 .add_entity(with!(Person, Age(22), InfectionStatus::Recovered))
1358 .unwrap();
1359 }
1360 }
1361 for _ in 0..10 {
1363 let _: PersonId = context.add_entity(with!(Person, Age(22))).unwrap();
1364 }
1365
1366 assert_eq!(
1367 context.query_entity_count(with!(Person, InfectionStatus::Recovered)),
1368 5
1369 );
1370 assert_eq!(
1371 context.query_entity_count(with!(Person, InfectionStatus::Susceptible)),
1372 15
1373 );
1374 }
1375
1376 #[test]
1377 fn query_unindexed_derived_properties() {
1378 let mut context = Context::new();
1379
1380 for _ in 0..10 {
1381 let _: PersonId = context.add_entity(with!(Person, Age(22))).unwrap();
1382 }
1383
1384 assert_eq!(
1385 context.query_entity_count(with!(Person, AdultAthlete(false))),
1386 10
1387 );
1388 }
1389
1390 #[test]
1391 fn track_periodic_value_change_counts_uses_distinct_counters() {
1392 let mut context = Context::new();
1393
1394 context.track_periodic_value_change_counts::<Person, (CounterStratum,), CounterValue, _>(
1395 1.0,
1396 move |_context, _counter| {},
1397 );
1398
1399 context.track_periodic_value_change_counts::<Person, (CounterStratum,), CounterValue, _>(
1400 1.0,
1401 move |_context, _counter| {},
1402 );
1403
1404 let property_value_store = context.get_property_value_store::<Person, CounterValue>();
1405 assert_eq!(property_value_store.value_change_counters.len(), 0);
1406
1407 context.add_plan(0.5, Context::shutdown);
1408 context.execute();
1409
1410 let property_value_store = context.get_property_value_store::<Person, CounterValue>();
1411 assert_eq!(property_value_store.value_change_counters.len(), 2);
1412 }
1413
1414 #[test]
1415 fn value_change_counter_updates_on_true_transitions() {
1416 let mut context = Context::new();
1417 let observed = Rc::new(RefCell::new(Vec::<(usize, usize)>::new()));
1418 let observed_clone = observed.clone();
1419
1420 context.track_periodic_value_change_counts(1.0, move |_context, counter| {
1421 observed_clone.borrow_mut().push((
1422 counter.get_count((CounterStratum(true),), CounterValue(1)),
1423 counter.get_count((CounterStratum(true),), CounterValue(2)),
1424 ));
1425 });
1426
1427 let person = context
1428 .add_entity(with!(
1429 Person,
1430 Age(10),
1431 CounterValue(0),
1432 CounterStratum(true)
1433 ))
1434 .unwrap();
1435 context.add_plan(0.1, move |context| {
1436 context.set_property(person, CounterValue(1));
1437 context.set_property(person, CounterValue(1));
1438 context.set_property(person, CounterValue(2));
1439 });
1440
1441 context.execute();
1442 assert_eq!(*observed.borrow(), vec![(0, 0), (1, 1)]);
1443 }
1444
1445 #[test]
1446 fn periodic_value_change_counts_report_and_clear() {
1447 let mut context = Context::new();
1448 let person = context
1449 .add_entity(with!(
1450 Person,
1451 Age(10),
1452 CounterValue(0),
1453 CounterStratum(true)
1454 ))
1455 .unwrap();
1456
1457 let observed = Rc::new(RefCell::new(Vec::<usize>::new()));
1458 let observed_clone = observed.clone();
1459
1460 context.track_periodic_value_change_counts(1.0, move |_context, counter| {
1461 observed_clone
1462 .borrow_mut()
1463 .push(counter.get_count((CounterStratum(true),), CounterValue(1)));
1464 });
1465
1466 context.add_plan(0.5, move |context| {
1467 context.set_property(person, CounterValue(1));
1468 });
1469 context.add_plan(1.5, move |context| {
1470 context.set_property(person, CounterValue(1));
1471 });
1472
1473 context.execute();
1474 assert_eq!(*observed.borrow(), vec![0, 1, 0]);
1475 }
1476
1477 #[test]
1478 fn periodic_value_change_counts_start_time_and_phase_behavior() {
1479 let mut context = Context::new();
1480 context.set_start_time(-2.0);
1481
1482 let person = context
1483 .add_entity(with!(
1484 Person,
1485 Age(10),
1486 CounterValue(0),
1487 CounterStratum(true)
1488 ))
1489 .unwrap();
1490
1491 let observed_times = Rc::new(RefCell::new(Vec::<f64>::new()));
1492 let observed_counts = Rc::new(RefCell::new(Vec::<usize>::new()));
1493 let observed_times_clone = observed_times.clone();
1494 let observed_counts_clone = observed_counts.clone();
1495
1496 context.track_periodic_value_change_counts(1.0, move |context, counter| {
1497 observed_times_clone
1498 .borrow_mut()
1499 .push(context.get_current_time());
1500 observed_counts_clone
1501 .borrow_mut()
1502 .push(counter.get_count((CounterStratum(true),), CounterValue(1)));
1503 });
1504
1505 context.add_plan_with_phase(
1506 -2.0,
1507 move |context| {
1508 context.set_property(person, CounterValue(1));
1509 },
1510 ExecutionPhase::Normal,
1511 );
1512 context.add_plan(0.0, |_| {});
1513
1514 context.execute();
1515
1516 assert_eq!(*observed_times.borrow(), vec![-2.0, -1.0, 0.0]);
1517 assert_eq!(*observed_counts.borrow(), vec![1, 0, 0]);
1518 }
1519}