1use std::any::{Any, TypeId};
2
3use crate::entity::entity_set::EntitySetIterator;
4use crate::entity::events::{EntityCreatedEvent, PartialPropertyChangeEvent};
5use crate::entity::index::{IndexCountResult, IndexSetResult, PropertyIndexType};
6use crate::entity::property::Property;
7use crate::entity::property_list::PropertyList;
8use crate::entity::query::Query;
9use crate::entity::{Entity, EntityId, PopulationIterator};
10use crate::hashing::IndexSet;
11use crate::rand::Rng;
12use crate::{warn, Context, ContextRandomExt, IxaError, RngId};
13
14pub trait ContextEntitiesExt {
17 fn add_entity<E: Entity, PL: PropertyList<E>>(
18 &mut self,
19 property_list: PL,
20 ) -> Result<EntityId<E>, IxaError>;
21
22 fn get_property<E: Entity, P: Property<E>>(&self, entity_id: EntityId<E>) -> P;
29
30 fn set_property<E: Entity, P: Property<E>>(
32 &mut self,
33 entity_id: EntityId<E>,
34 property_value: P,
35 );
36
37 fn index_property<E: Entity, P: Property<E>>(&mut self);
44
45 fn index_property_counts<E: Entity, P: Property<E>>(&mut self);
50
51 #[cfg(test)]
60 fn is_property_indexed<E: Entity, P: Property<E>>(&self) -> bool;
61
62 fn with_query_results<E: Entity, Q: Query<E>>(
66 &self,
67 query: Q,
68 callback: &mut dyn FnMut(&IndexSet<EntityId<E>>),
69 );
70
71 fn query_entity_count<E: Entity, Q: Query<E>>(&self, query: Q) -> usize;
76
77 fn sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> Option<EntityId<E>>
82 where
83 E: Entity,
84 Q: Query<E>,
85 R: RngId + 'static,
86 R::RngType: Rng;
87
88 fn sample_entities<E, Q, R>(&self, rng_id: R, query: Q, n: usize) -> Vec<EntityId<E>>
94 where
95 E: Entity,
96 Q: Query<E>,
97 R: RngId + 'static,
98 R::RngType: Rng;
99
100 fn get_entity_count<E: Entity>(&self) -> usize;
102
103 fn get_entity_iterator<E: Entity>(&self) -> PopulationIterator<E>;
105
106 fn query_result_iterator<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySetIterator<E>;
108
109 fn match_entity<E: Entity, Q: Query<E>>(&self, entity_id: EntityId<E>, query: Q) -> bool;
111
112 fn filter_entities<E: Entity, Q: Query<E>>(&self, entities: &mut Vec<EntityId<E>>, query: Q);
114}
115
116impl ContextEntitiesExt for Context {
117 fn add_entity<E: Entity, PL: PropertyList<E>>(
118 &mut self,
119 property_list: PL,
120 ) -> Result<EntityId<E>, IxaError> {
121 PL::validate()?;
123
124 if !PL::contains_required_properties() {
126 return Err("initialization list is missing required properties".into());
127 }
128
129 let new_entity_id = self.entity_store.new_entity_id::<E>();
131
132 property_list
135 .set_values_for_entity(new_entity_id, self.entity_store.get_property_store::<E>());
136
137 self.emit_event(EntityCreatedEvent::<E>::new(new_entity_id));
139
140 Ok(new_entity_id)
141 }
142
143 fn get_property<E: Entity, P: Property<E>>(&self, entity_id: EntityId<E>) -> P {
144 if P::is_derived() {
145 P::compute_derived(self, entity_id)
146 } else {
147 let property_store = self.get_property_value_store::<E, P>();
148 property_store.get(entity_id)
149 }
150 }
151
152 fn set_property<E: Entity, P: Property<E>>(
153 &mut self,
154 entity_id: EntityId<E>,
155 property_value: P,
156 ) {
157 debug_assert!(!P::is_derived(), "cannot set a derived property");
158
159 let mut dependents: Vec<Box<dyn PartialPropertyChangeEvent>> = vec![];
200 let property_store = self.entity_store.get_property_store::<E>();
201
202 dependents.push(property_store.create_partial_property_change(P::id(), entity_id, self));
204 for dependent_idx in P::dependents() {
206 dependents.push(property_store.create_partial_property_change(
207 *dependent_idx,
208 entity_id,
209 self,
210 ));
211 }
212
213 let property_value_store = self.get_property_value_store::<E, P>();
215 property_value_store.set(entity_id, property_value);
216
217 for dependent in dependents.into_iter() {
219 dependent.emit_in_context(self)
220 }
221 }
222
223 fn index_property<E: Entity, P: Property<E>>(&mut self) {
224 let property_store = self.entity_store.get_property_store_mut::<E>();
225 property_store.set_property_indexed::<P>(PropertyIndexType::FullIndex);
226 }
227
228 fn index_property_counts<E: Entity, P: Property<E>>(&mut self) {
229 let property_store = self.entity_store.get_property_store_mut::<E>();
230 let current_index_type = property_store.get::<P>().index_type();
231 if current_index_type != PropertyIndexType::FullIndex {
232 property_store.set_property_indexed::<P>(PropertyIndexType::ValueCountIndex);
233 }
234 }
235
236 #[cfg(test)]
237 fn is_property_indexed<E: Entity, P: Property<E>>(&self) -> bool {
238 let property_store = self.entity_store.get_property_store::<E>();
239 property_store.is_property_indexed::<P>()
240 }
241
242 fn with_query_results<E: Entity, Q: Query<E>>(
243 &self,
244 query: Q,
245 callback: &mut dyn FnMut(&IndexSet<EntityId<E>>),
246 ) {
247 if let Some(multi_property_id) = query.multi_property_id() {
252 let property_store = self.entity_store.get_property_store::<E>();
253 match property_store.get_index_set_with_hash_for_property_id(
254 self,
255 multi_property_id,
256 query.multi_property_value_hash(),
257 ) {
258 IndexSetResult::Set(people_set) => {
259 callback(&people_set);
260 return;
261 }
262 IndexSetResult::Empty => {
263 let people_set = IndexSet::default();
264 callback(&people_set);
265 return;
266 }
267 IndexSetResult::Unsupported => {}
268 }
269 }
271
272 if query.type_id() == TypeId::of::<()>() {
274 warn!("Called Context::with_query_results() with an empty query. Prefer Context::get_entity_iterator::<E>() for working with the entire population.");
275 let entity_set = self.get_entity_iterator::<E>().collect::<IndexSet<_>>();
276 callback(&entity_set);
277 return;
278 }
279
280 warn!("Called Context::with_query_results() with an unindexed query. It's almost always better to use Context::query_result_iterator() for unindexed queries.");
282
283 let people_set = query
285 .new_query_result_iterator(self)
286 .collect::<IndexSet<_>>();
287 callback(&people_set);
288 }
289
290 fn query_entity_count<E: Entity, Q: Query<E>>(&self, query: Q) -> usize {
291 if let Some(multi_property_id) = query.multi_property_id() {
295 let property_store = self.entity_store.get_property_store::<E>();
296 match property_store.get_index_count_with_hash_for_property_id(
297 self,
298 multi_property_id,
299 query.multi_property_value_hash(),
300 ) {
301 IndexCountResult::Count(count) => return count,
302 IndexCountResult::Unsupported => {}
303 }
304 }
306
307 self.query_result_iterator(query).count()
308 }
309 fn sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> Option<EntityId<E>>
310 where
311 E: Entity,
312 Q: Query<E>,
313 R: RngId + 'static,
314 R::RngType: Rng,
315 {
316 let query_result = self.query_result_iterator(query);
317 self.sample(rng_id, move |rng| query_result.sample_entity(rng))
318 }
319
320 fn sample_entities<E, Q, R>(&self, rng_id: R, query: Q, n: usize) -> Vec<EntityId<E>>
321 where
322 E: Entity,
323 Q: Query<E>,
324 R: RngId + 'static,
325 R::RngType: Rng,
326 {
327 let query_result = self.query_result_iterator(query);
328 self.sample(rng_id, move |rng| query_result.sample_entities(rng, n))
329 }
330
331 fn get_entity_count<E: Entity>(&self) -> usize {
332 self.entity_store.get_entity_count::<E>()
333 }
334
335 fn get_entity_iterator<E: Entity>(&self) -> PopulationIterator<E> {
336 self.entity_store.get_entity_iterator::<E>()
337 }
338
339 fn query_result_iterator<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySetIterator<E> {
340 query.new_query_result_iterator(self)
341 }
342
343 fn match_entity<E: Entity, Q: Query<E>>(&self, entity_id: EntityId<E>, query: Q) -> bool {
344 query.match_entity(entity_id, self)
345 }
346
347 fn filter_entities<E: Entity, Q: Query<E>>(&self, entities: &mut Vec<EntityId<E>>, query: Q) {
348 query.filter_entities(entities, self);
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use std::cell::{Ref, RefCell};
355 use std::rc::Rc;
356
357 use super::*;
358 use crate::hashing::IndexSet;
359 use crate::prelude::PropertyChangeEvent;
360 use crate::{define_derived_property, define_entity, define_multi_property, define_property};
361
362 define_entity!(Animal);
363 define_property!(struct Legs(u8), Animal, default_const = Legs(4));
364
365 define_entity!(Person);
366
367 define_property!(struct Age(u8), Person);
368
369 define_property!(
370 enum InfectionStatus {
371 Susceptible,
372 Infected,
373 Recovered,
374 },
375 Person,
376 default_const = InfectionStatus::Susceptible
377 );
378
379 define_property!(
380 struct Vaccinated(bool),
381 Person,
382 default_const = Vaccinated(false)
383 );
384
385 define_derived_property!(
386 enum AgeGroup {
387 Child,
388 Adult,
389 Senior,
390 },
391 Person,
392 [Age],
393 |age| {
394 if age.0 <= 18 {
395 AgeGroup::Child
396 } else if age.0 <= 65 {
397 AgeGroup::Adult
398 } else {
399 AgeGroup::Senior
400 }
401 }
402 );
403
404 define_derived_property!(
405 enum RiskLevel {
406 Low,
407 Medium,
408 High,
409 },
410 Person,
411 [AgeGroup, Vaccinated, InfectionStatus],
412 |age_group, vaccinated, infection_status| {
413 match (age_group, vaccinated, infection_status) {
414 (AgeGroup::Senior, Vaccinated(false), InfectionStatus::Susceptible) => {
415 RiskLevel::High
416 }
417 (_, Vaccinated(false), InfectionStatus::Susceptible) => RiskLevel::Medium,
418 _ => RiskLevel::Low,
419 }
420 }
421 );
422
423 define_property!(struct IsRunner(bool), Person, default_const = IsRunner(false));
437 define_property!(struct IsSwimmer(bool), Person, default_const = IsSwimmer(false));
438 define_derived_property!(
439 struct AdultRunner(bool),
440 Person,
441 [AgeGroup, IsRunner],
442 | age_group, is_runner | {
443 AdultRunner(
444 age_group == AgeGroup::Adult
445 && is_runner.0
446 )
447 }
448 );
449 define_derived_property!(
450 struct AdultSwimmer(bool),
451 Person,
452 [AgeGroup, IsSwimmer],
453 | age_group, is_swimmer | {
454 AdultSwimmer(
455 age_group == AgeGroup::Adult
456 && is_swimmer.0
457 )
458 }
459 );
460 define_derived_property!(
461 struct AdultAthlete(bool),
462 Person,
463 [AdultSwimmer, AdultRunner],
464 | adult_swimmer, adult_runner | {
465 AdultAthlete(
466 adult_swimmer.0 || adult_runner.0
467 )
468 }
469 );
470
471 #[test]
472 fn add_and_count_entities() {
473 let mut context = Context::new();
474
475 let _person1 = context
476 .add_entity((Age(12), InfectionStatus::Susceptible, Vaccinated(true)))
477 .unwrap();
478 assert_eq!(context.get_entity_count::<Person>(), 1);
479
480 let _person2 = context.add_entity((Age(34), Vaccinated(true))).unwrap();
481 assert_eq!(context.get_entity_count::<Person>(), 2);
482
483 let _person3 = context.add_entity((Age(120),)).unwrap();
485 assert_eq!(context.get_entity_count::<Person>(), 3);
486 }
487
488 #[derive(Copy, Clone)]
490 enum IndexMode {
491 Unindexed,
492 FullIndex,
493 ValueCountIndex,
494 }
495
496 fn setup_context_for_index_tests(index_mode: IndexMode) -> (Context, Age, Age) {
498 let mut context = Context::new();
499 match index_mode {
500 IndexMode::Unindexed => {}
501 IndexMode::FullIndex => context.index_property::<Person, Age>(),
502 IndexMode::ValueCountIndex => context.index_property_counts::<Person, Age>(),
503 }
504
505 let existing_value = Age(12);
506 let missing_value = Age(99);
507
508 let _ = context.add_entity((existing_value,)).unwrap();
509 let _ = context.add_entity((existing_value,)).unwrap();
510
511 (context, existing_value, missing_value)
512 }
513
514 #[test]
515 fn query_results_respect_index_modes() {
516 let modes = [
517 IndexMode::Unindexed,
518 IndexMode::FullIndex,
519 IndexMode::ValueCountIndex,
520 ];
521
522 for mode in modes {
523 let (context, existing_value, missing_value) = setup_context_for_index_tests(mode);
524
525 let mut existing_len = 0;
526 context.with_query_results((existing_value,), &mut |people_set| {
527 existing_len = people_set.len();
528 });
529 assert_eq!(existing_len, 2);
530
531 let mut missing_len = 0;
532 context.with_query_results((missing_value,), &mut |people_set| {
533 missing_len = people_set.len();
534 });
535 assert_eq!(missing_len, 0);
536
537 let existing_count = context.query_result_iterator((existing_value,)).count();
538 assert_eq!(existing_count, 2);
539
540 let missing_count = context.query_result_iterator((missing_value,)).count();
541 assert_eq!(missing_count, 0);
542
543 assert_eq!(context.query_entity_count((existing_value,)), 2);
544 assert_eq!(context.query_entity_count((missing_value,)), 0);
545 }
546 }
547
548 #[test]
549 fn add_an_entity_without_required_properties() {
550 let mut context = Context::new();
551 let result = context.add_entity((InfectionStatus::Susceptible, Vaccinated(true)));
552
553 assert!(matches!(
554 result,
555 Err(crate::IxaError::IxaError(ref msg)) if msg == "initialization list is missing required properties"
556 ));
557 }
558
559 #[test]
560 fn new_entities_have_default_values() {
561 let mut context = Context::new();
562
563 let person = context.add_entity((Age(25),)).unwrap();
565
566 let age: Age = context.get_property(person);
568 assert_eq!(age, Age(25));
569 let infection_status: InfectionStatus = context.get_property(person);
570 assert_eq!(infection_status, InfectionStatus::Susceptible);
571 let vaccinated: Vaccinated = context.get_property(person);
572 assert_eq!(vaccinated, Vaccinated(false));
573
574 context.set_property(person, Age(26));
576 context.set_property(person, InfectionStatus::Infected);
577 context.set_property(person, Vaccinated(true));
578
579 let age: Age = context.get_property(person);
581 assert_eq!(age, Age(26));
582 let infection_status: InfectionStatus = context.get_property(person);
583 assert_eq!(infection_status, InfectionStatus::Infected);
584 let vaccinated: Vaccinated = context.get_property(person);
585 assert_eq!(vaccinated, Vaccinated(true));
586 }
587
588 #[test]
589 fn get_and_set_property_explicit() {
590 let mut context = Context::new();
591
592 let person = context
594 .add_entity((Age(25), InfectionStatus::Recovered, Vaccinated(true)))
595 .unwrap();
596
597 let age: Age = context.get_property(person);
599 assert_eq!(age, Age(25));
600 let infection_status: InfectionStatus = context.get_property(person);
601 assert_eq!(infection_status, InfectionStatus::Recovered);
602 let vaccinated: Vaccinated = context.get_property(person);
603 assert_eq!(vaccinated, Vaccinated(true));
604
605 context.set_property(person, Age(26));
607 context.set_property(person, InfectionStatus::Infected);
608 context.set_property(person, Vaccinated(false));
609
610 let age: Age = context.get_property(person);
612 assert_eq!(age, Age(26));
613 let infection_status: InfectionStatus = context.get_property(person);
614 assert_eq!(infection_status, InfectionStatus::Infected);
615 let vaccinated: Vaccinated = context.get_property(person);
616 assert_eq!(vaccinated, Vaccinated(false));
617 }
618
619 #[test]
620 fn count_entities() {
621 let mut context = Context::new();
622
623 assert_eq!(context.get_entity_count::<Animal>(), 0);
624 assert_eq!(context.get_entity_count::<Person>(), 0);
625
626 for _ in 0..7 {
628 let _: PersonId = context.add_entity((Age(25),)).unwrap();
629 }
630 for _ in 0..5 {
631 let _: AnimalId = context.add_entity((Legs(2),)).unwrap();
632 }
633
634 assert_eq!(context.get_entity_count::<Animal>(), 5);
635 assert_eq!(context.get_entity_count::<Person>(), 7);
636
637 let _: PersonId = context.add_entity((Age(30),)).unwrap();
638 let _: AnimalId = context.add_entity((Legs(8),)).unwrap();
639
640 assert_eq!(context.get_entity_count::<Animal>(), 6);
641 assert_eq!(context.get_entity_count::<Person>(), 8);
642 }
643
644 #[test]
645 fn get_derived_property_multiple_deps() {
646 let mut context = Context::new();
647
648 let expected_high_id: PersonId = context
649 .add_entity((Age(77), Vaccinated(false), InfectionStatus::Susceptible))
650 .unwrap();
651 let expected_med_id: PersonId = context
652 .add_entity((Age(30), Vaccinated(false), InfectionStatus::Susceptible))
653 .unwrap();
654 let expected_low_id: PersonId = context
655 .add_entity((Age(3), Vaccinated(true), InfectionStatus::Recovered))
656 .unwrap();
657
658 let actual_high: RiskLevel = context.get_property(expected_high_id);
659 assert_eq!(actual_high, RiskLevel::High);
660 let actual_med: RiskLevel = context.get_property(expected_med_id);
661 assert_eq!(actual_med, RiskLevel::Medium);
662 let actual_low: RiskLevel = context.get_property(expected_low_id);
663 assert_eq!(actual_low, RiskLevel::Low);
664 }
665
666 #[test]
667 fn listen_to_derived_property_change_event() {
668 let mut context = Context::new();
669
670 let expected_high_id = PersonId::new(0);
671
672 let risk_flag = Rc::new(RefCell::new(0));
675 let risk_flag_clone = risk_flag.clone();
676 context.subscribe_to_event(
677 move |_context, event: PropertyChangeEvent<Person, RiskLevel>| {
678 assert_eq!(event.entity_id, expected_high_id);
679 assert_eq!(event.previous, RiskLevel::High);
680 assert_eq!(event.current, RiskLevel::Medium);
681 *risk_flag_clone.borrow_mut() += 1;
682 },
683 );
684 let age_group_flag = Rc::new(RefCell::new(0));
686 let age_group_flag_clone = age_group_flag.clone();
687 context.subscribe_to_event(
688 move |_context, event: PropertyChangeEvent<Person, AgeGroup>| {
689 assert_eq!(event.entity_id, expected_high_id);
690 assert_eq!(event.previous, AgeGroup::Senior);
691 assert_eq!(event.current, AgeGroup::Adult);
692 *age_group_flag_clone.borrow_mut() += 1;
693 },
694 );
695
696 let expected_high_id: PersonId = context
698 .add_entity((Age(77), Vaccinated(false), InfectionStatus::Susceptible))
699 .unwrap();
700
701 context.set_property(expected_high_id, Age(20));
703
704 context.execute();
706 assert_eq!(*risk_flag.borrow(), 1);
708 assert_eq!(*age_group_flag.borrow(), 1);
709 }
710
711 #[test]
731 fn observe_diamond_property_change() {
732 let mut context = Context::new();
733 let person = context.add_entity((Age(17), IsSwimmer(true))).unwrap();
734
735 let is_adult_athlete: AdultAthlete = context.get_property(person);
736 assert!(!is_adult_athlete.0);
737
738 let flag = Rc::new(RefCell::new(0));
739 let flag_clone = flag.clone();
740 context.subscribe_to_event(
741 move |_context, event: PropertyChangeEvent<Person, AdultAthlete>| {
742 assert_eq!(event.entity_id, person);
743 assert_eq!(event.previous, AdultAthlete(false));
744 assert_eq!(event.current, AdultAthlete(true));
745 *flag_clone.borrow_mut() += 1;
746 },
747 );
748
749 context.set_property(person, Age(20));
750 let is_adult_athlete: AdultAthlete = context.get_property(person);
752 assert!(is_adult_athlete.0);
753
754 context.execute();
756 assert_eq!(*flag.borrow(), 1);
758 }
759
760 define_multi_property!((InfectionStatus, Vaccinated), Person);
763 define_multi_property!((Vaccinated, InfectionStatus), Person);
764
765 #[test]
766 fn with_query_results_finds_multi_index() {
767 use crate::rand::seq::IndexedRandom;
768 let mut rng = crate::rand::rng();
769 let mut context = Context::new();
770
771 for _ in 0..10_000usize {
772 let infection_status = *[
773 InfectionStatus::Susceptible,
774 InfectionStatus::Infected,
775 InfectionStatus::Recovered,
776 ]
777 .choose(&mut rng)
778 .unwrap();
779 let vaccination_status: bool = rng.random_bool(0.5);
780 let age: u8 = rng.random_range(0..100);
781 context
782 .add_entity((Age(age), infection_status, Vaccinated(vaccination_status)))
783 .unwrap();
784 }
785 context.index_property::<Person, InfectionStatusVaccinated>();
786 let _ = context.query_result_iterator((InfectionStatus::Susceptible, Vaccinated(true)));
788
789 let mut address: *const IndexSet<EntityId<Person>> = std::ptr::null();
791 context.with_query_results(
792 (InfectionStatus::Susceptible, Vaccinated(true)),
793 &mut |result_set| {
794 address = result_set as *const _;
795 },
796 );
797
798 assert_eq!(
800 InfectionStatusVaccinated::index_id(),
801 VaccinatedInfectionStatus::index_id()
802 );
803 assert_eq!(
804 InfectionStatusVaccinated::index_id(),
805 (InfectionStatus::Susceptible, Vaccinated(true))
806 .multi_property_id()
807 .unwrap()
808 );
809
810 let index_id = InfectionStatusVaccinated::index_id();
812
813 let property_store = context.entity_store.get_property_store::<Person>();
814 let property_value_store = property_store.get_with_id(index_id);
815 let bucket: Ref<IndexSet<EntityId<Person>>> = property_value_store
816 .get_index_set_with_hash(
817 (InfectionStatus::Susceptible, Vaccinated(true)).multi_property_value_hash(),
818 )
819 .unwrap();
820
821 let address2 = &*bucket as *const _;
822 assert_eq!(address2, address);
823 }
824
825 #[test]
826 fn set_property_correctly_maintains_index() {
827 let mut context = Context::new();
828 context.index_property::<Person, InfectionStatus>();
829 context.index_property::<Person, AgeGroup>();
830
831 let person1 = context.add_entity((Age(22),)).unwrap();
832 let person2 = context.add_entity((Age(22),)).unwrap();
833 for _ in 0..4 {
834 let _: PersonId = context.add_entity((Age(22),)).unwrap();
835 }
836
837 assert_eq!(
839 context.query_entity_count((InfectionStatus::Susceptible,)),
840 6
841 );
842 assert_eq!(context.query_entity_count((InfectionStatus::Infected,)), 0);
843 assert_eq!(context.query_entity_count((InfectionStatus::Recovered,)), 0);
844
845 context.set_property(person1, InfectionStatus::Infected);
846
847 assert_eq!(
848 context.query_entity_count((InfectionStatus::Susceptible,)),
849 5
850 );
851 assert_eq!(context.query_entity_count((InfectionStatus::Infected,)), 1);
852 assert_eq!(context.query_entity_count((InfectionStatus::Recovered,)), 0);
853
854 context.set_property(person1, InfectionStatus::Recovered);
855
856 assert_eq!(
857 context.query_entity_count((InfectionStatus::Susceptible,)),
858 5
859 );
860 assert_eq!(context.query_entity_count((InfectionStatus::Infected,)), 0);
861 assert_eq!(context.query_entity_count((InfectionStatus::Recovered,)), 1);
862
863 assert_eq!(context.query_entity_count((AgeGroup::Child,)), 0);
865 assert_eq!(context.query_entity_count((AgeGroup::Adult,)), 6);
866 assert_eq!(context.query_entity_count((AgeGroup::Senior,)), 0);
867
868 context.set_property(person2, Age(12));
869
870 assert_eq!(context.query_entity_count((AgeGroup::Child,)), 1);
871 assert_eq!(context.query_entity_count((AgeGroup::Adult,)), 5);
872 assert_eq!(context.query_entity_count((AgeGroup::Senior,)), 0);
873
874 context.set_property(person1, Age(75));
875
876 assert_eq!(context.query_entity_count((AgeGroup::Child,)), 1);
877 assert_eq!(context.query_entity_count((AgeGroup::Adult,)), 4);
878 assert_eq!(context.query_entity_count((AgeGroup::Senior,)), 1);
879
880 context.set_property(person2, Age(77));
881
882 assert_eq!(context.query_entity_count((AgeGroup::Child,)), 0);
883 assert_eq!(context.query_entity_count((AgeGroup::Adult,)), 4);
884 assert_eq!(context.query_entity_count((AgeGroup::Senior,)), 2);
885 }
886
887 #[test]
888 fn query_unindexed_default_properties() {
889 let mut context = Context::new();
890
891 for idx in 0..10 {
893 if idx % 2 == 0 {
894 context.add_entity((Age(22),)).unwrap();
895 } else {
896 context
897 .add_entity((Age(22), InfectionStatus::Recovered))
898 .unwrap();
899 }
900 }
901 for _ in 0..10 {
903 let _: PersonId = context.add_entity((Age(22),)).unwrap();
904 }
905
906 assert_eq!(context.query_entity_count((InfectionStatus::Recovered,)), 5);
907 assert_eq!(
908 context.query_entity_count((InfectionStatus::Susceptible,)),
909 15
910 );
911 }
912
913 #[test]
914 fn query_unindexed_derived_properties() {
915 let mut context = Context::new();
916
917 for _ in 0..10 {
918 let _: PersonId = context.add_entity((Age(22),)).unwrap();
919 }
920
921 assert_eq!(context.query_entity_count((AdultAthlete(false),)), 10);
922 }
923}