1use std::borrow::Borrow;
14use std::ops::Range;
15
16use log::warn;
17use rand::{Rng, RngExt};
18
19use super::{EntitySetIterator, SourceSet};
20use crate::entity::{Entity, EntityId};
21#[cfg(feature = "profiling")]
22use crate::profiling::QueryProfileHandle;
23use crate::random::{
24 count_and_sample_single_l_reservoir, sample_multiple_from_known_length,
25 sample_multiple_l_reservoir, sample_single_excluding_l_reservoir, sample_single_l_reservoir,
26};
27
28pub struct EntitySet<'a, E: Entity> {
30 pub(super) inner: EntitySetInner<'a, E>,
31 #[cfg(feature = "profiling")]
32 pub(in crate::entity) query_profile: Option<QueryProfileHandle<'a>>,
33}
34
35pub(super) enum EntitySetInner<'a, E: Entity> {
37 Source(SourceSet<'a, E>),
38 Union(Box<EntitySet<'a, E>>, Box<EntitySet<'a, E>>),
39 Intersection(Vec<EntitySet<'a, E>>),
40 Difference(Box<EntitySet<'a, E>>, Box<EntitySet<'a, E>>),
41}
42
43impl<'a, E: Entity> Clone for EntitySet<'a, E> {
44 fn clone(&self) -> Self {
45 Self {
46 inner: self.inner.clone(),
47 #[cfg(feature = "profiling")]
48 query_profile: self.query_profile,
49 }
50 }
51}
52
53impl<'a, E: Entity> Clone for EntitySetInner<'a, E> {
54 fn clone(&self) -> Self {
55 match self {
56 Self::Source(source) => Self::Source(source.clone()),
57 Self::Union(left, right) => Self::Union(left.clone(), right.clone()),
58 Self::Intersection(sets) => Self::Intersection(sets.clone()),
59 Self::Difference(left, right) => Self::Difference(left.clone(), right.clone()),
60 }
61 }
62}
63
64impl<'a, E: Entity> Default for EntitySet<'a, E> {
65 fn default() -> Self {
66 Self::empty()
67 }
68}
69
70impl<'a, E: Entity> EntitySet<'a, E> {
71 fn new(inner: EntitySetInner<'a, E>) -> Self {
72 Self {
73 inner,
74 #[cfg(feature = "profiling")]
75 query_profile: None,
76 }
77 }
78
79 #[cfg(feature = "profiling")]
83 fn unique_query_profile(left: &Self, right: &Self) -> Option<QueryProfileHandle<'a>> {
84 match (left.query_profile, right.query_profile) {
85 (Some(left_profile), Some(right_profile)) if left_profile == right_profile => {
86 Some(left_profile)
87 }
88 (Some(_), Some(_)) => None,
89 (profile @ Some(_), None) | (None, profile @ Some(_)) => profile,
90 (None, None) => None,
91 }
92 }
93
94 pub(super) fn is_source_leaf(&self) -> bool {
95 matches!(self.inner, EntitySetInner::Source(_))
96 }
97
98 pub(super) fn into_source_leaf(self) -> Option<SourceSet<'a, E>> {
99 match self.inner {
100 EntitySetInner::Source(source) => Some(source),
101 _ => None,
102 }
103 }
104
105 #[must_use]
107 pub fn empty() -> Self {
108 Self::new(EntitySetInner::Source(SourceSet::empty()))
109 }
110
111 pub(crate) fn from_source(source: SourceSet<'a, E>) -> Self {
113 Self::new(EntitySetInner::Source(source))
114 }
115
116 pub(crate) fn from_intersection_sources(mut sources: Vec<SourceSet<'a, E>>) -> Self {
117 match sources.len() {
118 0 => return Self::empty(),
119 1 => return Self::from_source(sources.pop().unwrap()),
120 _ => {}
121 }
122
123 sources.sort_unstable_by_key(SourceSet::sort_key);
126
127 let sets = sources.into_iter().map(Self::from_source).collect();
128
129 Self::new(EntitySetInner::Intersection(sets))
130 }
131
132 #[cfg(feature = "profiling")]
133 #[inline]
134 pub(in crate::entity) fn with_query_profile(mut self, profile: QueryProfileHandle<'a>) -> Self {
135 self.query_profile = Some(profile);
136 self
137 }
138
139 #[must_use]
140 pub fn union(self, other: Self) -> Self {
141 #![allow(unused_mut)]
143 let mut left = self;
144 let mut right = other;
145 #[cfg(feature = "profiling")]
146 let query_profile = Self::unique_query_profile(&left, &right);
147 #[cfg(feature = "profiling")]
148 {
149 left.query_profile = None;
150 right.query_profile = None;
151 }
152
153 let mut result = 'result: {
154 if left.structurally_eq(&right) {
156 break 'result left;
157 }
158
159 if let (Some(a), Some(b)) = (left.as_range(), right.as_range()) {
161 if a.start <= b.end && b.start <= a.end {
162 break 'result Self::from_source(SourceSet::population_range(
163 a.start.min(b.start)..a.end.max(b.end),
164 ));
165 }
166 }
167
168 match (left.is_empty(), right.is_empty()) {
170 (true, _) => break 'result right,
171 (_, true) => break 'result left,
172 _ => { }
173 }
174
175 let (left, right) = if left.sort_key() >= right.sort_key() {
177 (left, right)
178 } else {
179 (right, left)
180 };
181 break 'result Self::new(EntitySetInner::Union(Box::new(left), Box::new(right)));
182 };
183 #[cfg(feature = "profiling")]
184 {
185 result.query_profile = query_profile;
186 }
187 result
188 }
189
190 #[must_use]
191 pub fn intersection(self, other: Self) -> Self {
192 #![allow(unused_mut)]
194 let mut left = self;
195 let mut right = other;
196 #[cfg(feature = "profiling")]
197 let query_profile = Self::unique_query_profile(&left, &right);
198 #[cfg(feature = "profiling")]
199 {
200 left.query_profile = None;
201 right.query_profile = None;
202 }
203
204 let mut result = 'result: {
205 if left.structurally_eq(&right) {
207 break 'result left;
208 }
209
210 if let (Some(a), Some(b)) = (left.as_range(), right.as_range()) {
212 let overlap = a.start.max(b.start)..a.end.min(b.end);
213 break 'result if overlap.is_empty() {
214 Self::empty()
215 } else {
216 Self::from_source(SourceSet::population_range(overlap))
217 };
218 }
219
220 match (left.is_empty(), right.is_empty()) {
222 (true, _) => break 'result left,
223 (_, true) => break 'result right,
224 _ => { }
225 }
226
227 let mut sets = match left {
228 EntitySet {
229 inner: EntitySetInner::Intersection(sets),
230 ..
231 } => sets,
232 _ => vec![left],
233 };
234
235 sets.push(right);
236 sets.sort_unstable_by_key(EntitySet::sort_key);
239 break 'result Self::new(EntitySetInner::Intersection(sets));
240 };
241 #[cfg(feature = "profiling")]
242 {
243 result.query_profile = query_profile;
244 }
245 result
246 }
247
248 #[must_use]
249 pub fn difference(self, other: Self) -> Self {
250 #![allow(unused_mut)]
252 let mut left = self;
253 let mut right = other;
254 #[cfg(feature = "profiling")]
255 let query_profile = Self::unique_query_profile(&left, &right);
256 #[cfg(feature = "profiling")]
257 {
258 left.query_profile = None;
259 right.query_profile = None;
260 }
261
262 let mut result = 'result: {
263 if left.structurally_eq(&right) {
265 break 'result Self::empty();
266 }
267
268 if let (Some(a), Some(b)) = (left.as_range(), right.as_range()) {
269 let overlap = a.start.max(b.start)..a.end.min(b.end);
270 if overlap.is_empty() {
272 break 'result Self::from_source(SourceSet::population_range(a));
273 }
274 if overlap.start == a.start && overlap.end == a.end {
276 break 'result Self::empty();
277 }
278 if overlap.start == a.start {
280 break 'result Self::from_source(SourceSet::population_range(
281 overlap.end..a.end,
282 ));
283 }
284 if overlap.end == a.end {
286 break 'result Self::from_source(SourceSet::population_range(
287 a.start..overlap.start,
288 ));
289 }
290 }
292
293 if left.is_empty() || right.is_empty() {
295 break 'result left;
296 }
297
298 break 'result Self::new(EntitySetInner::Difference(Box::new(left), Box::new(right)));
299 };
300 #[cfg(feature = "profiling")]
301 {
302 result.query_profile = query_profile;
303 }
304 result
305 }
306
307 #[must_use]
309 pub fn contains(&self, entity_id: EntityId<E>) -> bool {
310 #[cfg(feature = "profiling")]
311 let _query_profile_scope = self.query_profile.map(QueryProfileHandle::scope);
312 self.contains_impl(entity_id)
313 }
314
315 fn contains_impl(&self, entity_id: EntityId<E>) -> bool {
316 match &self.inner {
317 EntitySetInner::Source(source) => source.contains(entity_id),
318 EntitySetInner::Union(a, b) => a.contains_impl(entity_id) || b.contains_impl(entity_id),
319 EntitySetInner::Intersection(sets) => {
320 sets.iter().all(|set| set.contains_impl(entity_id))
321 }
322 EntitySetInner::Difference(a, b) => {
323 a.contains_impl(entity_id) && !b.contains_impl(entity_id)
324 }
325 }
326 }
327
328 #[must_use]
330 #[allow(unused_mut)]
331 pub fn to_owned_vec(mut self) -> Vec<EntityId<E>> {
332 #[cfg(feature = "profiling")]
333 let _query_profile_scope = self.query_profile.take().map(QueryProfileHandle::scope);
334
335 self.into_iter().collect()
336 }
337
338 #[must_use]
347 pub fn sample_entity_excluding<R, X>(&self, rng: &mut R, excluded: X) -> Option<EntityId<E>>
348 where
349 R: Rng,
350 X: Borrow<EntityId<E>>,
351 {
352 #[cfg(feature = "profiling")]
353 let _query_profile_scope = self.query_profile.map(QueryProfileHandle::scope);
354
355 let excluded = *excluded.borrow();
356 if let Some(n) = self.try_len_impl() {
357 if n == 0 {
358 None
359 } else {
360 let p = rng.random_range(0..n as u32) as usize;
361 match self.try_nth(p) {
362 Some(candidate) if candidate != excluded => Some(candidate),
363 Some(_) if n == 1 => None,
364 Some(_) => {
365 let k = rng.random_range(0..(n - 1) as u32) as usize;
369 let target = if k < p { k } else { k + 1 };
370 self.try_nth(target)
371 }
372 None => None,
373 }
374 }
375 } else {
376 #[allow(unused_mut)]
377 let mut set = self.clone();
378 #[cfg(feature = "profiling")]
379 {
380 set.query_profile = None;
381 }
382 sample_single_excluding_l_reservoir(rng, set, excluded)
383 }
384 }
385
386 #[must_use]
389 #[inline]
390 pub fn sample_entity<R>(&self, rng: &mut R) -> Option<EntityId<E>>
391 where
392 R: Rng,
393 {
394 #[cfg(feature = "profiling")]
395 let _query_profile_scope = self.query_profile.map(QueryProfileHandle::scope);
396
397 if let Some(n) = self.try_len_impl() {
398 if n == 0 {
399 warn!("Requested a sample entity from an empty population");
400 return None;
401 }
402 let index = rng.random_range(0..n as u32) as usize;
404 return self.try_nth(index);
405 }
406 #[allow(unused_mut)]
407 let mut set = self.clone();
408 #[cfg(feature = "profiling")]
409 {
410 set.query_profile = None;
411 }
412 sample_single_l_reservoir(rng, set)
413 }
414
415 #[must_use]
419 pub fn count_and_sample_entity<R>(&self, rng: &mut R) -> (usize, Option<EntityId<E>>)
420 where
421 R: Rng,
422 {
423 #[cfg(feature = "profiling")]
424 let _query_profile_scope = self.query_profile.map(QueryProfileHandle::scope);
425
426 if let Some(n) = self.try_len_impl() {
427 if n == 0 {
428 return (0, None);
429 }
430 let index = rng.random_range(0..n as u32) as usize;
431 return (n, self.try_nth(index));
432 }
433 #[allow(unused_mut)]
434 let mut set = self.clone();
435 #[cfg(feature = "profiling")]
436 {
437 set.query_profile = None;
438 }
439 count_and_sample_single_l_reservoir(rng, set)
440 }
441
442 #[must_use]
445 pub fn sample_entities<R>(&self, rng: &mut R, requested: usize) -> Vec<EntityId<E>>
446 where
447 R: Rng,
448 {
449 #[cfg(feature = "profiling")]
450 let _query_profile_scope = self.query_profile.map(QueryProfileHandle::scope);
451
452 match self.try_len_impl() {
453 Some(0) => {
454 warn!("Requested a sample of entities from an empty population");
455 vec![]
456 }
457 Some(_) => {
458 #[allow(unused_mut)]
459 let mut set = self.clone();
460 #[cfg(feature = "profiling")]
461 {
462 set.query_profile = None;
463 }
464 sample_multiple_from_known_length(rng, set, requested)
465 }
466 None => {
467 #[allow(unused_mut)]
468 let mut set = self.clone();
469 #[cfg(feature = "profiling")]
470 {
471 set.query_profile = None;
472 }
473 sample_multiple_l_reservoir(rng, set, requested)
474 }
475 }
476 }
477
478 #[must_use]
483 pub fn try_len(&self) -> Option<usize> {
484 #[cfg(feature = "profiling")]
485 let _query_profile_scope = self.query_profile.map(QueryProfileHandle::scope);
486 self.try_len_impl()
487 }
488
489 fn try_len_impl(&self) -> Option<usize> {
490 match &self.inner {
491 EntitySetInner::Source(source) => source.try_len(),
492 _ => None,
493 }
494 }
495
496 fn try_nth(&self, idx: usize) -> Option<EntityId<E>> {
497 match &self.inner {
498 EntitySetInner::Source(source) => source.try_nth(idx),
499 _ => None,
500 }
501 }
502
503 fn as_range(&self) -> Option<Range<usize>> {
504 match &self.inner {
505 EntitySetInner::Source(SourceSet::PopulationRange(range)) => Some(range.clone()),
506 _ => None,
507 }
508 }
509
510 fn is_empty(&self) -> bool {
511 match &self.inner {
512 EntitySetInner::Source(SourceSet::PopulationRange(range)) => range.is_empty(),
513 _ => false,
514 }
515 }
516
517 fn sort_key(&self) -> (usize, u8) {
518 match &self.inner {
519 EntitySetInner::Source(source) => source.sort_key(),
520 EntitySetInner::Union(left, right) => {
521 let (left_upper, left_hint) = left.sort_key();
523 let (right_upper, right_hint) = right.sort_key();
524 (
525 left_upper.saturating_add(right_upper),
526 left_hint.min(right_hint),
527 )
528 }
529 EntitySetInner::Intersection(sets) => {
530 let mut upper = usize::MAX;
531 let mut hint = 0u8;
532 for set in sets {
533 let (set_upper, set_hint) = set.sort_key();
534 upper = upper.min(set_upper);
535 hint = hint.saturating_add(set_hint);
536 }
537 if upper == usize::MAX {
538 upper = 0;
539 }
540 (upper, hint)
541 }
542 EntitySetInner::Difference(left, right) => {
543 let (left_upper, left_hint) = left.sort_key();
544 let (_, right_hint) = right.sort_key();
545 (left_upper, left_hint.saturating_add(right_hint))
546 }
547 }
548 }
549
550 fn structurally_eq(&self, other: &Self) -> bool {
552 match (&self.inner, &other.inner) {
553 (EntitySetInner::Source(a), EntitySetInner::Source(b)) => a == b,
554 (EntitySetInner::Union(a1, a2), EntitySetInner::Union(b1, b2))
555 | (EntitySetInner::Difference(a1, a2), EntitySetInner::Difference(b1, b2)) => {
556 a1.structurally_eq(b1) && a2.structurally_eq(b2)
557 }
558 (EntitySetInner::Intersection(a_sets), EntitySetInner::Intersection(b_sets)) => {
559 a_sets.len() == b_sets.len()
560 && a_sets
561 .iter()
562 .zip(b_sets.iter())
563 .all(|(a_set, b_set)| a_set.structurally_eq(b_set))
564 }
565 _ => false,
566 }
567 }
568}
569
570impl<'a, E: Entity> IntoIterator for EntitySet<'a, E> {
571 type Item = EntityId<E>;
572 type IntoIter = EntitySetIterator<'a, E>;
573
574 fn into_iter(self) -> Self::IntoIter {
575 EntitySetIterator::new(self)
576 }
577}
578
579#[cfg(test)]
580mod tests {
581 use rand::rngs::StdRng;
582 use rand::SeedableRng;
583
584 use super::*;
585 use crate::entity::ContextEntitiesExt;
586 use crate::hashing::IndexSet;
587 use crate::{
588 define_derived_property, define_entity, define_multi_property, define_property, with,
589 Context,
590 };
591
592 define_entity!(Person);
593 define_property!(struct Age(u8), Person);
594 define_property!(struct County(u32), Person, default_const = County(0));
595 define_derived_property!(struct Senior(bool), Person, [Age], |age| Senior(age.0 >= 65));
596 define_multi_property!(Person, (Age, County));
597 define_multi_property!(Person, (County, Age));
598
599 fn finite_set(ids: &[usize]) -> IndexSet<EntityId<Person>> {
600 ids.iter()
601 .copied()
602 .map(EntityId::<Person>::new)
603 .collect::<IndexSet<_>>()
604 }
605
606 fn as_entity_set(set: &IndexSet<EntityId<Person>>) -> EntitySet<Person> {
607 EntitySet::from_source(SourceSet::IndexSet(set))
608 }
609
610 #[test]
611 fn from_source_empty_is_empty() {
612 let es = EntitySet::<Person>::empty();
613 assert_eq!(es.sort_key().0, 0);
614 for value in 0..10 {
615 assert!(!es.contains(EntityId::<Person>::new(value)));
616 }
617 }
618
619 #[test]
620 fn from_source_population_ranges() {
621 let population = EntitySet::from_source(SourceSet::<Person>::population_range(0..3));
622 assert!(population.contains(EntityId::<Person>::new(0)));
623 assert!(population.contains(EntityId::<Person>::new(2)));
624 assert!(!population.contains(EntityId::<Person>::new(3)));
625 assert_eq!(population.sort_key().0, 3);
626
627 let singleton = EntitySet::from_source(SourceSet::<Person>::singleton(EntityId::new(5)));
628 assert!(singleton.contains(EntityId::<Person>::new(5)));
629 assert!(!singleton.contains(EntityId::<Person>::new(4)));
630 assert_eq!(singleton.sort_key().0, 1);
631
632 let range = EntitySet::from_source(SourceSet::<Person>::population_range(2..5));
633 assert!(range.contains(EntityId::<Person>::new(2)));
634 assert!(range.contains(EntityId::<Person>::new(4)));
635 assert!(!range.contains(EntityId::<Person>::new(1)));
636 assert!(!range.contains(EntityId::<Person>::new(5)));
637 assert_eq!(range.try_len(), Some(3));
638 }
639
640 #[test]
641 fn union_basic_behavior_without_legacy_reductions() {
642 let a = finite_set(&[1, 2, 3]);
643 let e = EntitySet::<Person>::empty();
644 let u = EntitySet::from_source(SourceSet::<Person>::population_range(0..10));
645
646 let a_union_empty = as_entity_set(&a).union(e);
647 assert!(a_union_empty.contains(EntityId::<Person>::new(1)));
648 assert!(!a_union_empty.contains(EntityId::<Person>::new(4)));
649
650 let u_union_a = u.union(as_entity_set(&a));
651 assert!(u_union_a.contains(EntityId::<Person>::new(0)));
652 assert!(u_union_a.contains(EntityId::<Person>::new(9)));
653 assert!(!u_union_a.contains(EntityId::<Person>::new(10)));
654 }
655
656 #[test]
657 fn union_range_optimizations() {
658 let adjacent = EntitySet::from_source(SourceSet::<Person>::population_range(0..3)).union(
659 EntitySet::from_source(SourceSet::<Person>::population_range(3..6)),
660 );
661 assert!(matches!(
662 adjacent,
663 EntitySet { inner: EntitySetInner::Source(SourceSet::PopulationRange(ref range)), .. } if range == &(0..6)
664 ));
665
666 let overlapping = EntitySet::from_source(SourceSet::<Person>::population_range(2..6))
667 .union(EntitySet::from_source(
668 SourceSet::<Person>::population_range(4..8),
669 ));
670 assert!(matches!(
671 overlapping,
672 EntitySet { inner: EntitySetInner::Source(SourceSet::PopulationRange(ref range)), .. } if range == &(2..8)
673 ));
674
675 let disjoint = EntitySet::from_source(SourceSet::<Person>::singleton(EntityId::new(1)))
676 .union(EntitySet::from_source(SourceSet::<Person>::singleton(
677 EntityId::new(4),
678 )));
679 assert!(matches!(
680 disjoint,
681 EntitySet {
682 inner: EntitySetInner::Union(_, _),
683 ..
684 }
685 ));
686 }
687
688 #[test]
689 fn intersection_range_optimizations() {
690 let overlap = EntitySet::from_source(SourceSet::<Person>::population_range(2..6))
691 .intersection(EntitySet::from_source(
692 SourceSet::<Person>::population_range(4..8),
693 ));
694 assert!(matches!(
695 overlap,
696 EntitySet { inner: EntitySetInner::Source(SourceSet::PopulationRange(ref range)), .. } if range == &(4..6)
697 ));
698
699 let empty = EntitySet::from_source(SourceSet::<Person>::population_range(1..3))
700 .intersection(EntitySet::from_source(
701 SourceSet::<Person>::population_range(5..7),
702 ));
703 assert!(matches!(
704 empty,
705 EntitySet { inner: EntitySetInner::Source(SourceSet::PopulationRange(ref range)), .. } if range == &(0..0)
706 ));
707
708 let indexed_ids = finite_set(&[1, 2, 3]);
709 let mixed = EntitySet::from_source(SourceSet::<Person>::singleton(EntityId::new(2)))
710 .intersection(as_entity_set(&indexed_ids));
711 assert!(mixed.contains(EntityId::<Person>::new(2)));
712 assert!(!mixed.contains(EntityId::<Person>::new(1)));
713 }
714
715 #[test]
716 fn difference_range_optimizations() {
717 let unchanged = EntitySet::from_source(SourceSet::<Person>::population_range(2..6))
718 .difference(EntitySet::from_source(
719 SourceSet::<Person>::population_range(8..10),
720 ));
721 assert!(matches!(
722 unchanged,
723 EntitySet { inner: EntitySetInner::Source(SourceSet::PopulationRange(ref range)), .. } if range == &(2..6)
724 ));
725
726 let empty = EntitySet::from_source(SourceSet::<Person>::population_range(2..6)).difference(
727 EntitySet::from_source(SourceSet::<Person>::population_range(1..7)),
728 );
729 assert!(matches!(
730 empty,
731 EntitySet { inner: EntitySetInner::Source(SourceSet::PopulationRange(ref range)), .. } if range == &(0..0)
732 ));
733
734 let trim_left = EntitySet::from_source(SourceSet::<Person>::population_range(2..6))
735 .difference(EntitySet::from_source(
736 SourceSet::<Person>::population_range(1..4),
737 ));
738 assert!(matches!(
739 trim_left,
740 EntitySet { inner: EntitySetInner::Source(SourceSet::PopulationRange(ref range)), .. } if range == &(4..6)
741 ));
742
743 let trim_right = EntitySet::from_source(SourceSet::<Person>::population_range(2..6))
744 .difference(EntitySet::from_source(
745 SourceSet::<Person>::population_range(4..8),
746 ));
747 assert!(matches!(
748 trim_right,
749 EntitySet { inner: EntitySetInner::Source(SourceSet::PopulationRange(ref range)), .. } if range == &(2..4)
750 ));
751
752 let split = EntitySet::from_source(SourceSet::<Person>::population_range(2..8)).difference(
753 EntitySet::from_source(SourceSet::<Person>::population_range(4..6)),
754 );
755 assert!(matches!(
756 split,
757 EntitySet {
758 inner: EntitySetInner::Difference(_, _),
759 ..
760 }
761 ));
762 assert!(split.contains(EntityId::<Person>::new(2)));
763 assert!(split.contains(EntityId::<Person>::new(7)));
764 assert!(!split.contains(EntityId::<Person>::new(4)));
765 assert!(!split.contains(EntityId::<Person>::new(5)));
766 }
767
768 #[test]
769 fn difference_is_not_commutative() {
770 let a = finite_set(&[1, 2, 3]);
771 let b = finite_set(&[2, 3, 4]);
772
773 let d1 = as_entity_set(&a).difference(as_entity_set(&b));
774 let c = finite_set(&[2, 3, 4]);
775 let d = finite_set(&[1, 2, 3]);
776 let d2 = as_entity_set(&c).difference(as_entity_set(&d));
777
778 assert!(d1.contains(EntityId::<Person>::new(1)));
779 assert!(!d1.contains(EntityId::<Person>::new(4)));
780 assert!(d2.contains(EntityId::<Person>::new(4)));
781 assert!(!d2.contains(EntityId::<Person>::new(1)));
782 }
783
784 #[test]
785 fn sort_key_rules() {
786 let a = finite_set(&[1, 2]);
787 let b = finite_set(&[2, 3, 4]);
788
789 let union = as_entity_set(&a).union(as_entity_set(&b));
790 assert_eq!(union.sort_key(), (a.len() + b.len(), 3));
791
792 let intersection = as_entity_set(&a).intersection(as_entity_set(&b));
793 assert_eq!(intersection.sort_key(), (a.len().min(b.len()), 6));
794
795 let difference = as_entity_set(&a).difference(as_entity_set(&b));
796 assert_eq!(difference.sort_key(), (a.len(), 6));
797 }
798
799 #[test]
800 fn compound_expressions_membership() {
801 let a = finite_set(&[1, 2, 3, 4]);
802 let b = finite_set(&[3, 4, 5]);
803 let c = finite_set(&[10, 20]);
804 let d = finite_set(&[20]);
805
806 let union_of_intersections = as_entity_set(&a)
807 .intersection(as_entity_set(&b))
808 .union(as_entity_set(&c).intersection(as_entity_set(&d)));
809 assert!(union_of_intersections.contains(EntityId::<Person>::new(3)));
810 assert!(union_of_intersections.contains(EntityId::<Person>::new(4)));
811 assert!(union_of_intersections.contains(EntityId::<Person>::new(20)));
812 assert!(!union_of_intersections.contains(EntityId::<Person>::new(5)));
813
814 let a2 = finite_set(&[1, 2, 3]);
815 let b2 = finite_set(&[3, 4, 5]);
816 let a3 = finite_set(&[1, 2, 3]);
817 let law = as_entity_set(&a3).intersection(as_entity_set(&a2).union(as_entity_set(&b2)));
818 assert!(law.contains(EntityId::<Person>::new(1)));
819 assert!(law.contains(EntityId::<Person>::new(2)));
820 assert!(law.contains(EntityId::<Person>::new(3)));
821 assert!(!law.contains(EntityId::<Person>::new(4)));
822 }
823
824 #[test]
825 fn clone_preserves_composite_expression_behavior() {
826 let a = finite_set(&[1, 2, 3, 4]);
827 let b = finite_set(&[3, 4, 5]);
828 let c = finite_set(&[2]);
829
830 let set = as_entity_set(&a)
831 .difference(as_entity_set(&c))
832 .union(as_entity_set(&b));
833 let cloned = set.clone();
834
835 for value in 0..7 {
836 let entity_id = EntityId::<Person>::new(value);
837 assert_eq!(set.contains(entity_id), cloned.contains(entity_id));
838 }
839
840 assert_eq!(
841 set.into_iter().collect::<Vec<_>>(),
842 cloned.into_iter().collect::<Vec<_>>()
843 );
844 }
845
846 #[test]
847 fn population_zero_is_empty() {
848 let es = EntitySet::from_source(SourceSet::<Person>::empty());
849 assert_eq!(es.sort_key().0, 0);
850 assert!(!es.contains(EntityId::<Person>::new(0)));
851 }
852
853 #[test]
854 fn try_len_known_only_for_non_property_sources() {
855 let empty = EntitySet::<Person>::from_source(SourceSet::empty());
856 assert_eq!(empty.try_len(), Some(0));
857
858 let singleton = EntitySet::<Person>::from_source(SourceSet::singleton(EntityId::new(42)));
859 assert_eq!(singleton.try_len(), Some(1));
860
861 let population = EntitySet::<Person>::from_source(SourceSet::population_range(0..5));
862 assert_eq!(population.try_len(), Some(5));
863
864 let range = EntitySet::<Person>::from_source(SourceSet::population_range(4..9));
865 assert_eq!(range.try_len(), Some(5));
866
867 let index_data = [EntityId::new(1), EntityId::new(2), EntityId::new(3)]
868 .into_iter()
869 .collect::<IndexSet<_>>();
870 let indexed = EntitySet::<Person>::from_source(SourceSet::IndexSet(&index_data));
871 assert_eq!(indexed.try_len(), Some(3));
872
873 let mut context = Context::new();
874 context.add_entity(with!(Person, Age(10))).unwrap();
875 let property_source = SourceSet::<Person>::new(Age(10), &context).unwrap();
876 assert!(matches!(property_source, SourceSet::PropertySet(_)));
877 let property_set = EntitySet::<Person>::from_source(property_source);
878 assert_eq!(property_set.try_len(), None);
879
880 let composed = EntitySet::<Person>::from_source(SourceSet::population_range(0..3))
881 .difference(EntitySet::from_source(SourceSet::singleton(EntityId::new(
882 1,
883 ))));
884 assert_eq!(composed.try_len(), None);
885 }
886
887 #[test]
888 fn range_leaf_works_inside_composite_expressions() {
889 let indexed_ids = finite_set(&[1, 3, 5, 8]);
890 let indexed = as_entity_set(&indexed_ids);
891 let range = EntitySet::from_source(SourceSet::<Person>::population_range(2..8));
892
893 let intersection = range.intersection(indexed);
894 assert!(!intersection.contains(EntityId::new(1)));
895 assert!(intersection.contains(EntityId::new(3)));
896 assert!(intersection.contains(EntityId::new(5)));
897 assert!(!intersection.contains(EntityId::new(8)));
898 }
899
900 #[test]
901 fn clone_preserves_unindexed_concrete_property_query_results() {
902 let mut context = Context::new();
903 let p1 = context.add_entity(with!(Person, Age(10))).unwrap();
904 let p2 = context.add_entity(with!(Person, Age(10))).unwrap();
905 let _p3 = context.add_entity(with!(Person, Age(11))).unwrap();
906
907 let set = context.query::<Person, _>(with!(Person, Age(10)));
908 assert_eq!(set.try_len(), None);
909 let cloned = set.clone();
910
911 let mut iter = set.into_iter();
912 assert_eq!(iter.next(), Some(p1));
913 assert_eq!(iter.collect::<Vec<_>>(), vec![p2]);
914
915 assert!(cloned.contains(p1));
916 assert!(cloned.contains(p2));
917 assert_eq!(cloned.into_iter().collect::<Vec<_>>(), vec![p1, p2]);
918 }
919
920 #[test]
921 fn clone_preserves_unindexed_derived_property_query_results() {
922 let mut context = Context::new();
923 let _p1 = context.add_entity(with!(Person, Age(64))).unwrap();
924 let p2 = context.add_entity(with!(Person, Age(65))).unwrap();
925 let p3 = context.add_entity(with!(Person, Age(90))).unwrap();
926
927 let set = context.query::<Person, _>(with!(Person, Senior(true)));
928 assert_eq!(set.try_len(), None);
929 let cloned = set.clone();
930
931 assert!(set.contains(p2));
932 assert!(set.contains(p3));
933 assert_eq!(set.into_iter().collect::<Vec<_>>(), vec![p2, p3]);
934 assert_eq!(cloned.into_iter().collect::<Vec<_>>(), vec![p2, p3]);
935 }
936
937 #[test]
938 fn union_of_same_unindexed_property_query_deduplicates_to_one_source() {
939 let mut context = Context::new();
940 let p1 = context.add_entity(with!(Person, Age(10))).unwrap();
941 let p2 = context.add_entity(with!(Person, Age(10))).unwrap();
942 let _p3 = context.add_entity(with!(Person, Age(11))).unwrap();
943
944 let query = context.query::<Person, _>(with!(Person, Age(10)));
945 let union = query.clone().union(query);
946
947 assert!(matches!(
948 union,
949 EntitySet {
950 inner: EntitySetInner::Source(SourceSet::PropertySet(_)),
951 ..
952 }
953 ));
954 assert_eq!(union.into_iter().collect::<Vec<_>>(), vec![p1, p2]);
955 }
956
957 #[test]
958 fn union_of_equivalent_unindexed_multi_property_queries_deduplicates_to_one_source() {
959 let mut context = Context::new();
960 let matching = context
961 .add_entity(with!(Person, Age(28), County(7)))
962 .unwrap();
963 let _wrong_county = context
964 .add_entity(with!(Person, Age(28), County(8)))
965 .unwrap();
966 let _wrong_age = context
967 .add_entity(with!(Person, Age(29), County(7)))
968 .unwrap();
969
970 let age_county = context.query::<Person, _>(with!(Person, (Age(28), County(7))));
971 let county_age = context.query::<Person, _>(with!(Person, (County(7), Age(28))));
972 let union = age_county.union(county_age);
973
974 assert!(matches!(
975 union,
976 EntitySet {
977 inner: EntitySetInner::Source(SourceSet::PropertySet(_)),
978 ..
979 }
980 ));
981 assert_eq!(union.into_iter().collect::<Vec<_>>(), vec![matching]);
982 }
983
984 #[test]
985 fn sample_entity_excluding_skips_excluded() {
986 let set = EntitySet::from_source(SourceSet::<Person>::PopulationRange(0..5));
987 let excluded = EntityId::<Person>::new(2);
988 let mut rng = StdRng::seed_from_u64(42);
989 for _ in 0..200 {
990 let sampled = set.sample_entity_excluding(&mut rng, excluded).unwrap();
991 assert_ne!(sampled, excluded);
992 assert!(sampled.0 < 5);
993 }
994 }
995
996 #[test]
997 fn sample_entity_excluding_returns_none_when_only_excluded_present() {
998 let only = EntityId::<Person>::new(7);
999 let single = finite_set(&[7]);
1000 let mut rng = StdRng::seed_from_u64(42);
1001 assert_eq!(
1002 as_entity_set(&single).sample_entity_excluding(&mut rng, only),
1003 None
1004 );
1005 }
1006
1007 #[test]
1008 fn sample_entity_excluding_returns_none_on_empty() {
1009 let mut rng = StdRng::seed_from_u64(42);
1010 assert_eq!(
1011 EntitySet::<Person>::empty()
1012 .sample_entity_excluding(&mut rng, EntityId::<Person>::new(0)),
1013 None
1014 );
1015 }
1016
1017 #[test]
1018 fn sample_entity_excluding_excluded_not_in_set_uses_first_pick() {
1019 let set = EntitySet::from_source(SourceSet::<Person>::PopulationRange(0..10));
1022 let mut rng = StdRng::seed_from_u64(42);
1023 for _ in 0..200 {
1024 let sampled = set
1025 .sample_entity_excluding(&mut rng, EntityId::<Person>::new(999))
1026 .unwrap();
1027 assert!(sampled.0 < 10);
1028 }
1029 }
1030
1031 #[test]
1032 fn sample_entity_excluding_uniform_over_known_length() {
1033 let excluded = EntityId::<Person>::new(7);
1035 let set = EntitySet::from_source(SourceSet::<Person>::PopulationRange(0..20));
1036 let num_runs = 50_000;
1037 let mut counts = [0usize; 20];
1038 let mut rng = StdRng::seed_from_u64(42);
1039 for _ in 0..num_runs {
1040 let id = set.sample_entity_excluding(&mut rng, excluded).unwrap();
1041 counts[id.0] += 1;
1042 }
1043 assert_eq!(counts[excluded.0], 0);
1044
1045 let expected = num_runs as f64 / 19.0;
1046 let chi_square: f64 = counts
1047 .iter()
1048 .enumerate()
1049 .filter(|(i, _)| *i != excluded.0)
1050 .map(|(_, &obs)| {
1051 let diff = obs as f64 - expected;
1052 diff * diff / expected
1053 })
1054 .sum();
1055 assert!(chi_square < 42.31, "χ² = {chi_square}");
1057 }
1058}