Skip to main content

ixa/entity/
context_extension.rs

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    // Populate while detached so a panic from allocation or property computation leaves the
61    // previously installed index and dispatcher state unchanged.
62    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        // Swap back the cleared counter to retain its allocated capacity.
135        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
150/// A trait extension for [`Context`] that exposes entity-related
151/// functionality.
152pub 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    /// Fetches the property value set for the given `entity_id`.
159    ///
160    /// The easiest way to call this method is by assigning it to a variable with an explicit type:
161    /// ```rust, ignore
162    /// let vaccine_status: VaccineStatus = context.get_property(entity_id);
163    /// ```
164    #[must_use]
165    fn get_property<E: Entity, P: Property<E>>(&self, entity_id: EntityId<E>) -> P;
166
167    /// Sets the value of the given property. This method unconditionally emits a `PropertyChangeEvent`.
168    ///
169    /// # Panics
170    ///
171    /// Panics if `P` is a derived property, which cannot be set directly.
172    fn set_property<E: Entity, P: Property<E>>(
173        &mut self,
174        entity_id: EntityId<E>,
175        property_value: P,
176    );
177
178    /// Enables full indexing of property values for the property `P`.
179    ///
180    /// This method is called with the turbo-fish syntax:
181    ///     `context.index_property::<Person, Age>()`
182    ///
183    /// This method both enables the index and catches it up to the current population.
184    ///
185    /// # Panics
186    ///
187    /// Panics if `P` is a nonrepresentative multi-property equivalent to another
188    /// registered multi-property.
189    fn index_property<E: Entity, P: IndexableProperty<E>>(&mut self);
190
191    /// Enables value-count indexing of property values for the property `P`.
192    ///
193    /// If the property already has a full index, that index is left unchanged, as it
194    /// already supports value-count queries.
195    ///
196    /// # Panics
197    ///
198    /// Panics if `P` is a nonrepresentative multi-property equivalent to another
199    /// registered multi-property.
200    fn index_property_counts<E: Entity, P: IndexableProperty<E>>(&mut self);
201
202    /// Tracks periodic value change counts for a newly created counter.
203    ///
204    /// The supplied period is converted to `f64` before validation.
205    ///
206    /// Recording starts at `ExecutionPhase::First` at simulation start time. The
207    /// report callbacks are passive plans: they do not keep the simulation
208    /// timeline alive. Reports run at simulation start time in
209    /// `ExecutionPhase::Last`, then at each subsequent `start_time + k * period`
210    /// that is reached while (non-passive) work remains or during final-time shutdown.
211    /// After the handler returns, the matched counter is cleared.
212    ///
213    /// ```rust,ignore
214    /// context.track_periodic_value_change_counts::<Person, (InfectionStatus,), Age>(
215    ///     30.0,
216    ///     |_context, counter| {
217    ///         let _ = counter;
218    ///     },
219    /// );
220    /// ```
221    ///
222    /// # Panics
223    ///
224    /// Panics if `period` is not a strictly positive finite number.
225    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    /// Checks if a property `P` is indexed.
236    ///
237    /// This method is called with the turbo-fish syntax:
238    ///     `context.index_property::<Person, Age>()`
239    ///
240    /// This method only checks the concrete property storage for `P`, not any equivalent
241    /// multi-properties.
242    #[cfg(test)]
243    #[must_use]
244    fn is_property_indexed<E: Entity, P: Property<E>>(&self) -> bool;
245
246    /// This method gives client code direct access to the query result as an `EntitySet`.
247    /// This is especially efficient for indexed queries, as this method can reduce to wrapping
248    /// a single indexed source.
249    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    /// Gives the count of distinct entity IDs satisfying the query. This is especially
256    /// efficient for indexed queries.
257    ///
258    /// Supplying a naked entity, e.g. `Person`, is equivalent to calling `get_entity_count::<Person>()`.
259    #[must_use]
260    fn query_entity_count<E: Entity, Q: Query<E>>(&self, query: Q) -> usize;
261
262    /// Sample a single entity uniformly from the query results. Returns `None` if the
263    /// query's result set is empty.
264    ///
265    /// To sample from the entire population, pass the entity type directly, for example `Person`.
266    #[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    /// Count query results and sample a single entity uniformly from them.
275    ///
276    /// Returns `(count, sample)`, where `sample` is `None` iff `count == 0`.
277    /// To sample from the entire population, pass the entity type directly, for example `Person`.
278    #[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    /// Sample up to `requested` entities uniformly from the query results. If the
287    /// query's result set has fewer than `requested` entities, the entire result
288    /// set is returned.
289    ///
290    /// To sample from the entire population, pass the entity type directly, for example `Person`.
291    #[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    /// Returns a total count of all created entities of type `E`.
300    #[must_use]
301    fn get_entity_count<E: Entity>(&self) -> usize;
302
303    /// Returns an iterator over all created entities of type `E`.
304    #[must_use]
305    fn get_entity_iterator<E: Entity>(&self) -> PopulationIterator<E>;
306
307    /// Generates an `EntitySet` representing the query results.
308    #[must_use]
309    fn query<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySet<E>;
310
311    /// Generates an iterator over the results of the query.
312    #[must_use]
313    fn query_result_iterator<E: Entity, Q: Query<E>>(&self, query: Q) -> EntitySetIterator<E>;
314
315    /// Determines if the given person matches this query.
316    #[must_use]
317    fn match_entity<E: Entity, Q: Query<E>>(&self, entity_id: EntityId<E>, query: Q) -> bool;
318
319    /// Removes all `EntityId`s from the given vector that do not match the given query.
320    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        // Check that the properties in the list are distinct.
329        PL::validate()?;
330
331        // Check that all required properties are present.
332        if !PL::contains_required_properties() {
333            return Err(IxaError::MissingRequiredInitializationProperties);
334        }
335
336        // Now that we know we will succeed, we create the entity.
337        let (new_entity_id, entity_created_event_subscribed) =
338            self.entity_store.new_entity_id::<E>();
339
340        // Assign the properties in the list to the new entity.
341        // This does not generate a property change event.
342        property_list.set_values_for_new_entity(
343            new_entity_id,
344            self.entity_store.get_property_store_mut::<E>(),
345        );
346
347        // All explicit values are now available, so derived and multi-property dispatchers see
348        // the entity's complete initialized state.
349
350        // We simultaneously fetch the index count and, when nonempty, clone the set of property
351        // initialized event subscriptions without retaining a borrow of the entity store.
352        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            // Copy the function pointer so the immutable PropertyStore borrow ends before dispatch
370            // mutably borrows the Context.
371            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        // The algorithm is as follows:
408        // 1. Snapshot previous values for the main property and any dependents that need change
409        //    processing by creating `PartialPropertyChangeEvent` instances.
410        // 2. Set the new value of the main property in the property store.
411        // 3. Emit each partial event; during emission each event computes the current value,
412        //    updates its index (remove old/add new), and emits a `PropertyChangeEvent`.
413
414        // We need two passes over the dependents: one pass to compute all the old values and
415        // another to compute all the new values. We group the steps for each dependent (and, it
416        // turns out, for the main property `P` as well) into two parts:
417        //  1. Before setting the main property `P`, factored out into
418        //     `self.property_store.create_partial_property_change`
419        //  2. After setting the main property `P`, factored out into
420        //     `PartialPropertyChangeEvent::emit_in_context`
421
422        // We decided not to do the following check:
423        // ```rust
424        // let previous_value = { self.get_property_value_store::<E, P>().get(entity_id) };
425        // if property_value == previous_value {
426        //     return;
427        // }
428        // ```
429        // The reasoning is:
430        // - It should be rare that we ever set a property to its present value.
431        // - It's not a significant burden on client code to check `property_value == previous_value` on
432        //   their own if they need to.
433        // - There may be use cases for listening to "writes" that don't actually change values.
434
435        // `SmallVec` inline capacity balances stack footprint against heap allocations: a larger
436        // inline size avoids spills for more dependents, while a smaller one keeps every
437        // set_property call lighter when most properties have few dependents. A value of 5 is
438        // chosen somewhat arbitrarily.
439        let mut dependents: SmallVec<[PartialPropertyChangeEventBox; 5]> = SmallVec::new();
440
441        // Immutable: Collect the previous value to create partial property change events
442        {
443            let property_store = self.entity_store.get_property_store::<E>();
444
445            // Create the partial property change for this value.
446            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            // Now create partial property change events for each dependent.
454            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        // Update the value
466        let property_value_store = self.get_property_value_store_mut::<E, P>();
467        property_value_store.set(entity_id, property_value);
468
469        // Mutable: After updating the value, we update its dependents, removing old values and
470        // storing the new values in their respective indexes, and emit the property change event.
471        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                // We create the counter at simulation start so initialization-time
504                // property writes are never recorded.
505                let counter_id = context
506                    .entity_store
507                    .get_property_store_mut::<E>()
508                    .create_value_change_counter::<PL, P>();
509
510                // We defer the first handler plan until now because it needs
511                // `counter_id`, and it must run in `ExecutionPhase::Last`.
512                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        // The fast path for indexed queries.
542        //
543        // This mirrors the indexed case in `SourceSet<'a, E>::new()` and
544        // `QueryInternal::new_query_result`. The difference is, we access the index set if we find it.
545        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            // If the property is not indexed, we fall through.
568        }
569
570        // Special case a whole-population query.
571        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        // The slow path of computing the full query set.
582        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        // The fast path for indexed queries.
596        //
597        // This mirrors the indexed case in `SourceSet<'a, E>::new()` and `QueryInternal::new_query_result`.
598        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            // If the property is not indexed, we fall through.
608        }
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    // ToDo(RobertJacobsonCDC): Enable this once #691 is resolved, https://github.com/CDCgov/ixa/issues/691.
956    // define_global_property!(GlobalDummy, u8);
957    // define_derived_property!(
958    //     struct MyDerivedProperty(u8),
959    //     Person,
960    //     [Age],
961    //     [GlobalDummy],
962    //     |age, global_dummy| {
963    //         MyDerivedProperty(age.0 + global_dummy)
964    //     }
965    // );
966
967    // Derived properties in a diamond dependency relationship
968    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        // Age is the only required property
1023        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    // Helper for index tests
1036    #[derive(Copy, Clone, Debug)]
1037    enum IndexMode {
1038        Unindexed,
1039        FullIndex,
1040        ValueCountIndex,
1041    }
1042
1043    // Returns `(context, existing_value, missing_value)`
1044    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        // Create a person with required Age property
1119        let person = context.add_entity(with!(Person, Age(25))).unwrap();
1120
1121        // Retrieve and check their values
1122        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        // Change them
1130        context.set_property(person, Age(26));
1131        context.set_property(person, InfectionStatus::Infected);
1132        context.set_property(person, Vaccinated(true));
1133
1134        // Retrieve and check their values
1135        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        // Create a person with explicit property values
1148        let person = context
1149            .add_entity(with!(
1150                Person,
1151                Age(25),
1152                InfectionStatus::Recovered,
1153                Vaccinated(true)
1154            ))
1155            .unwrap();
1156
1157        // Retrieve and check their values
1158        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        // Change them
1166        context.set_property(person, Age(26));
1167        context.set_property(person, InfectionStatus::Infected);
1168        context.set_property(person, Vaccinated(false));
1169
1170        // Retrieve and check their values
1171        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        // Create entities of different kinds
1187        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        // Listen for derived property change events and record how many times it fires
1323        // For `RiskLevel`
1324        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        // For `AgeGroup`
1335        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        // Should not emit change events
1347        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        // Should emit change events
1357        context.set_property(expected_high_id, Age(20));
1358
1359        // Execute queued event handlers
1360        context.execute();
1361        // Should have exactly one event recorded
1362        assert_eq!(*risk_flag.borrow(), 1);
1363        assert_eq!(*age_group_flag.borrow(), 1);
1364    }
1365
1366    /*
1367    ToDo(RobertJacobsonCDC): Enable this once #691 is resolved, https://github.com/CDCgov/ixa/issues/691.
1368
1369    #[test]
1370    fn get_derived_property_with_globals() {
1371        let mut context = Context::new();
1372
1373        context.set_global_property_value(GlobalDummy, 18).unwrap();
1374        let child = context.add_entity(with!(Person, Age(17))).unwrap();
1375        let adult = context.add_entity(with!(Person, Age(19))).unwrap();
1376
1377        let child_computed: MyDerivedProperty = context.get_property(child);
1378        assert_eq!(child_computed, MyDerivedProperty(17+18));
1379
1380        let adult_computed: MyDerivedProperty = context.get_property(adult);
1381        assert_eq!(adult_computed, MyDerivedProperty(19+18));
1382    }
1383    */
1384
1385    #[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        // Make sure the derived property is what we expect.
1408        let is_adult_athlete: AdultAthlete = context.get_property(person);
1409        assert!(is_adult_athlete.0);
1410
1411        // Execute queued event handlers
1412        context.execute();
1413        // Should have exactly one event recorded
1414        assert_eq!(*flag.borrow(), 1);
1415    }
1416
1417    // Tests related to queries and indexing
1418
1419    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        // Force an index build by running a query.
1452        let _ = context.query_result_iterator(with!(
1453            Person,
1454            InfectionStatus::Susceptible,
1455            Vaccinated(true)
1456        ));
1457
1458        // Capture the set given by `with_query_results`.
1459        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        // Check that equivalent multi-properties keep distinct storage and type IDs while
1468        // sharing query routing identity through the registry.
1469        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        // Check if it matches the expected bucket.
1485        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        // Check non-derived property index is correctly maintained
1557        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        // Check derived property index is correctly maintained.
1601        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        // Half will have the default value.
1665        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        // The tail also has the default value
1675        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}