1use std::hash::Hash;
2
3use smallvec::SmallVec;
4
5use crate::entity::entity_set::{EntitySet, EntitySetIterator, SourceSet};
6use crate::entity::events::{EntityCreatedEvent, PartialPropertyChangeEventBox};
7use crate::entity::index::{IndexCountResult, IndexSetResult, PropertyIndexType};
8use crate::entity::multi_property::multi_property_id_for_property_type_id;
9use crate::entity::property::{IndexableProperty, Property};
10use crate::entity::property_list::{PropertyInitializationList, PropertyList};
11use crate::entity::query::Query;
12use crate::entity::value_change_counter::StratifiedValueChangeCounter;
13use crate::entity::{Entity, EntityId, PopulationIterator};
14use crate::rand::{Rng, RngExt};
15use crate::random::sample_multiple_from_known_length;
16use crate::{warn, Context, ContextRandomExt, ExecutionPhase, IxaError, RngId};
17
18fn create_property_index<E, P>(context: &mut Context, requested: PropertyIndexType)
19where
20 E: Entity,
21 P: IndexableProperty<E>,
22{
23 debug_assert_ne!(
24 requested,
25 PropertyIndexType::Unindexed,
26 "Ixa internal error: create_property_index received an unindexed request",
27 );
28
29 if let Some((representative_id, representative_name)) =
30 multi_property_id_for_property_type_id(E::id(), P::type_id())
31 {
32 if representative_id != P::id() {
33 panic!(
34 "Cannot index multi-property {} because it is equivalent to representative \
35 multi-property {}.",
36 P::name(),
37 representative_name,
38 );
39 }
40 }
41
42 let current = context.get_property_value_store::<E, P>().index_type();
43 let current_satisfies_request = current == requested
44 || matches!(
45 (current, requested),
46 (
47 PropertyIndexType::FullIndex,
48 PropertyIndexType::ValueCountIndex,
49 )
50 );
51
52 if current_satisfies_request {
53 return;
54 }
55
56 let mut new_index = requested
57 .new_property_index::<E, P>()
58 .expect("Ixa internal error: an indexed request must construct an index");
59
60 for entity_id in context.get_entity_iterator::<E>() {
63 let value: P = context.get_property(entity_id);
64 new_index.add_entity(&value, entity_id);
65 }
66
67 context
68 .entity_store
69 .get_property_store_mut::<E>()
70 .install_property_index::<P>(Some(new_index));
71}
72
73fn handle_periodic_value_change_count_event<E, PL, P, F>(
74 context: &mut Context,
75 period: f64,
76 counter_id: usize,
77 handler: F,
78) where
79 E: Entity,
80 PL: PropertyList<E> + Eq + Hash,
81 P: IndexableProperty<E>,
82 F: Fn(&mut Context, &mut StratifiedValueChangeCounter<E, PL, P>) + 'static,
83{
84 let mut counter = {
85 let property_value_store = context.get_property_value_store_mut::<E, P>();
86 let slot = property_value_store
87 .value_change_counters
88 .get_mut(counter_id)
89 .unwrap_or_else(|| {
90 panic!(
91 "Ixa internal error: no value change counter found for property {} with \
92 counter_id {}",
93 P::name(),
94 counter_id
95 )
96 });
97 std::mem::replace(
98 slot.get_mut(),
99 Box::new(StratifiedValueChangeCounter::<E, PL, P>::new()),
100 )
101 };
102
103 {
104 let counter = counter
105 .as_any_mut()
106 .downcast_mut::<StratifiedValueChangeCounter<E, PL, P>>()
107 .unwrap_or_else(|| {
108 panic!(
109 "Ixa internal error: value change counter for property {} and counter_id {} \
110 had unexpected type",
111 P::name(),
112 counter_id
113 )
114 });
115
116 handler(context, counter);
117 counter.clear();
118 }
119
120 {
121 let property_value_store = context.get_property_value_store_mut::<E, P>();
122 let slot = property_value_store
123 .value_change_counters
124 .get_mut(counter_id)
125 .unwrap_or_else(|| {
126 panic!(
127 "Ixa internal error: no value change counter found for property {} with \
128 counter_id {}",
129 P::name(),
130 counter_id
131 )
132 });
133
134 let _ = std::mem::replace(slot.get_mut(), counter);
136 }
137
138 let next_time = context.get_current_time() + period;
139 context.add_passive_plan_with_phase(
140 next_time,
141 move |context| {
142 handle_periodic_value_change_count_event::<E, PL, P, F>(
143 context, period, counter_id, handler,
144 );
145 },
146 ExecutionPhase::Last,
147 );
148}
149
150pub trait ContextEntitiesExt {
153 fn add_entity<E: Entity, PL: PropertyInitializationList<E>>(
154 &mut self,
155 property_list: PL,
156 ) -> Result<EntityId<E>, IxaError>;
157
158 #[must_use]
165 fn get_property<E: Entity, P: Property<E>>(&self, entity_id: EntityId<E>) -> P;
166
167 fn set_property<E: Entity, P: Property<E>>(
173 &mut self,
174 entity_id: EntityId<E>,
175 property_value: P,
176 );
177
178 fn index_property<E: Entity, P: IndexableProperty<E>>(&mut self);
190
191 fn index_property_counts<E: Entity, P: IndexableProperty<E>>(&mut self);
201
202 fn track_periodic_value_change_counts<E, PL, P, F>(
226 &mut self,
227 period: impl Into<f64>,
228 handler: F,
229 ) where
230 E: Entity,
231 PL: PropertyList<E> + Eq + Hash,
232 P: Property<E> + Eq + Hash,
233 F: Fn(&mut Context, &mut StratifiedValueChangeCounter<E, PL, P>) + 'static;
234
235 #[cfg(test)]
243 #[must_use]
244 fn is_property_indexed<E: Entity, P: Property<E>>(&self) -> bool;
245
246 fn with_query_results<'a, E: Entity, Q: Query<E>>(
250 &'a self,
251 query: Q,
252 callback: &mut dyn FnMut(EntitySet<'a, E>),
253 );
254
255 #[must_use]
260 fn query_entity_count<E: Entity, Q: Query<E>>(&self, query: Q) -> usize;
261
262 #[must_use]
267 fn sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> Option<EntityId<E>>
268 where
269 E: Entity,
270 Q: Query<E>,
271 R: RngId + 'static,
272 R::RngType: Rng;
273
274 #[must_use]
279 fn count_and_sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> (usize, Option<EntityId<E>>)
280 where
281 E: Entity,
282 Q: Query<E>,
283 R: RngId + 'static,
284 R::RngType: Rng;
285
286 #[must_use]
292 fn sample_entities<E, Q, R>(&self, rng_id: R, query: Q, n: usize) -> Vec<EntityId<E>>
293 where
294 E: Entity,
295 Q: Query<E>,
296 R: RngId + 'static,
297 R::RngType: Rng;
298
299 #[must_use]
301 fn get_entity_count<E: Entity>(&self) -> usize;
302
303 #[must_use]
305 fn get_entity_iterator<E: Entity>(&self) -> PopulationIterator<E>;
306
307 #[must_use]
309 fn query<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySet<E>;
310
311 #[must_use]
313 fn query_result_iterator<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySetIterator<E>;
314
315 #[must_use]
317 fn match_entity<E: Entity, Q: Query<E>>(&self, entity_id: EntityId<E>, query: Q) -> bool;
318
319 fn filter_entities<E: Entity, Q: Query<E>>(&self, entities: &mut Vec<EntityId<E>>, query: Q);
321}
322
323impl ContextEntitiesExt for Context {
324 fn add_entity<E: Entity, PL: PropertyInitializationList<E>>(
325 &mut self,
326 property_list: PL,
327 ) -> Result<EntityId<E>, IxaError> {
328 PL::validate()?;
330
331 if !PL::contains_required_properties() {
333 return Err(IxaError::MissingRequiredInitializationProperties);
334 }
335
336 let (new_entity_id, entity_created_event_subscribed) =
338 self.entity_store.new_entity_id::<E>();
339
340 property_list.set_values_for_new_entity(
343 new_entity_id,
344 self.entity_store.get_property_store_mut::<E>(),
345 );
346
347 let (index_count, property_initialized_event_subscriptions) = {
353 let property_store = self.entity_store.get_property_store::<E>();
354 (
355 property_store.index_new_entity_fns.len(),
356 property_store
357 .property_initialized_event_subscriptions
358 .clone_if_nonempty(),
359 )
360 };
361
362 for index in 0..index_count {
363 let dispatch = self
364 .entity_store
365 .get_property_store::<E>()
366 .index_new_entity_fns[index]
367 .1;
368
369 dispatch(self, new_entity_id);
372 }
373
374 if let Some(property_initialized_event_subscriptions) =
375 property_initialized_event_subscriptions
376 {
377 property_list.emit_initialized_events(
378 self,
379 new_entity_id,
380 &property_initialized_event_subscriptions,
381 );
382 }
383
384 if entity_created_event_subscribed {
385 self.emit_event(EntityCreatedEvent::<E>::new(new_entity_id));
386 }
387
388 Ok(new_entity_id)
389 }
390
391 fn get_property<E: Entity, P: Property<E>>(&self, entity_id: EntityId<E>) -> P {
392 if P::is_derived() {
393 P::compute_derived(self, entity_id)
394 } else {
395 let property_store = self.get_property_value_store::<E, P>();
396 property_store.get(entity_id)
397 }
398 }
399
400 fn set_property<E: Entity, P: Property<E>>(
401 &mut self,
402 entity_id: EntityId<E>,
403 property_value: P,
404 ) {
405 debug_assert!(!P::is_derived(), "cannot set a derived property");
406
407 let mut dependents: SmallVec<[PartialPropertyChangeEventBox; 5]> = SmallVec::new();
440
441 {
443 let property_store = self.entity_store.get_property_store::<E>();
444
445 if property_store.should_create_partial_property_change(P::id(), self) {
447 dependents.push(property_store.create_partial_property_change(
448 P::id(),
449 entity_id,
450 self,
451 ));
452 }
453 for dependent_idx in P::dependents() {
455 if property_store.should_create_partial_property_change(*dependent_idx, self) {
456 dependents.push(property_store.create_partial_property_change(
457 *dependent_idx,
458 entity_id,
459 self,
460 ));
461 }
462 }
463 }
464
465 let property_value_store = self.get_property_value_store_mut::<E, P>();
467 property_value_store.set(entity_id, property_value);
468
469 for mut dependent in dependents {
472 dependent.emit_in_context(self)
473 }
474 }
475
476 fn index_property<E: Entity, P: IndexableProperty<E>>(&mut self) {
477 create_property_index::<E, P>(self, PropertyIndexType::FullIndex);
478 }
479
480 fn index_property_counts<E: Entity, P: IndexableProperty<E>>(&mut self) {
481 create_property_index::<E, P>(self, PropertyIndexType::ValueCountIndex);
482 }
483
484 fn track_periodic_value_change_counts<E, PL, P, F>(
485 &mut self,
486 period: impl Into<f64>,
487 handler: F,
488 ) where
489 E: Entity,
490 PL: PropertyList<E> + Eq + Hash,
491 P: IndexableProperty<E>,
492 F: Fn(&mut Context, &mut StratifiedValueChangeCounter<E, PL, P>) + 'static,
493 {
494 let period = period.into();
495 assert!(
496 period > 0.0 && !period.is_nan() && !period.is_infinite(),
497 "Period must be greater than 0"
498 );
499 let start_time = self.get_start_time().unwrap_or(0.0);
500 self.add_plan_with_phase(
501 start_time,
502 move |context| {
503 let counter_id = context
506 .entity_store
507 .get_property_store_mut::<E>()
508 .create_value_change_counter::<PL, P>();
509
510 context.add_passive_plan_with_phase(
513 context.get_current_time(),
514 move |context| {
515 handle_periodic_value_change_count_event::<E, PL, P, F>(
516 context, period, counter_id, handler,
517 );
518 },
519 ExecutionPhase::Last,
520 );
521 },
522 ExecutionPhase::First,
523 );
524 }
525
526 #[cfg(test)]
527 fn is_property_indexed<E: Entity, P: Property<E>>(&self) -> bool {
528 let property_store = self.entity_store.get_property_store::<E>();
529 property_store.is_property_indexed::<P>()
530 }
531
532 fn with_query_results<'a, E: Entity, Q: Query<E>>(
533 &'a self,
534 query: Q,
535 callback: &mut dyn FnMut(EntitySet<'a, E>),
536 ) {
537 let resolved = query.resolved_query();
538 #[cfg(feature = "profiling")]
539 let profile = self.query_profile_handle(resolved.identity);
540
541 if let Some(multi_property_id) = resolved.index_property_id {
546 let property_store = self.entity_store.get_property_store::<E>();
547 let query_parts = query.query_parts();
548 let lookup_result = property_store
549 .get_index_set_for_query_parts(multi_property_id, query_parts.as_ref());
550 match lookup_result {
551 IndexSetResult::Set(people_set) => {
552 let result = EntitySet::from_source(SourceSet::IndexSet(people_set));
553 #[cfg(feature = "profiling")]
554 let result = result.with_query_profile(profile);
555 callback(result);
556 return;
557 }
558 IndexSetResult::Empty => {
559 let result = EntitySet::empty();
560 #[cfg(feature = "profiling")]
561 let result = result.with_query_profile(profile);
562 callback(result);
563 return;
564 }
565 IndexSetResult::Unsupported => {}
566 }
567 }
569
570 if query.is_empty_query() {
572 warn!("Called Context::with_query_results() with an empty query. Prefer Context::get_entity_iterator::<E>() for working with the entire population.");
573 let result =
574 EntitySet::from_source(SourceSet::PopulationRange(0..self.get_entity_count::<E>()));
575 #[cfg(feature = "profiling")]
576 let result = result.with_query_profile(profile);
577 callback(result);
578 return;
579 }
580
581 warn!("Called Context::with_query_results() with an unindexed query. It's almost always better to use Context::query_result_iterator() for unindexed queries.");
583
584 let result = query.new_query_result(self);
585 #[cfg(feature = "profiling")]
586 let result = result.with_query_profile(profile);
587 callback(result);
588 }
589
590 fn query_entity_count<E: Entity, Q: Query<E>>(&self, query: Q) -> usize {
591 let resolved = query.resolved_query();
592 #[cfg(feature = "profiling")]
593 let _query_profile_scope = self.query_profile_handle(resolved.identity).scope();
594
595 if let Some(multi_property_id) = resolved.index_property_id {
599 let property_store = self.entity_store.get_property_store::<E>();
600 let query_parts = query.query_parts();
601 let lookup_result = property_store
602 .get_index_count_for_query_parts(multi_property_id, query_parts.as_ref());
603 match lookup_result {
604 IndexCountResult::Count(count) => return count,
605 IndexCountResult::Unsupported => {}
606 }
607 }
609
610 query.new_query_result_iterator(self).count()
611 }
612
613 #[inline]
614 fn sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> Option<EntityId<E>>
615 where
616 E: Entity,
617 Q: Query<E>,
618 R: RngId + 'static,
619 R::RngType: Rng,
620 {
621 #[cfg(feature = "profiling")]
622 let _query_profile_scope = self
623 .query_profile_handle(query.resolved_query().identity)
624 .scope();
625
626 if query.is_empty_query() {
627 let population = self.get_entity_count::<E>();
628 return self.sample(rng_id, move |rng| {
629 if population == 0 {
630 warn!("Requested a sample entity from an empty population");
631 return None;
632 }
633 let index = if population <= u32::MAX as usize {
634 rng.random_range(0..population as u32) as usize
635 } else {
636 rng.random_range(0..population)
637 };
638 Some(EntityId::new(index))
639 });
640 }
641
642 let query_result = query.new_query_result(self);
643 self.sample(rng_id, move |rng| query_result.sample_entity(rng))
644 }
645
646 fn count_and_sample_entity<E, Q, R>(&self, rng_id: R, query: Q) -> (usize, Option<EntityId<E>>)
647 where
648 E: Entity,
649 Q: Query<E>,
650 R: RngId + 'static,
651 R::RngType: Rng,
652 {
653 #[cfg(feature = "profiling")]
654 let _query_profile_scope = self
655 .query_profile_handle(query.resolved_query().identity)
656 .scope();
657
658 if query.is_empty_query() {
659 let population = self.get_entity_count::<E>();
660 return self.sample(rng_id, move |rng| {
661 if population == 0 {
662 return (0, None);
663 }
664 let index = if population <= u32::MAX as usize {
665 rng.random_range(0..population as u32) as usize
666 } else {
667 rng.random_range(0..population)
668 };
669 (population, Some(EntityId::new(index)))
670 });
671 }
672
673 let query_result = query.new_query_result(self);
674 self.sample(rng_id, move |rng| query_result.count_and_sample_entity(rng))
675 }
676
677 fn sample_entities<E, Q, R>(&self, rng_id: R, query: Q, n: usize) -> Vec<EntityId<E>>
678 where
679 E: Entity,
680 Q: Query<E>,
681 R: RngId + 'static,
682 R::RngType: Rng,
683 {
684 #[cfg(feature = "profiling")]
685 let _query_profile_scope = self
686 .query_profile_handle(query.resolved_query().identity)
687 .scope();
688
689 if query.is_empty_query() {
690 let population = self.get_entity_count::<E>();
691 return self.sample(rng_id, move |rng| {
692 if population == 0 {
693 warn!("Requested a sample of entities from an empty population");
694 return vec![];
695 }
696 if n >= population {
697 return PopulationIterator::<E>::new(population).collect();
698 }
699 sample_multiple_from_known_length(rng, PopulationIterator::<E>::new(population), n)
700 });
701 }
702
703 let query_result = query.new_query_result(self);
704 self.sample(rng_id, move |rng| query_result.sample_entities(rng, n))
705 }
706
707 fn get_entity_count<E: Entity>(&self) -> usize {
708 self.entity_store.get_entity_count::<E>()
709 }
710
711 fn get_entity_iterator<E: Entity>(&self) -> PopulationIterator<E> {
712 self.entity_store.get_entity_iterator::<E>()
713 }
714
715 fn query<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySet<E> {
716 #[cfg(feature = "profiling")]
717 let profile = self.query_profile_handle(query.resolved_query().identity);
718 let result = query.new_query_result(self);
719 #[cfg(feature = "profiling")]
720 let result = result.with_query_profile(profile);
721 result
722 }
723
724 fn query_result_iterator<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySetIterator<E> {
725 #[cfg(feature = "profiling")]
726 let profile = self.query_profile_handle(query.resolved_query().identity);
727 let result = query.new_query_result_iterator(self);
728 #[cfg(feature = "profiling")]
729 let result = result.with_query_profile(profile);
730 result
731 }
732
733 fn match_entity<E: Entity, Q: Query<E>>(&self, entity_id: EntityId<E>, query: Q) -> bool {
734 #[cfg(feature = "profiling")]
735 let _query_profile_scope = self
736 .query_profile_handle(query.resolved_query().identity)
737 .scope();
738 query.match_entity(entity_id, self)
739 }
740
741 fn filter_entities<E: Entity, Q: Query<E>>(&self, entities: &mut Vec<EntityId<E>>, query: Q) {
742 #[cfg(feature = "profiling")]
743 let _query_profile_scope = self
744 .query_profile_handle(query.resolved_query().identity)
745 .scope();
746 query.filter_entities(entities, self);
747 }
748}
749
750#[cfg(test)]
751mod tests {
752 use std::cell::RefCell;
753 use std::rc::Rc;
754 #[cfg(feature = "profiling")]
755 use std::thread;
756 #[cfg(feature = "profiling")]
757 use std::time::Duration;
758
759 use super::*;
760 #[cfg(feature = "profiling")]
761 use crate::entity::multi_property::QueryIdentityId;
762 use crate::entity::query::QueryInternal;
763 use crate::hashing::IndexSet;
764 use crate::prelude::PropertyChangeEvent;
765 use crate::{
766 define_derived_property, define_entity, define_multi_property, define_property, define_rng,
767 impl_property, with,
768 };
769
770 define_entity!(Animal);
771 define_property!(struct Legs(u8), Animal, default_const = Legs(4));
772 define_rng!(EntityContextTestRng);
773
774 define_entity!(Person);
775
776 define_property!(struct Age(u8), Person);
777
778 #[cfg(feature = "profiling")]
779 define_entity!(ProfilingPerson);
780 #[cfg(feature = "profiling")]
781 define_property!(struct ProfilingAge(u8), ProfilingPerson);
782 #[cfg(feature = "profiling")]
783 define_property!(struct ProfilingCounty(u8), ProfilingPerson);
784 #[cfg(feature = "profiling")]
785 define_entity!(ProfilingBoundaryPerson);
786 #[cfg(feature = "profiling")]
787 define_property!(struct ProfilingBoundaryAge(u8), ProfilingBoundaryPerson);
788 #[cfg(feature = "profiling")]
789 define_entity!(ProfilingCallbackPerson);
790 #[cfg(feature = "profiling")]
791 define_property!(struct ProfilingCallbackAge(u8), ProfilingCallbackPerson);
792 #[cfg(feature = "profiling")]
793 define_entity!(ProfilingIdlePerson);
794 #[cfg(feature = "profiling")]
795 define_property!(struct ProfilingIdleAge(u8), ProfilingIdlePerson);
796 #[cfg(feature = "profiling")]
797 define_entity!(ProfilingUnusedIteratorPerson);
798 #[cfg(feature = "profiling")]
799 define_property!(struct ProfilingUnusedIteratorAge(u8), ProfilingUnusedIteratorPerson);
800 #[cfg(feature = "profiling")]
801 define_entity!(ProfilingContainsPerson);
802 #[cfg(feature = "profiling")]
803 define_property!(struct ProfilingContainsAge(u8), ProfilingContainsPerson);
804 #[cfg(feature = "profiling")]
805 define_entity!(ProfilingIndexedCountPerson);
806 #[cfg(feature = "profiling")]
807 define_property!(struct ProfilingIndexedCountAge(u8), ProfilingIndexedCountPerson);
808 #[cfg(feature = "profiling")]
809 define_entity!(ProfilingUnindexedCountPerson);
810 #[cfg(feature = "profiling")]
811 define_property!(
812 struct ProfilingUnindexedCountAge(u8),
813 ProfilingUnindexedCountPerson
814 );
815 #[cfg(feature = "profiling")]
816 define_entity!(ProfilingIndexedIteratorPerson);
817 #[cfg(feature = "profiling")]
818 define_property!(
819 struct ProfilingIndexedIteratorAge(u8),
820 ProfilingIndexedIteratorPerson
821 );
822 #[cfg(feature = "profiling")]
823 define_entity!(ProfilingUnindexedIteratorPerson);
824 #[cfg(feature = "profiling")]
825 define_property!(
826 struct ProfilingUnindexedIteratorAge(u8),
827 ProfilingUnindexedIteratorPerson
828 );
829 #[cfg(feature = "profiling")]
830 define_entity!(ProfilingIndexedWithResultsPerson);
831 #[cfg(feature = "profiling")]
832 define_property!(
833 struct ProfilingIndexedWithResultsAge(u8),
834 ProfilingIndexedWithResultsPerson
835 );
836 #[cfg(feature = "profiling")]
837 define_entity!(ProfilingIndexedMatchPerson);
838 #[cfg(feature = "profiling")]
839 define_property!(
840 struct ProfilingIndexedMatchAge(u8),
841 ProfilingIndexedMatchPerson
842 );
843 #[cfg(feature = "profiling")]
844 define_entity!(ProfilingIndexedFilterPerson);
845 #[cfg(feature = "profiling")]
846 define_property!(
847 struct ProfilingIndexedFilterAge(u8),
848 ProfilingIndexedFilterPerson
849 );
850 #[cfg(feature = "profiling")]
851 define_property!(
852 struct ProfilingIndexedFilterCounty(u8),
853 ProfilingIndexedFilterPerson
854 );
855 #[cfg(feature = "profiling")]
856 define_multi_property!(
857 ProfilingIndexedFilterPerson,
858 (ProfilingIndexedFilterAge, ProfilingIndexedFilterCounty)
859 );
860 #[cfg(feature = "profiling")]
861 define_entity!(ProfilingSingleFilterPerson);
862 #[cfg(feature = "profiling")]
863 define_property!(
864 struct ProfilingSingleFilterAge(u8),
865 ProfilingSingleFilterPerson
866 );
867 #[cfg(feature = "profiling")]
868 define_entity!(ProfilingComposedPerson);
869 #[cfg(feature = "profiling")]
870 define_property!(
871 struct ProfilingComposedAge(u8),
872 ProfilingComposedPerson
873 );
874 #[cfg(feature = "profiling")]
875 define_property!(
876 struct ProfilingComposedCounty(u8),
877 ProfilingComposedPerson
878 );
879
880 #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
881 struct CounterValue(u8);
882 impl_property!(CounterValue, Person, default_const = CounterValue(0));
883
884 #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
885 struct CounterStratum(bool);
886 impl_property!(
887 CounterStratum,
888 Person,
889 default_const = CounterStratum(false)
890 );
891
892 #[derive(Clone, Copy)]
893 struct WrappedF64(f64);
894
895 impl From<WrappedF64> for f64 {
896 fn from(value: WrappedF64) -> Self {
897 value.0
898 }
899 }
900
901 define_property!(
902 enum InfectionStatus {
903 Susceptible,
904 Infected,
905 Recovered,
906 },
907 Person,
908 default_const = InfectionStatus::Susceptible
909 );
910
911 define_property!(
912 struct Vaccinated(bool),
913 Person,
914 default_const = Vaccinated(false)
915 );
916
917 define_derived_property!(
918 enum AgeGroup {
919 Child,
920 Adult,
921 Senior,
922 },
923 Person,
924 [Age],
925 |age| {
926 if age.0 <= 18 {
927 AgeGroup::Child
928 } else if age.0 <= 65 {
929 AgeGroup::Adult
930 } else {
931 AgeGroup::Senior
932 }
933 }
934 );
935
936 define_derived_property!(
937 enum RiskLevel {
938 Low,
939 Medium,
940 High,
941 },
942 Person,
943 [AgeGroup, Vaccinated, InfectionStatus],
944 |age_group, vaccinated, infection_status| {
945 match (age_group, vaccinated, infection_status) {
946 (AgeGroup::Senior, Vaccinated(false), InfectionStatus::Susceptible) => {
947 RiskLevel::High
948 }
949 (_, Vaccinated(false), InfectionStatus::Susceptible) => RiskLevel::Medium,
950 _ => RiskLevel::Low,
951 }
952 }
953 );
954
955 define_property!(struct IsRunner(bool), Person, default_const = IsRunner(false));
969 define_property!(struct IsSwimmer(bool), Person, default_const = IsSwimmer(false));
970 define_derived_property!(
971 struct AdultRunner(bool),
972 Person,
973 [AgeGroup, IsRunner],
974 | age_group, is_runner | {
975 AdultRunner(
976 age_group == AgeGroup::Adult
977 && is_runner.0
978 )
979 }
980 );
981 define_derived_property!(
982 struct AdultSwimmer(bool),
983 Person,
984 [AgeGroup, IsSwimmer],
985 | age_group, is_swimmer | {
986 AdultSwimmer(
987 age_group == AgeGroup::Adult
988 && is_swimmer.0
989 )
990 }
991 );
992 define_derived_property!(
993 struct AdultAthlete(bool),
994 Person,
995 [AdultSwimmer, AdultRunner],
996 | adult_swimmer, adult_runner | {
997 AdultAthlete(
998 adult_swimmer.0 || adult_runner.0
999 )
1000 }
1001 );
1002
1003 #[test]
1004 fn add_and_count_entities() {
1005 let mut context = Context::new();
1006
1007 let _person1 = context
1008 .add_entity(with!(
1009 Person,
1010 Age(12),
1011 InfectionStatus::Susceptible,
1012 Vaccinated(true)
1013 ))
1014 .unwrap();
1015 assert_eq!(context.get_entity_count::<Person>(), 1);
1016
1017 let _person2 = context
1018 .add_entity(with!(Person, Age(34), Vaccinated(true)))
1019 .unwrap();
1020 assert_eq!(context.get_entity_count::<Person>(), 2);
1021
1022 let _person3 = context.add_entity(with!(Person, Age(120))).unwrap();
1024 assert_eq!(context.get_entity_count::<Person>(), 3);
1025 }
1026
1027 #[test]
1028 fn add_entity_with_zst() {
1029 let mut context = Context::new();
1030 let animal = context.add_entity(Animal).unwrap();
1031 assert_eq!(context.get_entity_count::<Animal>(), 1);
1032 assert_eq!(context.get_property::<Animal, Legs>(animal), Legs(4));
1033 }
1034
1035 #[derive(Copy, Clone, Debug)]
1037 enum IndexMode {
1038 Unindexed,
1039 FullIndex,
1040 ValueCountIndex,
1041 }
1042
1043 fn setup_context_for_index_tests(index_mode: IndexMode) -> (Context, Age, Age) {
1045 let mut context = Context::new();
1046 match index_mode {
1047 IndexMode::Unindexed => {}
1048 IndexMode::FullIndex => context.index_property::<Person, Age>(),
1049 IndexMode::ValueCountIndex => context.index_property_counts::<Person, Age>(),
1050 }
1051
1052 let existing_value = Age(12);
1053 let missing_value = Age(99);
1054
1055 let _ = context.add_entity(with!(Person, existing_value)).unwrap();
1056 let _ = context.add_entity(with!(Person, existing_value)).unwrap();
1057
1058 (context, existing_value, missing_value)
1059 }
1060
1061 #[test]
1062 fn query_results_respect_index_modes() {
1063 let modes = [
1064 IndexMode::Unindexed,
1065 IndexMode::FullIndex,
1066 IndexMode::ValueCountIndex,
1067 ];
1068
1069 for mode in modes {
1070 let (context, existing_value, missing_value) = setup_context_for_index_tests(mode);
1071
1072 let mut existing_len = 0;
1073 context.with_query_results(with!(Person, existing_value), &mut |people_set| {
1074 existing_len = people_set.into_iter().count();
1075 });
1076 assert_eq!(existing_len, 2, "Wrong length for {mode:?}");
1077
1078 let mut missing_len = 0;
1079 context.with_query_results(with!(Person, missing_value), &mut |people_set| {
1080 missing_len = people_set.into_iter().count();
1081 });
1082 assert_eq!(missing_len, 0);
1083
1084 let existing_count = context
1085 .query_result_iterator(with!(Person, existing_value))
1086 .count();
1087 assert_eq!(existing_count, 2);
1088
1089 let missing_count = context
1090 .query_result_iterator(with!(Person, missing_value))
1091 .count();
1092 assert_eq!(missing_count, 0);
1093
1094 assert_eq!(context.query_entity_count(with!(Person, existing_value)), 2);
1095 assert_eq!(context.query_entity_count(with!(Person, missing_value)), 0);
1096 }
1097 }
1098
1099 #[test]
1100 fn add_an_entity_without_required_properties() {
1101 let mut context = Context::new();
1102 let result = context.add_entity(with!(
1103 Person,
1104 InfectionStatus::Susceptible,
1105 Vaccinated(true)
1106 ));
1107
1108 assert!(matches!(
1109 result,
1110 Err(crate::IxaError::MissingRequiredInitializationProperties)
1111 ));
1112 }
1113
1114 #[test]
1115 fn new_entities_have_default_values() {
1116 let mut context = Context::new();
1117
1118 let person = context.add_entity(with!(Person, Age(25))).unwrap();
1120
1121 let age: Age = context.get_property(person);
1123 assert_eq!(age, Age(25));
1124 let infection_status: InfectionStatus = context.get_property(person);
1125 assert_eq!(infection_status, InfectionStatus::Susceptible);
1126 let vaccinated: Vaccinated = context.get_property(person);
1127 assert_eq!(vaccinated, Vaccinated(false));
1128
1129 context.set_property(person, Age(26));
1131 context.set_property(person, InfectionStatus::Infected);
1132 context.set_property(person, Vaccinated(true));
1133
1134 let age: Age = context.get_property(person);
1136 assert_eq!(age, Age(26));
1137 let infection_status: InfectionStatus = context.get_property(person);
1138 assert_eq!(infection_status, InfectionStatus::Infected);
1139 let vaccinated: Vaccinated = context.get_property(person);
1140 assert_eq!(vaccinated, Vaccinated(true));
1141 }
1142
1143 #[test]
1144 fn get_and_set_property_explicit() {
1145 let mut context = Context::new();
1146
1147 let person = context
1149 .add_entity(with!(
1150 Person,
1151 Age(25),
1152 InfectionStatus::Recovered,
1153 Vaccinated(true)
1154 ))
1155 .unwrap();
1156
1157 let age: Age = context.get_property(person);
1159 assert_eq!(age, Age(25));
1160 let infection_status: InfectionStatus = context.get_property(person);
1161 assert_eq!(infection_status, InfectionStatus::Recovered);
1162 let vaccinated: Vaccinated = context.get_property(person);
1163 assert_eq!(vaccinated, Vaccinated(true));
1164
1165 context.set_property(person, Age(26));
1167 context.set_property(person, InfectionStatus::Infected);
1168 context.set_property(person, Vaccinated(false));
1169
1170 let age: Age = context.get_property(person);
1172 assert_eq!(age, Age(26));
1173 let infection_status: InfectionStatus = context.get_property(person);
1174 assert_eq!(infection_status, InfectionStatus::Infected);
1175 let vaccinated: Vaccinated = context.get_property(person);
1176 assert_eq!(vaccinated, Vaccinated(false));
1177 }
1178
1179 #[test]
1180 fn count_entities() {
1181 let mut context = Context::new();
1182
1183 assert_eq!(context.get_entity_count::<Animal>(), 0);
1184 assert_eq!(context.get_entity_count::<Person>(), 0);
1185
1186 for _ in 0..7 {
1188 let _: PersonId = context.add_entity(with!(Person, Age(25))).unwrap();
1189 }
1190 for _ in 0..5 {
1191 let _: AnimalId = context.add_entity(with!(Animal, Legs(2))).unwrap();
1192 }
1193
1194 assert_eq!(context.get_entity_count::<Animal>(), 5);
1195 assert_eq!(context.get_entity_count::<Person>(), 7);
1196
1197 let _: PersonId = context.add_entity(with!(Person, Age(30))).unwrap();
1198 let _: AnimalId = context.add_entity(with!(Animal, Legs(8))).unwrap();
1199
1200 assert_eq!(context.get_entity_count::<Animal>(), 6);
1201 assert_eq!(context.get_entity_count::<Person>(), 8);
1202 }
1203
1204 #[test]
1205 fn count_and_sample_entity_empty_query_fast_path() {
1206 let mut context = Context::new();
1207 context.init_random(42);
1208 for age in [10u8, 20, 30] {
1209 let _: PersonId = context.add_entity(with!(Person, Age(age))).unwrap();
1210 }
1211
1212 let (count, sampled) =
1213 context.count_and_sample_entity::<Person, _, _>(EntityContextTestRng, Person);
1214 assert_eq!(count, 3);
1215 assert!(sampled.is_some());
1216 }
1217
1218 #[test]
1219 fn count_and_sample_entity_unindexed_derived_query() {
1220 let mut context = Context::new();
1221 context.init_random(43);
1222 for age in [10u8, 20, 30, 80] {
1223 let _: PersonId = context.add_entity(with!(Person, Age(age))).unwrap();
1224 }
1225
1226 let query = with!(Person, AgeGroup::Adult);
1227 let expected_count = context.query_entity_count(query);
1228 let (count, sampled) = context.count_and_sample_entity(EntityContextTestRng, query);
1229 assert_eq!(count, expected_count);
1230 assert_eq!(sampled.is_some(), count > 0);
1231 if let Some(entity_id) = sampled {
1232 assert!(context.match_entity(entity_id, query));
1233 }
1234 }
1235
1236 #[test]
1237 fn get_derived_property_multiple_deps() {
1238 let mut context = Context::new();
1239 context.index_property::<Person, RiskLevel>();
1240
1241 let expected_high_id: PersonId = context
1242 .add_entity(with!(
1243 Person,
1244 Age(77),
1245 Vaccinated(false),
1246 InfectionStatus::Susceptible
1247 ))
1248 .unwrap();
1249 let expected_med_id: PersonId = context
1250 .add_entity(with!(
1251 Person,
1252 Age(30),
1253 Vaccinated(false),
1254 InfectionStatus::Susceptible
1255 ))
1256 .unwrap();
1257 let expected_low_id: PersonId = context
1258 .add_entity(with!(
1259 Person,
1260 Age(3),
1261 Vaccinated(true),
1262 InfectionStatus::Recovered
1263 ))
1264 .unwrap();
1265
1266 let actual_high: RiskLevel = context.get_property(expected_high_id);
1267 assert_eq!(actual_high, RiskLevel::High);
1268 let actual_med: RiskLevel = context.get_property(expected_med_id);
1269 assert_eq!(actual_med, RiskLevel::Medium);
1270 let actual_low: RiskLevel = context.get_property(expected_low_id);
1271 assert_eq!(actual_low, RiskLevel::Low);
1272
1273 assert_eq!(
1274 context
1275 .query(with!(Person, RiskLevel::High))
1276 .into_iter()
1277 .collect::<IndexSet<_>>(),
1278 [expected_high_id].into_iter().collect::<IndexSet<_>>(),
1279 );
1280 assert_eq!(
1281 context
1282 .query(with!(Person, RiskLevel::Medium))
1283 .into_iter()
1284 .collect::<IndexSet<_>>(),
1285 [expected_med_id].into_iter().collect::<IndexSet<_>>(),
1286 );
1287 assert_eq!(
1288 context
1289 .query(with!(Person, RiskLevel::Low))
1290 .into_iter()
1291 .collect::<IndexSet<_>>(),
1292 [expected_low_id].into_iter().collect::<IndexSet<_>>(),
1293 );
1294 }
1295
1296 #[test]
1297 fn indexed_constant_default_handles_sparse_creation_paths() {
1298 let mut context = Context::new();
1299 context.index_property::<Person, IsRunner>();
1300
1301 let omitted = context.add_entity(with!(Person, Age(20))).unwrap();
1302 let explicit = context
1303 .add_entity(with!(Person, Age(21), IsRunner(false)))
1304 .unwrap();
1305
1306 let matching = context
1307 .query(with!(Person, IsRunner(false)))
1308 .into_iter()
1309 .collect::<IndexSet<_>>();
1310 assert_eq!(
1311 matching,
1312 [omitted, explicit].into_iter().collect::<IndexSet<_>>()
1313 );
1314 }
1315
1316 #[test]
1317 fn listen_to_derived_property_change_event() {
1318 let mut context = Context::new();
1319
1320 let expected_high_id = PersonId::new(0);
1321
1322 let risk_flag = Rc::new(RefCell::new(0));
1325 let risk_flag_clone = risk_flag.clone();
1326 context.subscribe_to_event(
1327 move |_context, event: PropertyChangeEvent<Person, RiskLevel>| {
1328 assert_eq!(event.entity_id, expected_high_id);
1329 assert_eq!(event.previous, RiskLevel::High);
1330 assert_eq!(event.current, RiskLevel::Medium);
1331 *risk_flag_clone.borrow_mut() += 1;
1332 },
1333 );
1334 let age_group_flag = Rc::new(RefCell::new(0));
1336 let age_group_flag_clone = age_group_flag.clone();
1337 context.subscribe_to_event(
1338 move |_context, event: PropertyChangeEvent<Person, AgeGroup>| {
1339 assert_eq!(event.entity_id, expected_high_id);
1340 assert_eq!(event.previous, AgeGroup::Senior);
1341 assert_eq!(event.current, AgeGroup::Adult);
1342 *age_group_flag_clone.borrow_mut() += 1;
1343 },
1344 );
1345
1346 let expected_high_id: PersonId = context
1348 .add_entity(with!(
1349 Person,
1350 Age(77),
1351 Vaccinated(false),
1352 InfectionStatus::Susceptible
1353 ))
1354 .unwrap();
1355
1356 context.set_property(expected_high_id, Age(20));
1358
1359 context.execute();
1361 assert_eq!(*risk_flag.borrow(), 1);
1363 assert_eq!(*age_group_flag.borrow(), 1);
1364 }
1365
1366 #[test]
1386 fn observe_diamond_property_change() {
1387 let mut context = Context::new();
1388 let person = context
1389 .add_entity(with!(Person, Age(17), IsSwimmer(true)))
1390 .unwrap();
1391
1392 let is_adult_athlete: AdultAthlete = context.get_property(person);
1393 assert!(!is_adult_athlete.0);
1394
1395 let flag = Rc::new(RefCell::new(0));
1396 let flag_clone = flag.clone();
1397 context.subscribe_to_event(
1398 move |_context, event: PropertyChangeEvent<Person, AdultAthlete>| {
1399 assert_eq!(event.entity_id, person);
1400 assert_eq!(event.previous, AdultAthlete(false));
1401 assert_eq!(event.current, AdultAthlete(true));
1402 *flag_clone.borrow_mut() += 1;
1403 },
1404 );
1405
1406 context.set_property(person, Age(20));
1407 let is_adult_athlete: AdultAthlete = context.get_property(person);
1409 assert!(is_adult_athlete.0);
1410
1411 context.execute();
1413 assert_eq!(*flag.borrow(), 1);
1415 }
1416
1417 define_multi_property!(Person, (InfectionStatus, Vaccinated));
1420 define_multi_property!(Person, (Vaccinated, InfectionStatus));
1421
1422 #[test]
1423 fn with_query_results_finds_multi_index() {
1424 use crate::rand::rngs::SmallRng;
1425 use crate::rand::seq::IndexedRandom;
1426 use crate::rand::SeedableRng;
1427
1428 let mut rng = SmallRng::seed_from_u64(42);
1429 let mut context = Context::new();
1430
1431 for _ in 0..10_000usize {
1432 let infection_status = *[
1433 InfectionStatus::Susceptible,
1434 InfectionStatus::Infected,
1435 InfectionStatus::Recovered,
1436 ]
1437 .choose(&mut rng)
1438 .unwrap();
1439 let vaccination_status: bool = rng.random_bool(0.5);
1440 let age: u8 = rng.random_range(0..100);
1441 context
1442 .add_entity(with!(
1443 Person,
1444 Age(age),
1445 infection_status,
1446 Vaccinated(vaccination_status)
1447 ))
1448 .unwrap();
1449 }
1450 context.index_property::<Person, InfectionStatusVaccinated>();
1451 let _ = context.query_result_iterator(with!(
1453 Person,
1454 InfectionStatus::Susceptible,
1455 Vaccinated(true)
1456 ));
1457
1458 let mut result_entities: IndexSet<EntityId<Person>> = IndexSet::default();
1460 context.with_query_results(
1461 with!(Person, InfectionStatus::Susceptible, Vaccinated(true)),
1462 &mut |result_set| {
1463 result_entities = result_set.into_iter().collect::<IndexSet<_>>();
1464 },
1465 );
1466
1467 assert_ne!(
1470 InfectionStatusVaccinated::id(),
1471 VaccinatedInfectionStatus::id()
1472 );
1473 assert_ne!(
1474 InfectionStatusVaccinated::type_id(),
1475 VaccinatedInfectionStatus::type_id()
1476 );
1477 assert_eq!(
1478 InfectionStatusVaccinated::id(),
1479 (InfectionStatus::Susceptible, Vaccinated(true))
1480 .multi_property_id()
1481 .unwrap()
1482 );
1483
1484 let property_id = InfectionStatusVaccinated::id();
1486
1487 let property_store = context.entity_store.get_property_store::<Person>();
1488 let query = (InfectionStatus::Susceptible, Vaccinated(true));
1489 let query_parts = query.query_parts();
1490 let bucket =
1491 match property_store.get_index_set_for_query_parts(property_id, query_parts.as_ref()) {
1492 IndexSetResult::Set(bucket) => bucket,
1493 other => panic!("expected indexed query bucket, found {other:?}"),
1494 };
1495
1496 let expected_entities = bucket.iter().copied().collect::<IndexSet<_>>();
1497 assert_eq!(expected_entities, result_entities);
1498 }
1499
1500 #[test]
1501 fn query_returns_entity_set_and_query_result_iterator_remains_compatible() {
1502 let mut context = Context::new();
1503 let p1 = context
1504 .add_entity(with!(
1505 Person,
1506 Age(21),
1507 InfectionStatus::Susceptible,
1508 Vaccinated(true)
1509 ))
1510 .unwrap();
1511 let _p2 = context
1512 .add_entity(with!(
1513 Person,
1514 Age(22),
1515 InfectionStatus::Susceptible,
1516 Vaccinated(false)
1517 ))
1518 .unwrap();
1519 let p3 = context
1520 .add_entity(with!(
1521 Person,
1522 Age(23),
1523 InfectionStatus::Infected,
1524 Vaccinated(true)
1525 ))
1526 .unwrap();
1527
1528 let query = with!(Person, Vaccinated(true));
1529
1530 let from_set = context
1531 .query::<Person, _>(query)
1532 .into_iter()
1533 .collect::<IndexSet<_>>();
1534 let from_iterator = context
1535 .query_result_iterator(query)
1536 .collect::<IndexSet<_>>();
1537
1538 assert_eq!(from_set, from_iterator);
1539 assert!(from_set.contains(&p1));
1540 assert!(from_set.contains(&p3));
1541 assert_eq!(from_set.len(), 2);
1542 }
1543
1544 #[test]
1545 fn set_property_correctly_maintains_index() {
1546 let mut context = Context::new();
1547 context.index_property::<Person, InfectionStatus>();
1548 context.index_property::<Person, AgeGroup>();
1549
1550 let person1 = context.add_entity(with!(Person, Age(22))).unwrap();
1551 let person2 = context.add_entity(with!(Person, Age(22))).unwrap();
1552 for _ in 0..4 {
1553 let _: PersonId = context.add_entity(with!(Person, Age(22))).unwrap();
1554 }
1555
1556 assert_eq!(
1558 context.query_entity_count(with!(Person, InfectionStatus::Susceptible)),
1559 6
1560 );
1561 assert_eq!(
1562 context.query_entity_count(with!(Person, InfectionStatus::Infected)),
1563 0
1564 );
1565 assert_eq!(
1566 context.query_entity_count(with!(Person, InfectionStatus::Recovered)),
1567 0
1568 );
1569
1570 context.set_property(person1, InfectionStatus::Infected);
1571
1572 assert_eq!(
1573 context.query_entity_count(with!(Person, InfectionStatus::Susceptible)),
1574 5
1575 );
1576 assert_eq!(
1577 context.query_entity_count(with!(Person, InfectionStatus::Infected)),
1578 1
1579 );
1580 assert_eq!(
1581 context.query_entity_count(with!(Person, InfectionStatus::Recovered)),
1582 0
1583 );
1584
1585 context.set_property(person1, InfectionStatus::Recovered);
1586
1587 assert_eq!(
1588 context.query_entity_count(with!(Person, InfectionStatus::Susceptible)),
1589 5
1590 );
1591 assert_eq!(
1592 context.query_entity_count(with!(Person, InfectionStatus::Infected)),
1593 0
1594 );
1595 assert_eq!(
1596 context.query_entity_count(with!(Person, InfectionStatus::Recovered)),
1597 1
1598 );
1599
1600 assert_eq!(
1602 context.query_entity_count(with!(Person, AgeGroup::Child)),
1603 0
1604 );
1605 assert_eq!(
1606 context.query_entity_count(with!(Person, AgeGroup::Adult)),
1607 6
1608 );
1609 assert_eq!(
1610 context.query_entity_count(with!(Person, AgeGroup::Senior)),
1611 0
1612 );
1613
1614 context.set_property(person2, Age(12));
1615
1616 assert_eq!(
1617 context.query_entity_count(with!(Person, AgeGroup::Child)),
1618 1
1619 );
1620 assert_eq!(
1621 context.query_entity_count(with!(Person, AgeGroup::Adult)),
1622 5
1623 );
1624 assert_eq!(
1625 context.query_entity_count(with!(Person, AgeGroup::Senior)),
1626 0
1627 );
1628
1629 context.set_property(person1, Age(75));
1630
1631 assert_eq!(
1632 context.query_entity_count(with!(Person, AgeGroup::Child)),
1633 1
1634 );
1635 assert_eq!(
1636 context.query_entity_count(with!(Person, AgeGroup::Adult)),
1637 4
1638 );
1639 assert_eq!(
1640 context.query_entity_count(with!(Person, AgeGroup::Senior)),
1641 1
1642 );
1643
1644 context.set_property(person2, Age(77));
1645
1646 assert_eq!(
1647 context.query_entity_count(with!(Person, AgeGroup::Child)),
1648 0
1649 );
1650 assert_eq!(
1651 context.query_entity_count(with!(Person, AgeGroup::Adult)),
1652 4
1653 );
1654 assert_eq!(
1655 context.query_entity_count(with!(Person, AgeGroup::Senior)),
1656 2
1657 );
1658 }
1659
1660 #[test]
1661 fn query_unindexed_default_properties() {
1662 let mut context = Context::new();
1663
1664 for idx in 0..10 {
1666 if idx % 2 == 0 {
1667 context.add_entity(with!(Person, Age(22))).unwrap();
1668 } else {
1669 context
1670 .add_entity(with!(Person, Age(22), InfectionStatus::Recovered))
1671 .unwrap();
1672 }
1673 }
1674 for _ in 0..10 {
1676 let _: PersonId = context.add_entity(with!(Person, Age(22))).unwrap();
1677 }
1678
1679 assert_eq!(
1680 context.query_entity_count(with!(Person, InfectionStatus::Recovered)),
1681 5
1682 );
1683 assert_eq!(
1684 context.query_entity_count(with!(Person, InfectionStatus::Susceptible)),
1685 15
1686 );
1687 }
1688
1689 #[test]
1690 fn query_unindexed_derived_properties() {
1691 let mut context = Context::new();
1692
1693 for _ in 0..10 {
1694 let _: PersonId = context.add_entity(with!(Person, Age(22))).unwrap();
1695 }
1696
1697 assert_eq!(
1698 context.query_entity_count(with!(Person, AdultAthlete(false))),
1699 10
1700 );
1701 }
1702
1703 #[test]
1704 fn track_periodic_value_change_counts_uses_distinct_counters() {
1705 let mut context = Context::new();
1706
1707 context.track_periodic_value_change_counts::<Person, (CounterStratum,), CounterValue, _>(
1708 1.0,
1709 move |_context, _counter| {},
1710 );
1711
1712 context.track_periodic_value_change_counts::<Person, (CounterStratum,), CounterValue, _>(
1713 1.0,
1714 move |_context, _counter| {},
1715 );
1716
1717 let property_value_store = context.get_property_value_store::<Person, CounterValue>();
1718 assert_eq!(property_value_store.value_change_counters.len(), 0);
1719
1720 context.add_plan(0.5, Context::shutdown);
1721 context.execute();
1722
1723 let property_value_store = context.get_property_value_store::<Person, CounterValue>();
1724 assert_eq!(property_value_store.value_change_counters.len(), 2);
1725 }
1726
1727 #[test]
1728 fn track_periodic_value_change_counts_accepts_into_f64() {
1729 let mut context = Context::new();
1730 let observed_times = Rc::new(RefCell::new(Vec::new()));
1731 let observed_times_clone = Rc::clone(&observed_times);
1732
1733 context.track_periodic_value_change_counts::<Person, (CounterStratum,), CounterValue, _>(
1734 WrappedF64(1.0),
1735 move |context, _counter| {
1736 observed_times_clone
1737 .borrow_mut()
1738 .push(context.get_current_time());
1739 },
1740 );
1741 context.add_plan(1.0, |_| {});
1742
1743 context.execute();
1744
1745 assert_eq!(*observed_times.borrow(), vec![0.0, 1.0]);
1746 }
1747
1748 #[test]
1749 fn value_change_counter_updates_on_true_transitions() {
1750 let mut context = Context::new();
1751 let observed = Rc::new(RefCell::new(Vec::<(usize, usize)>::new()));
1752 let observed_clone = observed.clone();
1753
1754 context.track_periodic_value_change_counts(1.0, move |_context, counter| {
1755 observed_clone.borrow_mut().push((
1756 counter.get_count((CounterStratum(true),), CounterValue(1)),
1757 counter.get_count((CounterStratum(true),), CounterValue(2)),
1758 ));
1759 });
1760
1761 let person = context
1762 .add_entity(with!(
1763 Person,
1764 Age(10),
1765 CounterValue(0),
1766 CounterStratum(true)
1767 ))
1768 .unwrap();
1769 context.add_plan(0.1, move |context| {
1770 context.set_property(person, CounterValue(1));
1771 context.set_property(person, CounterValue(1));
1772 context.set_property(person, CounterValue(2));
1773 });
1774 context.add_plan(1.0, |_| {});
1775
1776 context.execute();
1777 assert_eq!(*observed.borrow(), vec![(0, 0), (1, 1)]);
1778 }
1779
1780 #[test]
1781 fn periodic_value_change_counts_report_and_clear() {
1782 let mut context = Context::new();
1783 let person = context
1784 .add_entity(with!(
1785 Person,
1786 Age(10),
1787 CounterValue(0),
1788 CounterStratum(true)
1789 ))
1790 .unwrap();
1791
1792 let observed = Rc::new(RefCell::new(Vec::<usize>::new()));
1793 let observed_clone = observed.clone();
1794
1795 context.track_periodic_value_change_counts(1.0, move |_context, counter| {
1796 observed_clone
1797 .borrow_mut()
1798 .push(counter.get_count((CounterStratum(true),), CounterValue(1)));
1799 });
1800
1801 context.add_plan(0.5, move |context| {
1802 context.set_property(person, CounterValue(1));
1803 });
1804 context.add_plan(1.5, move |context| {
1805 context.set_property(person, CounterValue(1));
1806 });
1807 context.add_plan(2.0, |_| {});
1808
1809 context.execute();
1810 assert_eq!(*observed.borrow(), vec![0, 1, 0]);
1811 }
1812
1813 #[test]
1814 fn periodic_value_change_counts_start_time_and_phase_behavior() {
1815 let mut context = Context::new();
1816 context.set_start_time(-2.0);
1817
1818 let person = context
1819 .add_entity(with!(
1820 Person,
1821 Age(10),
1822 CounterValue(0),
1823 CounterStratum(true)
1824 ))
1825 .unwrap();
1826
1827 let observed_times = Rc::new(RefCell::new(Vec::<f64>::new()));
1828 let observed_counts = Rc::new(RefCell::new(Vec::<usize>::new()));
1829 let observed_times_clone = observed_times.clone();
1830 let observed_counts_clone = observed_counts.clone();
1831
1832 context.track_periodic_value_change_counts(1.0, move |context, counter| {
1833 observed_times_clone
1834 .borrow_mut()
1835 .push(context.get_current_time());
1836 observed_counts_clone
1837 .borrow_mut()
1838 .push(counter.get_count((CounterStratum(true),), CounterValue(1)));
1839 });
1840
1841 context.add_plan_with_phase(
1842 -2.0,
1843 move |context| {
1844 context.set_property(person, CounterValue(1));
1845 },
1846 ExecutionPhase::Normal,
1847 );
1848 context.add_plan(0.0, |_| {});
1849
1850 context.execute();
1851
1852 assert_eq!(*observed_times.borrow(), vec![-2.0, -1.0, 0.0]);
1853 assert_eq!(*observed_counts.borrow(), vec![1, 0, 0]);
1854 }
1855
1856 #[cfg(feature = "profiling")]
1857 fn query_count(context: &Context, identity: QueryIdentityId) -> Option<usize> {
1858 context
1859 .query_profiling_data(identity)
1860 .map(|profiling_data| profiling_data.count)
1861 }
1862
1863 #[cfg(feature = "profiling")]
1864 fn query_total(context: &Context, identity: QueryIdentityId) -> Option<Duration> {
1865 context
1866 .query_profiling_data(identity)
1867 .map(|profiling_data| profiling_data.total)
1868 }
1869
1870 #[cfg(feature = "profiling")]
1871 #[test]
1872 fn query_identity_aggregates_unordered_properties_and_ignores_values() {
1873 let mut context = Context::new();
1874 context
1875 .add_entity(with!(ProfilingPerson, ProfilingAge(42), ProfilingCounty(1)))
1876 .unwrap();
1877 context
1878 .add_entity(with!(ProfilingPerson, ProfilingAge(7), ProfilingCounty(2)))
1879 .unwrap();
1880
1881 let identity = with!(ProfilingPerson, ProfilingAge(0), ProfilingCounty(0))
1882 .resolved_query()
1883 .identity;
1884 assert_eq!(
1885 context
1886 .query_result_iterator(with!(ProfilingPerson, ProfilingAge(42), ProfilingCounty(1)))
1887 .count(),
1888 1
1889 );
1890 assert_eq!(
1891 context
1892 .query_result_iterator(with!(ProfilingPerson, ProfilingCounty(1), ProfilingAge(42)))
1893 .count(),
1894 1
1895 );
1896 assert_eq!(
1897 context
1898 .query_result_iterator(with!(ProfilingPerson, ProfilingAge(7), ProfilingCounty(2)))
1899 .count(),
1900 1
1901 );
1902
1903 assert_eq!(query_count(&context, identity), Some(3));
1904 }
1905
1906 #[cfg(feature = "profiling")]
1907 #[test]
1908 fn iterator_consumption_styles_each_record_one_execution() {
1909 let mut context = Context::new();
1910 let person = context
1911 .add_entity(with!(ProfilingBoundaryPerson, ProfilingBoundaryAge(42)))
1912 .unwrap();
1913
1914 let identity = with!(ProfilingBoundaryPerson, ProfilingBoundaryAge(0))
1915 .resolved_query()
1916 .identity;
1917 assert_eq!(
1918 context
1919 .query_result_iterator(with!(ProfilingBoundaryPerson, ProfilingBoundaryAge(42)))
1920 .count(),
1921 1
1922 );
1923 assert_eq!(query_count(&context, identity), Some(1));
1924 let profiling_data = context.query_profiling_data(identity).unwrap();
1925 assert_eq!(profiling_data.min, profiling_data.total);
1926 assert_eq!(profiling_data.max, profiling_data.total);
1927
1928 let mut iter =
1929 context.query_result_iterator(with!(ProfilingBoundaryPerson, ProfilingBoundaryAge(42)));
1930 assert_eq!(iter.next(), Some(person));
1931 assert_eq!(query_count(&context, identity), Some(1));
1932 assert_eq!(iter.next(), None);
1933 assert_eq!(query_count(&context, identity), Some(2));
1934 assert_eq!(iter.next(), None);
1935 assert_eq!(query_count(&context, identity), Some(2));
1936 }
1937
1938 #[cfg(feature = "profiling")]
1939 #[test]
1940 fn partially_consumed_iterator_records_one_execution_when_dropped() {
1941 let mut context = Context::new();
1942 let person = context
1943 .add_entity(with!(ProfilingBoundaryPerson, ProfilingBoundaryAge(42)))
1944 .unwrap();
1945
1946 let identity = with!(ProfilingBoundaryPerson, ProfilingBoundaryAge(0))
1947 .resolved_query()
1948 .identity;
1949 let mut iter =
1950 context.query_result_iterator(with!(ProfilingBoundaryPerson, ProfilingBoundaryAge(42)));
1951 assert_eq!(iter.next(), Some(person));
1952 assert_eq!(query_count(&context, identity), None);
1953 drop(iter);
1954 assert_eq!(query_count(&context, identity), Some(1));
1955 }
1956
1957 #[cfg(feature = "profiling")]
1958 #[test]
1959 fn count_commits_work_from_a_partially_consumed_iterator_once() {
1960 let mut context = Context::new();
1961 let person = context
1962 .add_entity(with!(ProfilingBoundaryPerson, ProfilingBoundaryAge(42)))
1963 .unwrap();
1964
1965 let identity = with!(ProfilingBoundaryPerson, ProfilingBoundaryAge(0))
1966 .resolved_query()
1967 .identity;
1968 let mut iter =
1969 context.query_result_iterator(with!(ProfilingBoundaryPerson, ProfilingBoundaryAge(42)));
1970 assert_eq!(iter.next(), Some(person));
1971 assert_eq!(iter.count(), 0);
1972
1973 assert_eq!(query_count(&context, identity), Some(1));
1974 }
1975
1976 #[cfg(feature = "profiling")]
1977 #[test]
1978 fn unused_query_result_iterator_does_not_record_profiling_data() {
1979 let mut context = Context::new();
1980 context
1981 .add_entity(with!(
1982 ProfilingUnusedIteratorPerson,
1983 ProfilingUnusedIteratorAge(42)
1984 ))
1985 .unwrap();
1986
1987 let identity = with!(ProfilingUnusedIteratorPerson, ProfilingUnusedIteratorAge(0))
1988 .resolved_query()
1989 .identity;
1990 let iter = context.query_result_iterator(with!(
1991 ProfilingUnusedIteratorPerson,
1992 ProfilingUnusedIteratorAge(42)
1993 ));
1994 drop(iter);
1995
1996 assert_eq!(query_count(&context, identity), None);
1997 }
1998
1999 #[cfg(feature = "profiling")]
2000 #[test]
2001 fn iterator_adaptor_methods_record_once_per_direct_call() {
2002 let mut context = Context::new();
2003 for age in [1, 2, 2, 3] {
2004 context
2005 .add_entity(with!(ProfilingIdlePerson, ProfilingIdleAge(age)))
2006 .unwrap();
2007 }
2008
2009 let identity = with!(ProfilingIdlePerson, ProfilingIdleAge(0))
2010 .resolved_query()
2011 .identity;
2012 assert_eq!(
2013 context
2014 .query_result_iterator(with!(ProfilingIdlePerson, ProfilingIdleAge(1)))
2015 .count(),
2016 1
2017 );
2018 assert!(context
2019 .query_result_iterator(with!(ProfilingIdlePerson, ProfilingIdleAge(2)))
2020 .nth(1)
2021 .is_some());
2022 context
2023 .query_result_iterator(with!(ProfilingIdlePerson, ProfilingIdleAge(3)))
2024 .for_each(|_| {});
2025 let folded = context
2026 .query_result_iterator(with!(ProfilingIdlePerson, ProfilingIdleAge(1)))
2027 .fold(0usize, |count, _| count + 1);
2028 assert_eq!(folded, 1);
2029
2030 assert_eq!(query_count(&context, identity), Some(4));
2031 }
2032
2033 #[cfg(feature = "profiling")]
2034 #[test]
2035 fn iterator_adaptor_methods_do_not_record_callback_work() {
2036 let mut context = Context::new();
2037 for age in [10, 11] {
2038 context
2039 .add_entity(with!(ProfilingIdlePerson, ProfilingIdleAge(age)))
2040 .unwrap();
2041 }
2042
2043 let identity = with!(ProfilingIdlePerson, ProfilingIdleAge(0))
2044 .resolved_query()
2045 .identity;
2046 context
2047 .query_result_iterator(with!(ProfilingIdlePerson, ProfilingIdleAge(10)))
2048 .for_each(|_| thread::sleep(Duration::from_millis(50)));
2049
2050 let folded = context
2051 .query_result_iterator(with!(ProfilingIdlePerson, ProfilingIdleAge(11)))
2052 .fold(0usize, |count, _| {
2053 thread::sleep(Duration::from_millis(50));
2054 count + 1
2055 });
2056
2057 assert_eq!(folded, 1);
2058 assert_eq!(query_count(&context, identity), Some(2));
2059 assert!(
2060 query_total(&context, identity).unwrap() < Duration::from_millis(50),
2061 "query timing should exclude callback work"
2062 );
2063 }
2064
2065 #[cfg(feature = "profiling")]
2066 #[test]
2067 fn entity_set_operations_record_once_without_counting_callback_work() {
2068 use rand::rngs::StdRng;
2069 use rand::SeedableRng;
2070
2071 let mut context = Context::new();
2072 let person = context
2073 .add_entity(with!(ProfilingCallbackPerson, ProfilingCallbackAge(42)))
2074 .unwrap();
2075 context
2076 .add_entity(with!(ProfilingCallbackPerson, ProfilingCallbackAge(7)))
2077 .unwrap();
2078
2079 let identity = with!(ProfilingCallbackPerson, ProfilingCallbackAge(0))
2080 .resolved_query()
2081 .identity;
2082 context.with_query_results(
2083 with!(ProfilingCallbackPerson, ProfilingCallbackAge(42)),
2084 &mut |_people| {
2085 thread::sleep(Duration::from_millis(10));
2086 },
2087 );
2088 assert_eq!(query_count(&context, identity), None);
2089
2090 context.with_query_results(
2091 with!(ProfilingCallbackPerson, ProfilingCallbackAge(42)),
2092 &mut |people| {
2093 let mut rng = StdRng::seed_from_u64(1);
2094 assert!(people.contains(person));
2095 assert!(people.sample_entity(&mut rng).is_some());
2096 assert_eq!(people.count_and_sample_entity(&mut rng).0, 1);
2097 assert_eq!(people.sample_entities(&mut rng, 1).len(), 1);
2098 },
2099 );
2100
2101 assert_eq!(query_count(&context, identity), Some(4));
2102 }
2103
2104 #[cfg(feature = "profiling")]
2105 #[test]
2106 fn to_owned_vec_records_one_execution_without_nested_iterator_timing() {
2107 let mut context = Context::new();
2108 let person = context
2109 .add_entity(with!(ProfilingCallbackPerson, ProfilingCallbackAge(42)))
2110 .unwrap();
2111 context
2112 .add_entity(with!(ProfilingCallbackPerson, ProfilingCallbackAge(7)))
2113 .unwrap();
2114
2115 let identity = with!(ProfilingCallbackPerson, ProfilingCallbackAge(0))
2116 .resolved_query()
2117 .identity;
2118 let people = context
2119 .query(with!(ProfilingCallbackPerson, ProfilingCallbackAge(42)))
2120 .to_owned_vec();
2121
2122 assert_eq!(people, vec![person]);
2123 assert_eq!(query_count(&context, identity), Some(1));
2124 }
2125
2126 #[cfg(feature = "profiling")]
2127 #[test]
2128 fn eager_public_query_methods_record_once_per_call() {
2129 let mut context = Context::new();
2130 let person = context
2131 .add_entity(with!(ProfilingContainsPerson, ProfilingContainsAge(42)))
2132 .unwrap();
2133 let other = context
2134 .add_entity(with!(ProfilingContainsPerson, ProfilingContainsAge(7)))
2135 .unwrap();
2136
2137 let identity = with!(ProfilingContainsPerson, ProfilingContainsAge(0))
2138 .resolved_query()
2139 .identity;
2140 assert_eq!(
2141 context.query_entity_count(with!(ProfilingContainsPerson, ProfilingContainsAge(42))),
2142 1
2143 );
2144 assert!(context.match_entity(
2145 person,
2146 with!(ProfilingContainsPerson, ProfilingContainsAge(42))
2147 ));
2148 let mut people = vec![person, other];
2149 context.filter_entities(
2150 &mut people,
2151 with!(ProfilingContainsPerson, ProfilingContainsAge(42)),
2152 );
2153 assert_eq!(people, vec![person]);
2154
2155 assert_eq!(query_count(&context, identity), Some(3));
2156 }
2157
2158 #[cfg(feature = "profiling")]
2159 #[test]
2160 fn composed_entity_set_operation_preserves_single_query_profile() {
2161 let mut context = Context::new();
2162 let included = context
2163 .add_entity(with!(
2164 ProfilingComposedPerson,
2165 ProfilingComposedAge(42),
2166 ProfilingComposedCounty(1)
2167 ))
2168 .unwrap();
2169 let excluded = context
2170 .add_entity(with!(
2171 ProfilingComposedPerson,
2172 ProfilingComposedAge(42),
2173 ProfilingComposedCounty(2)
2174 ))
2175 .unwrap();
2176 context
2177 .add_entity(with!(
2178 ProfilingComposedPerson,
2179 ProfilingComposedAge(7),
2180 ProfilingComposedCounty(3)
2181 ))
2182 .unwrap();
2183
2184 let identity = with!(ProfilingComposedPerson, ProfilingComposedAge(0))
2185 .resolved_query()
2186 .identity;
2187 let exclusions = EntitySet::from_source(SourceSet::singleton(excluded));
2188 let count = context
2189 .query(with!(ProfilingComposedPerson, ProfilingComposedAge(42)))
2190 .difference(exclusions)
2191 .into_iter()
2192 .count();
2193
2194 assert_eq!(count, 1);
2195 assert_eq!(query_count(&context, identity), Some(1));
2196 assert!(context
2197 .query(with!(ProfilingComposedPerson, ProfilingComposedAge(42)))
2198 .difference(EntitySet::from_source(SourceSet::singleton(excluded)))
2199 .contains(included));
2200 }
2201
2202 #[cfg(feature = "profiling")]
2203 #[test]
2204 fn set_algebra_preserves_a_shared_query_profile() {
2205 let mut context = Context::new();
2206 context
2207 .add_entity(with!(
2208 ProfilingComposedPerson,
2209 ProfilingComposedAge(42),
2210 ProfilingComposedCounty(1)
2211 ))
2212 .unwrap();
2213 context
2214 .add_entity(with!(
2215 ProfilingComposedPerson,
2216 ProfilingComposedAge(7),
2217 ProfilingComposedCounty(2)
2218 ))
2219 .unwrap();
2220
2221 let identity = with!(ProfilingComposedPerson, ProfilingComposedAge(0))
2222 .resolved_query()
2223 .identity;
2224 let count = context
2225 .query(with!(ProfilingComposedPerson, ProfilingComposedAge(42)))
2226 .union(context.query(with!(ProfilingComposedPerson, ProfilingComposedAge(7))))
2227 .into_iter()
2228 .count();
2229
2230 assert_eq!(count, 2);
2231 assert_eq!(query_count(&context, identity), Some(1));
2232 }
2233
2234 #[cfg(feature = "profiling")]
2235 #[test]
2236 fn set_algebra_clears_distinct_query_profiles() {
2237 let mut context = Context::new();
2238 context
2239 .add_entity(with!(
2240 ProfilingComposedPerson,
2241 ProfilingComposedAge(42),
2242 ProfilingComposedCounty(1)
2243 ))
2244 .unwrap();
2245
2246 let age_identity = with!(ProfilingComposedPerson, ProfilingComposedAge(0))
2247 .resolved_query()
2248 .identity;
2249 let county_identity = with!(ProfilingComposedPerson, ProfilingComposedCounty(0))
2250 .resolved_query()
2251 .identity;
2252 let count = context
2253 .query(with!(ProfilingComposedPerson, ProfilingComposedAge(42)))
2254 .intersection(context.query(with!(ProfilingComposedPerson, ProfilingComposedCounty(1))))
2255 .into_iter()
2256 .count();
2257
2258 assert_eq!(count, 1);
2259 assert_eq!(query_count(&context, age_identity), None);
2260 assert_eq!(query_count(&context, county_identity), None);
2261 }
2262}