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