1use std::any::{Any, TypeId};
2use std::hash::Hash;
3
4use crate::entity::entity_set::{EntitySet, EntitySetIterator, SourceSet};
5use crate::entity::events::{EntityCreatedEvent, PartialPropertyChangeEvent};
6use crate::entity::index::{IndexCountResult, IndexSetResult, PropertyIndexType};
7use crate::entity::property::Property;
8use crate::entity::property_list::PropertyList;
9use crate::entity::query::Query;
10use crate::entity::value_change_counter::StratifiedValueChangeCounter;
11use crate::entity::{Entity, EntityId, PopulationIterator};
12use crate::rand::Rng;
13use crate::random::sample_multiple_from_known_length;
14use crate::{warn, Context, ContextRandomExt, ExecutionPhase, IxaError, RngId};
15
16fn handle_periodic_value_change_count_event<E, PL, P, F>(
17 context: &mut Context,
18 period: f64,
19 counter_id: usize,
20 handler: F,
21) where
22 E: Entity,
23 PL: PropertyList<E> + Eq + Hash,
24 P: Property<E> + Eq + Hash,
25 F: Fn(&mut Context, &mut StratifiedValueChangeCounter<E, PL, P>) + 'static,
26{
27 let mut counter = {
28 let property_value_store = context.get_property_value_store_mut::<E, P>();
29 let slot = property_value_store
30 .value_change_counters
31 .get_mut(counter_id)
32 .unwrap_or_else(|| {
33 panic!(
34 "No value change counter found for property {} with counter_id {}",
35 P::name(),
36 counter_id
37 )
38 });
39 std::mem::replace(
40 slot.get_mut(),
41 Box::new(StratifiedValueChangeCounter::<E, PL, P>::new()),
42 )
43 };
44
45 {
46 let counter = counter
47 .as_any_mut()
48 .downcast_mut::<StratifiedValueChangeCounter<E, PL, P>>()
49 .unwrap_or_else(|| {
50 panic!(
51 "Value change counter for property {} and counter_id {} had unexpected type",
52 P::name(),
53 counter_id
54 )
55 });
56
57 handler(context, counter);
58 counter.clear();
59 }
60
61 {
62 let property_value_store = context.get_property_value_store_mut::<E, P>();
63 let slot = property_value_store
64 .value_change_counters
65 .get_mut(counter_id)
66 .unwrap_or_else(|| {
67 panic!(
68 "No value change counter found for property {} with counter_id {}",
69 P::name(),
70 counter_id
71 )
72 });
73
74 let _ = std::mem::replace(slot.get_mut(), counter);
76 }
77
78 if context.remaining_plan_count() == 0 {
79 return;
80 }
81
82 let next_time = context.get_current_time() + period;
83 context.add_plan_with_phase(
84 next_time,
85 move |context| {
86 handle_periodic_value_change_count_event::<E, PL, P, F>(
87 context, period, counter_id, handler,
88 );
89 },
90 ExecutionPhase::Last,
91 );
92}
93
94pub trait ContextEntitiesExt {
97 fn add_entity<E: Entity, PL: PropertyList<E>>(
98 &mut self,
99 property_list: PL,
100 ) -> Result<EntityId<E>, IxaError>;
101
102 fn get_property<E: Entity, P: Property<E>>(&self, entity_id: EntityId<E>) -> P;
109
110 fn set_property<E: Entity, P: Property<E>>(
112 &mut self,
113 entity_id: EntityId<E>,
114 property_value: P,
115 );
116
117 fn index_property<E: Entity, P: Property<E>>(&mut self);
124
125 fn index_property_counts<E: Entity, P: Property<E>>(&mut self);
130
131 fn track_periodic_value_change_counts<E, PL, P, F>(&mut self, period: f64, handler: F)
149 where
150 E: Entity,
151 PL: PropertyList<E> + Eq + Hash,
152 P: Property<E> + Eq + Hash,
153 F: Fn(&mut Context, &mut StratifiedValueChangeCounter<E, PL, P>) + 'static;
154
155 #[cfg(test)]
164 fn is_property_indexed<E: Entity, P: Property<E>>(&self) -> bool;
165
166 fn with_query_results<'a, E: Entity, Q: Query<E>>(
170 &'a self,
171 query: Q,
172 callback: &mut dyn FnMut(EntitySet<'a, E>),
173 );
174
175 fn query_entity_count<E: Entity, Q: Query<E>>(&self, query: Q) -> usize;
180
181 fn sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> Option<EntityId<E>>
186 where
187 E: Entity,
188 Q: Query<E>,
189 R: RngId + 'static,
190 R::RngType: Rng;
191
192 fn count_and_sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> (usize, Option<EntityId<E>>)
197 where
198 E: Entity,
199 Q: Query<E>,
200 R: RngId + 'static,
201 R::RngType: Rng;
202
203 fn sample_entities<E, Q, R>(&self, rng_id: R, query: Q, n: usize) -> Vec<EntityId<E>>
209 where
210 E: Entity,
211 Q: Query<E>,
212 R: RngId + 'static,
213 R::RngType: Rng;
214
215 fn get_entity_count<E: Entity>(&self) -> usize;
217
218 fn get_entity_iterator<E: Entity>(&self) -> PopulationIterator<E>;
220
221 fn query<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySet<E>;
223
224 fn query_result_iterator<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySetIterator<E>;
226
227 fn match_entity<E: Entity, Q: Query<E>>(&self, entity_id: EntityId<E>, query: Q) -> bool;
229
230 fn filter_entities<E: Entity, Q: Query<E>>(&self, entities: &mut Vec<EntityId<E>>, query: Q);
232}
233
234impl ContextEntitiesExt for Context {
235 fn add_entity<E: Entity, PL: PropertyList<E>>(
236 &mut self,
237 property_list: PL,
238 ) -> Result<EntityId<E>, IxaError> {
239 PL::validate()?;
241
242 if !PL::contains_required_properties() {
244 return Err(IxaError::MissingRequiredInitializationProperties);
245 }
246
247 let new_entity_id = self.entity_store.new_entity_id::<E>();
249
250 property_list.set_values_for_new_entity(
253 new_entity_id,
254 self.entity_store.get_property_store_mut::<E>(),
255 );
256
257 let context_ptr: *const Context = self;
259 let property_store = self.entity_store.get_property_store_mut::<E>();
260 unsafe {
263 property_store.index_unindexed_entities_for_all_properties(&*context_ptr);
264 }
265
266 self.emit_event(EntityCreatedEvent::<E>::new(new_entity_id));
268
269 Ok(new_entity_id)
270 }
271
272 fn get_property<E: Entity, P: Property<E>>(&self, entity_id: EntityId<E>) -> P {
273 if P::is_derived() {
274 P::compute_derived(self, entity_id)
275 } else {
276 let property_store = self.get_property_value_store::<E, P>();
277 property_store.get(entity_id)
278 }
279 }
280
281 fn set_property<E: Entity, P: Property<E>>(
282 &mut self,
283 entity_id: EntityId<E>,
284 property_value: P,
285 ) {
286 debug_assert!(!P::is_derived(), "cannot set a derived property");
287
288 let mut dependents: Vec<Box<dyn PartialPropertyChangeEvent>> = vec![];
317
318 {
320 let property_store = self.entity_store.get_property_store::<E>();
321
322 dependents.push(property_store.create_partial_property_change(
324 P::id(),
325 entity_id,
326 self,
327 ));
328 for dependent_idx in P::dependents() {
330 dependents.push(property_store.create_partial_property_change(
331 *dependent_idx,
332 entity_id,
333 self,
334 ));
335 }
336 }
337
338 let property_value_store = self.get_property_value_store::<E, P>();
340 property_value_store.set(entity_id, property_value);
341
342 for dependent in dependents.into_iter() {
345 dependent.emit_in_context(self)
346 }
347 }
348
349 fn index_property<E: Entity, P: Property<E>>(&mut self) {
350 let property_id = P::index_id();
351 let context_ptr: *const Context = self;
352 let property_store = self.entity_store.get_property_store_mut::<E>();
353 property_store.set_property_indexed::<P>(PropertyIndexType::FullIndex);
354 unsafe {
357 property_store.index_unindexed_entities_for_property_id(&*context_ptr, property_id);
358 }
359 }
360
361 fn index_property_counts<E: Entity, P: Property<E>>(&mut self) {
362 let property_store = self.entity_store.get_property_store_mut::<E>();
363 let current_index_type = property_store.get::<P>().index_type();
364 if current_index_type != PropertyIndexType::FullIndex {
365 property_store.set_property_indexed::<P>(PropertyIndexType::ValueCountIndex);
366 }
367 }
368
369 fn track_periodic_value_change_counts<E, PL, P, F>(&mut self, period: f64, handler: F)
370 where
371 E: Entity,
372 PL: PropertyList<E> + Eq + Hash,
373 P: Property<E> + Eq + Hash,
374 F: Fn(&mut Context, &mut StratifiedValueChangeCounter<E, PL, P>) + 'static,
375 {
376 assert!(
377 period > 0.0 && !period.is_nan() && !period.is_infinite(),
378 "Period must be greater than 0"
379 );
380 let start_time = self.get_start_time().unwrap_or(0.0);
381 self.add_plan_with_phase(
382 start_time,
383 move |context| {
384 let counter_id = context
387 .entity_store
388 .get_property_store_mut::<E>()
389 .create_value_change_counter::<PL, P>();
390
391 context.add_plan_with_phase(
394 context.get_current_time(),
395 move |context| {
396 handle_periodic_value_change_count_event::<E, PL, P, F>(
397 context, period, counter_id, handler,
398 );
399 },
400 ExecutionPhase::Last,
401 );
402 },
403 ExecutionPhase::First,
404 );
405 }
406
407 #[cfg(test)]
408 fn is_property_indexed<E: Entity, P: Property<E>>(&self) -> bool {
409 let property_store = self.entity_store.get_property_store::<E>();
410 property_store.is_property_indexed::<P>()
411 }
412
413 fn with_query_results<'a, E: Entity, Q: Query<E>>(
414 &'a self,
415 query: Q,
416 callback: &mut dyn FnMut(EntitySet<'a, E>),
417 ) {
418 if let Some(multi_property_id) = query.multi_property_id() {
423 let property_store = self.entity_store.get_property_store::<E>();
424 match property_store.get_index_set_with_hash_for_property_id(
425 multi_property_id,
426 query.multi_property_value_hash(),
427 ) {
428 IndexSetResult::Set(people_set) => {
429 callback(EntitySet::from_source(SourceSet::IndexSet(people_set)));
430 return;
431 }
432 IndexSetResult::Empty => {
433 callback(EntitySet::empty());
434 return;
435 }
436 IndexSetResult::Unsupported => {}
437 }
438 }
440
441 if query.type_id() == TypeId::of::<()>() {
443 warn!("Called Context::with_query_results() with an empty query. Prefer Context::get_entity_iterator::<E>() for working with the entire population.");
444 callback(EntitySet::from_source(SourceSet::Population(
445 self.get_entity_count::<E>(),
446 )));
447 return;
448 }
449
450 warn!("Called Context::with_query_results() with an unindexed query. It's almost always better to use Context::query_result_iterator() for unindexed queries.");
452
453 callback(self.query(query));
455 }
456
457 fn query_entity_count<E: Entity, Q: Query<E>>(&self, query: Q) -> usize {
458 if let Some(multi_property_id) = query.multi_property_id() {
462 let property_store = self.entity_store.get_property_store::<E>();
463 match property_store.get_index_count_with_hash_for_property_id(
464 multi_property_id,
465 query.multi_property_value_hash(),
466 ) {
467 IndexCountResult::Count(count) => return count,
468 IndexCountResult::Unsupported => {}
469 }
470 }
472
473 self.query_result_iterator(query).count()
474 }
475 fn sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> Option<EntityId<E>>
476 where
477 E: Entity,
478 Q: Query<E>,
479 R: RngId + 'static,
480 R::RngType: Rng,
481 {
482 if query.type_id() == TypeId::of::<()>() {
483 let population = self.get_entity_count::<E>();
484 return self.sample(rng_id, move |rng| {
485 if population == 0 {
486 warn!("Requested a sample entity from an empty population");
487 return None;
488 }
489 let index = if population <= u32::MAX as usize {
490 rng.random_range(0..population as u32) as usize
491 } else {
492 rng.random_range(0..population)
493 };
494 Some(EntityId::new(index))
495 });
496 }
497
498 let query_result = self.query_result_iterator(query);
499 self.sample(rng_id, move |rng| query_result.sample_entity(rng))
500 }
501
502 fn count_and_sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> (usize, Option<EntityId<E>>)
503 where
504 E: Entity,
505 Q: Query<E>,
506 R: RngId + 'static,
507 R::RngType: Rng,
508 {
509 if query.type_id() == TypeId::of::<()>() {
510 let population = self.get_entity_count::<E>();
511 return self.sample(rng_id, move |rng| {
512 if population == 0 {
513 return (0, None);
514 }
515 let index = if population <= u32::MAX as usize {
516 rng.random_range(0..population as u32) as usize
517 } else {
518 rng.random_range(0..population)
519 };
520 (population, Some(EntityId::new(index)))
521 });
522 }
523
524 let query_result = self.query_result_iterator(query);
525 self.sample(rng_id, move |rng| query_result.count_and_sample_entity(rng))
526 }
527
528 fn sample_entities<E, Q, R>(&self, rng_id: R, query: Q, n: usize) -> Vec<EntityId<E>>
529 where
530 E: Entity,
531 Q: Query<E>,
532 R: RngId + 'static,
533 R::RngType: Rng,
534 {
535 if query.type_id() == TypeId::of::<()>() {
536 let population = self.get_entity_count::<E>();
537 return self.sample(rng_id, move |rng| {
538 if population == 0 {
539 warn!("Requested a sample of entities from an empty population");
540 return vec![];
541 }
542 if n >= population {
543 return PopulationIterator::<E>::new(population).collect();
544 }
545 sample_multiple_from_known_length(rng, PopulationIterator::<E>::new(population), n)
546 });
547 }
548
549 let query_result = self.query_result_iterator(query);
550 self.sample(rng_id, move |rng| query_result.sample_entities(rng, n))
551 }
552
553 fn get_entity_count<E: Entity>(&self) -> usize {
554 self.entity_store.get_entity_count::<E>()
555 }
556
557 fn get_entity_iterator<E: Entity>(&self) -> PopulationIterator<E> {
558 self.entity_store.get_entity_iterator::<E>()
559 }
560
561 fn query<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySet<E> {
562 query.new_query_result(self)
563 }
564
565 fn query_result_iterator<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySetIterator<E> {
566 query.new_query_result_iterator(self)
567 }
568
569 fn match_entity<E: Entity, Q: Query<E>>(&self, entity_id: EntityId<E>, query: Q) -> bool {
570 query.match_entity(entity_id, self)
571 }
572
573 fn filter_entities<E: Entity, Q: Query<E>>(&self, entities: &mut Vec<EntityId<E>>, query: Q) {
574 query.filter_entities(entities, self);
575 }
576}
577
578#[cfg(test)]
579mod tests {
580 use std::cell::RefCell;
581 use std::rc::Rc;
582
583 use serde::Serialize;
584
585 use super::*;
586 use crate::hashing::IndexSet;
587 use crate::prelude::PropertyChangeEvent;
588 use crate::{
589 define_derived_property, define_entity, define_multi_property, define_property, define_rng,
590 impl_property,
591 };
592
593 define_entity!(Animal);
594 define_property!(struct Legs(u8), Animal, default_const = Legs(4));
595 define_rng!(EntityContextTestRng);
596
597 define_entity!(Person);
598
599 define_property!(struct Age(u8), Person);
600
601 #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize)]
602 struct CounterValue(u8);
603 impl_property!(CounterValue, Person, default_const = CounterValue(0));
604
605 #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize)]
606 struct CounterStratum(bool);
607 impl_property!(
608 CounterStratum,
609 Person,
610 default_const = CounterStratum(false)
611 );
612
613 define_property!(
614 enum InfectionStatus {
615 Susceptible,
616 Infected,
617 Recovered,
618 },
619 Person,
620 default_const = InfectionStatus::Susceptible
621 );
622
623 define_property!(
624 struct Vaccinated(bool),
625 Person,
626 default_const = Vaccinated(false)
627 );
628
629 define_derived_property!(
630 enum AgeGroup {
631 Child,
632 Adult,
633 Senior,
634 },
635 Person,
636 [Age],
637 |age| {
638 if age.0 <= 18 {
639 AgeGroup::Child
640 } else if age.0 <= 65 {
641 AgeGroup::Adult
642 } else {
643 AgeGroup::Senior
644 }
645 }
646 );
647
648 define_derived_property!(
649 enum RiskLevel {
650 Low,
651 Medium,
652 High,
653 },
654 Person,
655 [AgeGroup, Vaccinated, InfectionStatus],
656 |age_group, vaccinated, infection_status| {
657 match (age_group, vaccinated, infection_status) {
658 (AgeGroup::Senior, Vaccinated(false), InfectionStatus::Susceptible) => {
659 RiskLevel::High
660 }
661 (_, Vaccinated(false), InfectionStatus::Susceptible) => RiskLevel::Medium,
662 _ => RiskLevel::Low,
663 }
664 }
665 );
666
667 define_property!(struct IsRunner(bool), Person, default_const = IsRunner(false));
681 define_property!(struct IsSwimmer(bool), Person, default_const = IsSwimmer(false));
682 define_derived_property!(
683 struct AdultRunner(bool),
684 Person,
685 [AgeGroup, IsRunner],
686 | age_group, is_runner | {
687 AdultRunner(
688 age_group == AgeGroup::Adult
689 && is_runner.0
690 )
691 }
692 );
693 define_derived_property!(
694 struct AdultSwimmer(bool),
695 Person,
696 [AgeGroup, IsSwimmer],
697 | age_group, is_swimmer | {
698 AdultSwimmer(
699 age_group == AgeGroup::Adult
700 && is_swimmer.0
701 )
702 }
703 );
704 define_derived_property!(
705 struct AdultAthlete(bool),
706 Person,
707 [AdultSwimmer, AdultRunner],
708 | adult_swimmer, adult_runner | {
709 AdultAthlete(
710 adult_swimmer.0 || adult_runner.0
711 )
712 }
713 );
714
715 #[test]
716 fn add_and_count_entities() {
717 let mut context = Context::new();
718
719 let _person1 = context
720 .add_entity((Age(12), InfectionStatus::Susceptible, Vaccinated(true)))
721 .unwrap();
722 assert_eq!(context.get_entity_count::<Person>(), 1);
723
724 let _person2 = context.add_entity((Age(34), Vaccinated(true))).unwrap();
725 assert_eq!(context.get_entity_count::<Person>(), 2);
726
727 let _person3 = context.add_entity((Age(120),)).unwrap();
729 assert_eq!(context.get_entity_count::<Person>(), 3);
730 }
731
732 #[test]
733 fn add_entity_with_zst() {
734 let mut context = Context::new();
735 let animal = context.add_entity(Animal).unwrap();
736 assert_eq!(context.get_entity_count::<Animal>(), 1);
737 assert_eq!(context.get_property::<Animal, Legs>(animal), Legs(4));
738 }
739
740 #[derive(Copy, Clone, Debug)]
742 enum IndexMode {
743 Unindexed,
744 FullIndex,
745 ValueCountIndex,
746 }
747
748 fn setup_context_for_index_tests(index_mode: IndexMode) -> (Context, Age, Age) {
750 let mut context = Context::new();
751 match index_mode {
752 IndexMode::Unindexed => {}
753 IndexMode::FullIndex => context.index_property::<Person, Age>(),
754 IndexMode::ValueCountIndex => context.index_property_counts::<Person, Age>(),
755 }
756
757 let existing_value = Age(12);
758 let missing_value = Age(99);
759
760 let _ = context.add_entity((existing_value,)).unwrap();
761 let _ = context.add_entity((existing_value,)).unwrap();
762
763 (context, existing_value, missing_value)
764 }
765
766 #[test]
767 fn query_results_respect_index_modes() {
768 let modes = [
769 IndexMode::Unindexed,
770 IndexMode::FullIndex,
771 IndexMode::ValueCountIndex,
772 ];
773
774 for mode in modes {
775 let (context, existing_value, missing_value) = setup_context_for_index_tests(mode);
776
777 let mut existing_len = 0;
778 context.with_query_results((existing_value,), &mut |people_set| {
779 existing_len = people_set.into_iter().count();
780 });
781 assert_eq!(existing_len, 2, "Wrong length for {mode:?}");
782
783 let mut missing_len = 0;
784 context.with_query_results((missing_value,), &mut |people_set| {
785 missing_len = people_set.into_iter().count();
786 });
787 assert_eq!(missing_len, 0);
788
789 let existing_count = context.query_result_iterator((existing_value,)).count();
790 assert_eq!(existing_count, 2);
791
792 let missing_count = context.query_result_iterator((missing_value,)).count();
793 assert_eq!(missing_count, 0);
794
795 assert_eq!(context.query_entity_count((existing_value,)), 2);
796 assert_eq!(context.query_entity_count((missing_value,)), 0);
797 }
798 }
799
800 #[test]
801 fn add_an_entity_without_required_properties() {
802 let mut context = Context::new();
803 let result = context.add_entity((InfectionStatus::Susceptible, Vaccinated(true)));
804
805 assert!(matches!(
806 result,
807 Err(crate::IxaError::MissingRequiredInitializationProperties)
808 ));
809 }
810
811 #[test]
812 fn new_entities_have_default_values() {
813 let mut context = Context::new();
814
815 let person = context.add_entity((Age(25),)).unwrap();
817
818 let age: Age = context.get_property(person);
820 assert_eq!(age, Age(25));
821 let infection_status: InfectionStatus = context.get_property(person);
822 assert_eq!(infection_status, InfectionStatus::Susceptible);
823 let vaccinated: Vaccinated = context.get_property(person);
824 assert_eq!(vaccinated, Vaccinated(false));
825
826 context.set_property(person, Age(26));
828 context.set_property(person, InfectionStatus::Infected);
829 context.set_property(person, Vaccinated(true));
830
831 let age: Age = context.get_property(person);
833 assert_eq!(age, Age(26));
834 let infection_status: InfectionStatus = context.get_property(person);
835 assert_eq!(infection_status, InfectionStatus::Infected);
836 let vaccinated: Vaccinated = context.get_property(person);
837 assert_eq!(vaccinated, Vaccinated(true));
838 }
839
840 #[test]
841 fn get_and_set_property_explicit() {
842 let mut context = Context::new();
843
844 let person = context
846 .add_entity((Age(25), InfectionStatus::Recovered, Vaccinated(true)))
847 .unwrap();
848
849 let age: Age = context.get_property(person);
851 assert_eq!(age, Age(25));
852 let infection_status: InfectionStatus = context.get_property(person);
853 assert_eq!(infection_status, InfectionStatus::Recovered);
854 let vaccinated: Vaccinated = context.get_property(person);
855 assert_eq!(vaccinated, Vaccinated(true));
856
857 context.set_property(person, Age(26));
859 context.set_property(person, InfectionStatus::Infected);
860 context.set_property(person, Vaccinated(false));
861
862 let age: Age = context.get_property(person);
864 assert_eq!(age, Age(26));
865 let infection_status: InfectionStatus = context.get_property(person);
866 assert_eq!(infection_status, InfectionStatus::Infected);
867 let vaccinated: Vaccinated = context.get_property(person);
868 assert_eq!(vaccinated, Vaccinated(false));
869 }
870
871 #[test]
872 fn count_entities() {
873 let mut context = Context::new();
874
875 assert_eq!(context.get_entity_count::<Animal>(), 0);
876 assert_eq!(context.get_entity_count::<Person>(), 0);
877
878 for _ in 0..7 {
880 let _: PersonId = context.add_entity((Age(25),)).unwrap();
881 }
882 for _ in 0..5 {
883 let _: AnimalId = context.add_entity((Legs(2),)).unwrap();
884 }
885
886 assert_eq!(context.get_entity_count::<Animal>(), 5);
887 assert_eq!(context.get_entity_count::<Person>(), 7);
888
889 let _: PersonId = context.add_entity((Age(30),)).unwrap();
890 let _: AnimalId = context.add_entity((Legs(8),)).unwrap();
891
892 assert_eq!(context.get_entity_count::<Animal>(), 6);
893 assert_eq!(context.get_entity_count::<Person>(), 8);
894 }
895
896 #[test]
897 fn count_and_sample_entity_empty_query_fast_path() {
898 let mut context = Context::new();
899 context.init_random(42);
900 for age in [10u8, 20, 30] {
901 let _: PersonId = context.add_entity((Age(age),)).unwrap();
902 }
903
904 let (count, sampled) =
905 context.count_and_sample_entity::<Person, _, _>(EntityContextTestRng, ());
906 assert_eq!(count, 3);
907 assert!(sampled.is_some());
908 }
909
910 #[test]
911 fn count_and_sample_entity_unindexed_derived_query() {
912 let mut context = Context::new();
913 context.init_random(43);
914 for age in [10u8, 20, 30, 80] {
915 let _: PersonId = context.add_entity((Age(age),)).unwrap();
916 }
917
918 let query = (AgeGroup::Adult,);
919 let expected_count = context.query_entity_count(query);
920 let (count, sampled) = context.count_and_sample_entity(EntityContextTestRng, query);
921 assert_eq!(count, expected_count);
922 assert_eq!(sampled.is_some(), count > 0);
923 if let Some(entity_id) = sampled {
924 assert!(context.match_entity(entity_id, query));
925 }
926 }
927
928 #[test]
929 fn get_derived_property_multiple_deps() {
930 let mut context = Context::new();
931
932 let expected_high_id: PersonId = context
933 .add_entity((Age(77), Vaccinated(false), InfectionStatus::Susceptible))
934 .unwrap();
935 let expected_med_id: PersonId = context
936 .add_entity((Age(30), Vaccinated(false), InfectionStatus::Susceptible))
937 .unwrap();
938 let expected_low_id: PersonId = context
939 .add_entity((Age(3), Vaccinated(true), InfectionStatus::Recovered))
940 .unwrap();
941
942 let actual_high: RiskLevel = context.get_property(expected_high_id);
943 assert_eq!(actual_high, RiskLevel::High);
944 let actual_med: RiskLevel = context.get_property(expected_med_id);
945 assert_eq!(actual_med, RiskLevel::Medium);
946 let actual_low: RiskLevel = context.get_property(expected_low_id);
947 assert_eq!(actual_low, RiskLevel::Low);
948 }
949
950 #[test]
951 fn listen_to_derived_property_change_event() {
952 let mut context = Context::new();
953
954 let expected_high_id = PersonId::new(0);
955
956 let risk_flag = Rc::new(RefCell::new(0));
959 let risk_flag_clone = risk_flag.clone();
960 context.subscribe_to_event(
961 move |_context, event: PropertyChangeEvent<Person, RiskLevel>| {
962 assert_eq!(event.entity_id, expected_high_id);
963 assert_eq!(event.previous, RiskLevel::High);
964 assert_eq!(event.current, RiskLevel::Medium);
965 *risk_flag_clone.borrow_mut() += 1;
966 },
967 );
968 let age_group_flag = Rc::new(RefCell::new(0));
970 let age_group_flag_clone = age_group_flag.clone();
971 context.subscribe_to_event(
972 move |_context, event: PropertyChangeEvent<Person, AgeGroup>| {
973 assert_eq!(event.entity_id, expected_high_id);
974 assert_eq!(event.previous, AgeGroup::Senior);
975 assert_eq!(event.current, AgeGroup::Adult);
976 *age_group_flag_clone.borrow_mut() += 1;
977 },
978 );
979
980 let expected_high_id: PersonId = context
982 .add_entity((Age(77), Vaccinated(false), InfectionStatus::Susceptible))
983 .unwrap();
984
985 context.set_property(expected_high_id, Age(20));
987
988 context.execute();
990 assert_eq!(*risk_flag.borrow(), 1);
992 assert_eq!(*age_group_flag.borrow(), 1);
993 }
994
995 #[test]
1015 fn observe_diamond_property_change() {
1016 let mut context = Context::new();
1017 let person = context.add_entity((Age(17), IsSwimmer(true))).unwrap();
1018
1019 let is_adult_athlete: AdultAthlete = context.get_property(person);
1020 assert!(!is_adult_athlete.0);
1021
1022 let flag = Rc::new(RefCell::new(0));
1023 let flag_clone = flag.clone();
1024 context.subscribe_to_event(
1025 move |_context, event: PropertyChangeEvent<Person, AdultAthlete>| {
1026 assert_eq!(event.entity_id, person);
1027 assert_eq!(event.previous, AdultAthlete(false));
1028 assert_eq!(event.current, AdultAthlete(true));
1029 *flag_clone.borrow_mut() += 1;
1030 },
1031 );
1032
1033 context.set_property(person, Age(20));
1034 let is_adult_athlete: AdultAthlete = context.get_property(person);
1036 assert!(is_adult_athlete.0);
1037
1038 context.execute();
1040 assert_eq!(*flag.borrow(), 1);
1042 }
1043
1044 define_multi_property!((InfectionStatus, Vaccinated), Person);
1047 define_multi_property!((Vaccinated, InfectionStatus), Person);
1048
1049 #[test]
1050 fn with_query_results_finds_multi_index() {
1051 use crate::rand::seq::IndexedRandom;
1052 let mut rng = crate::rand::rng();
1053 let mut context = Context::new();
1054
1055 for _ in 0..10_000usize {
1056 let infection_status = *[
1057 InfectionStatus::Susceptible,
1058 InfectionStatus::Infected,
1059 InfectionStatus::Recovered,
1060 ]
1061 .choose(&mut rng)
1062 .unwrap();
1063 let vaccination_status: bool = rng.random_bool(0.5);
1064 let age: u8 = rng.random_range(0..100);
1065 context
1066 .add_entity((Age(age), infection_status, Vaccinated(vaccination_status)))
1067 .unwrap();
1068 }
1069 context.index_property::<Person, InfectionStatusVaccinated>();
1070 let _ = context.query_result_iterator((InfectionStatus::Susceptible, Vaccinated(true)));
1072
1073 let mut result_entities: IndexSet<EntityId<Person>> = IndexSet::default();
1075 context.with_query_results(
1076 (InfectionStatus::Susceptible, Vaccinated(true)),
1077 &mut |result_set| {
1078 result_entities = result_set.into_iter().collect::<IndexSet<_>>();
1079 },
1080 );
1081
1082 assert_eq!(
1084 InfectionStatusVaccinated::index_id(),
1085 VaccinatedInfectionStatus::index_id()
1086 );
1087 assert_eq!(
1088 InfectionStatusVaccinated::index_id(),
1089 (InfectionStatus::Susceptible, Vaccinated(true))
1090 .multi_property_id()
1091 .unwrap()
1092 );
1093
1094 let index_id = InfectionStatusVaccinated::index_id();
1096
1097 let property_store = context.entity_store.get_property_store::<Person>();
1098 let property_value_store = property_store.get_with_id(index_id);
1099 let bucket: &IndexSet<EntityId<Person>> = property_value_store
1100 .get_index_set_with_hash(
1101 (InfectionStatus::Susceptible, Vaccinated(true)).multi_property_value_hash(),
1102 )
1103 .unwrap();
1104
1105 let expected_entities = bucket.iter().copied().collect::<IndexSet<_>>();
1106 assert_eq!(expected_entities, result_entities);
1107 }
1108
1109 #[test]
1110 fn query_returns_entity_set_and_query_result_iterator_remains_compatible() {
1111 let mut context = Context::new();
1112 let p1 = context
1113 .add_entity((Age(21), InfectionStatus::Susceptible, Vaccinated(true)))
1114 .unwrap();
1115 let _p2 = context
1116 .add_entity((Age(22), InfectionStatus::Susceptible, Vaccinated(false)))
1117 .unwrap();
1118 let p3 = context
1119 .add_entity((Age(23), InfectionStatus::Infected, Vaccinated(true)))
1120 .unwrap();
1121
1122 let query = (Vaccinated(true),);
1123
1124 let from_set = context
1125 .query::<Person, _>(query)
1126 .into_iter()
1127 .collect::<IndexSet<_>>();
1128 let from_iterator = context
1129 .query_result_iterator(query)
1130 .collect::<IndexSet<_>>();
1131
1132 assert_eq!(from_set, from_iterator);
1133 assert!(from_set.contains(&p1));
1134 assert!(from_set.contains(&p3));
1135 assert_eq!(from_set.len(), 2);
1136 }
1137
1138 #[test]
1139 fn set_property_correctly_maintains_index() {
1140 let mut context = Context::new();
1141 context.index_property::<Person, InfectionStatus>();
1142 context.index_property::<Person, AgeGroup>();
1143
1144 let person1 = context.add_entity((Age(22),)).unwrap();
1145 let person2 = context.add_entity((Age(22),)).unwrap();
1146 for _ in 0..4 {
1147 let _: PersonId = context.add_entity((Age(22),)).unwrap();
1148 }
1149
1150 assert_eq!(
1152 context.query_entity_count((InfectionStatus::Susceptible,)),
1153 6
1154 );
1155 assert_eq!(context.query_entity_count((InfectionStatus::Infected,)), 0);
1156 assert_eq!(context.query_entity_count((InfectionStatus::Recovered,)), 0);
1157
1158 context.set_property(person1, InfectionStatus::Infected);
1159
1160 assert_eq!(
1161 context.query_entity_count((InfectionStatus::Susceptible,)),
1162 5
1163 );
1164 assert_eq!(context.query_entity_count((InfectionStatus::Infected,)), 1);
1165 assert_eq!(context.query_entity_count((InfectionStatus::Recovered,)), 0);
1166
1167 context.set_property(person1, InfectionStatus::Recovered);
1168
1169 assert_eq!(
1170 context.query_entity_count((InfectionStatus::Susceptible,)),
1171 5
1172 );
1173 assert_eq!(context.query_entity_count((InfectionStatus::Infected,)), 0);
1174 assert_eq!(context.query_entity_count((InfectionStatus::Recovered,)), 1);
1175
1176 assert_eq!(context.query_entity_count((AgeGroup::Child,)), 0);
1178 assert_eq!(context.query_entity_count((AgeGroup::Adult,)), 6);
1179 assert_eq!(context.query_entity_count((AgeGroup::Senior,)), 0);
1180
1181 context.set_property(person2, Age(12));
1182
1183 assert_eq!(context.query_entity_count((AgeGroup::Child,)), 1);
1184 assert_eq!(context.query_entity_count((AgeGroup::Adult,)), 5);
1185 assert_eq!(context.query_entity_count((AgeGroup::Senior,)), 0);
1186
1187 context.set_property(person1, Age(75));
1188
1189 assert_eq!(context.query_entity_count((AgeGroup::Child,)), 1);
1190 assert_eq!(context.query_entity_count((AgeGroup::Adult,)), 4);
1191 assert_eq!(context.query_entity_count((AgeGroup::Senior,)), 1);
1192
1193 context.set_property(person2, Age(77));
1194
1195 assert_eq!(context.query_entity_count((AgeGroup::Child,)), 0);
1196 assert_eq!(context.query_entity_count((AgeGroup::Adult,)), 4);
1197 assert_eq!(context.query_entity_count((AgeGroup::Senior,)), 2);
1198 }
1199
1200 #[test]
1201 fn query_unindexed_default_properties() {
1202 let mut context = Context::new();
1203
1204 for idx in 0..10 {
1206 if idx % 2 == 0 {
1207 context.add_entity((Age(22),)).unwrap();
1208 } else {
1209 context
1210 .add_entity((Age(22), InfectionStatus::Recovered))
1211 .unwrap();
1212 }
1213 }
1214 for _ in 0..10 {
1216 let _: PersonId = context.add_entity((Age(22),)).unwrap();
1217 }
1218
1219 assert_eq!(context.query_entity_count((InfectionStatus::Recovered,)), 5);
1220 assert_eq!(
1221 context.query_entity_count((InfectionStatus::Susceptible,)),
1222 15
1223 );
1224 }
1225
1226 #[test]
1227 fn query_unindexed_derived_properties() {
1228 let mut context = Context::new();
1229
1230 for _ in 0..10 {
1231 let _: PersonId = context.add_entity((Age(22),)).unwrap();
1232 }
1233
1234 assert_eq!(context.query_entity_count((AdultAthlete(false),)), 10);
1235 }
1236
1237 #[test]
1238 fn track_periodic_value_change_counts_uses_distinct_counters() {
1239 let mut context = Context::new();
1240
1241 context.track_periodic_value_change_counts::<Person, (CounterStratum,), CounterValue, _>(
1242 1.0,
1243 move |_context, _counter| {},
1244 );
1245
1246 context.track_periodic_value_change_counts::<Person, (CounterStratum,), CounterValue, _>(
1247 1.0,
1248 move |_context, _counter| {},
1249 );
1250
1251 let property_value_store = context.get_property_value_store::<Person, CounterValue>();
1252 assert_eq!(property_value_store.value_change_counters.len(), 0);
1253
1254 context.add_plan(0.5, Context::shutdown);
1255 context.execute();
1256
1257 let property_value_store = context.get_property_value_store::<Person, CounterValue>();
1258 assert_eq!(property_value_store.value_change_counters.len(), 2);
1259 }
1260
1261 #[test]
1262 fn value_change_counter_updates_on_true_transitions() {
1263 let mut context = Context::new();
1264 let observed = Rc::new(RefCell::new(Vec::<(usize, usize)>::new()));
1265 let observed_clone = observed.clone();
1266
1267 context.track_periodic_value_change_counts(1.0, move |_context, counter| {
1268 observed_clone.borrow_mut().push((
1269 counter.get_count((CounterStratum(true),), CounterValue(1)),
1270 counter.get_count((CounterStratum(true),), CounterValue(2)),
1271 ));
1272 });
1273
1274 let person = context
1275 .add_entity((Age(10), CounterValue(0), CounterStratum(true)))
1276 .unwrap();
1277 context.add_plan(0.1, move |context| {
1278 context.set_property(person, CounterValue(1));
1279 context.set_property(person, CounterValue(1));
1280 context.set_property(person, CounterValue(2));
1281 });
1282
1283 context.execute();
1284 assert_eq!(*observed.borrow(), vec![(0, 0), (1, 1)]);
1285 }
1286
1287 #[test]
1288 fn periodic_value_change_counts_report_and_clear() {
1289 let mut context = Context::new();
1290 let person = context
1291 .add_entity((Age(10), CounterValue(0), CounterStratum(true)))
1292 .unwrap();
1293
1294 let observed = Rc::new(RefCell::new(Vec::<usize>::new()));
1295 let observed_clone = observed.clone();
1296
1297 context.track_periodic_value_change_counts(1.0, move |_context, counter| {
1298 observed_clone
1299 .borrow_mut()
1300 .push(counter.get_count((CounterStratum(true),), CounterValue(1)));
1301 });
1302
1303 context.add_plan(0.5, move |context| {
1304 context.set_property(person, CounterValue(1));
1305 });
1306 context.add_plan(1.5, move |context| {
1307 context.set_property(person, CounterValue(1));
1308 });
1309
1310 context.execute();
1311 assert_eq!(*observed.borrow(), vec![0, 1, 0]);
1312 }
1313
1314 #[test]
1315 fn periodic_value_change_counts_start_time_and_phase_behavior() {
1316 let mut context = Context::new();
1317 context.set_start_time(-2.0);
1318
1319 let person = context
1320 .add_entity((Age(10), CounterValue(0), CounterStratum(true)))
1321 .unwrap();
1322
1323 let observed_times = Rc::new(RefCell::new(Vec::<f64>::new()));
1324 let observed_counts = Rc::new(RefCell::new(Vec::<usize>::new()));
1325 let observed_times_clone = observed_times.clone();
1326 let observed_counts_clone = observed_counts.clone();
1327
1328 context.track_periodic_value_change_counts(1.0, move |context, counter| {
1329 observed_times_clone
1330 .borrow_mut()
1331 .push(context.get_current_time());
1332 observed_counts_clone
1333 .borrow_mut()
1334 .push(counter.get_count((CounterStratum(true),), CounterValue(1)));
1335 });
1336
1337 context.add_plan_with_phase(
1338 -2.0,
1339 move |context| {
1340 context.set_property(person, CounterValue(1));
1341 },
1342 ExecutionPhase::Normal,
1343 );
1344 context.add_plan(0.0, |_| {});
1345
1346 context.execute();
1347
1348 assert_eq!(*observed_times.borrow(), vec![-2.0, -1.0, 0.0]);
1349 assert_eq!(*observed_counts.borrow(), vec![1, 0, 0]);
1350 }
1351}