1mod query_impls;
2
3use std::any::TypeId;
4use std::marker::PhantomData;
5use std::sync::{Mutex, OnceLock};
6
7use crate::data_structures::bit_set::BitSet;
8use crate::entity::entity_set::{EntitySet, EntitySetIterator};
9use crate::entity::multi_property::type_ids_to_multi_property_id;
10#[cfg(feature = "profiling")]
11use crate::entity::multi_property::{intern_query_identity, QueryIdentityId};
12use crate::entity::property_list::{PropertyInitializationList, PropertyList};
13use crate::entity::property_store::PropertyStore;
14use crate::entity::Entity;
15use crate::hashing::HashMap;
16use crate::prelude::EntityId;
17use crate::{Context, IxaError};
18
19pub struct EntityPropertyTuple<E: Entity, T> {
35 inner: T,
36 _marker: PhantomData<E>,
37}
38
39impl<E: Entity, T: Copy> Copy for EntityPropertyTuple<E, T> {}
41
42impl<E: Entity, T: Clone> Clone for EntityPropertyTuple<E, T> {
43 fn clone(&self) -> Self {
44 Self {
45 inner: self.inner.clone(),
46 _marker: PhantomData,
47 }
48 }
49}
50
51impl<E: Entity, T: std::fmt::Debug> std::fmt::Debug for EntityPropertyTuple<E, T> {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 f.debug_struct("EntityPropertyTuple")
54 .field("inner", &self.inner)
55 .finish()
56 }
57}
58
59impl<E: Entity, T> EntityPropertyTuple<E, T> {
60 pub fn new(inner: T) -> Self {
62 Self {
63 inner,
64 _marker: PhantomData,
65 }
66 }
67
68 pub fn inner(&self) -> &T {
70 &self.inner
71 }
72
73 pub fn into_inner(self) -> T {
75 self.inner
76 }
77}
78
79impl<E: Entity, T: QueryInternal<E>> QueryInternal<E> for EntityPropertyTuple<E, T> {
80 type QueryParts<'a>
81 = T::QueryParts<'a>
82 where
83 Self: 'a;
84
85 fn get_type_ids(&self) -> Vec<TypeId> {
86 self.inner.get_type_ids()
87 }
88
89 fn resolved_query(&self) -> ResolvedQuery {
90 self.inner.resolved_query()
91 }
92
93 fn is_empty_query(&self) -> bool {
94 self.inner.is_empty_query()
95 }
96
97 fn query_parts(&self) -> Self::QueryParts<'_> {
98 self.inner.query_parts()
99 }
100
101 fn new_query_result<'c>(&self, context: &'c Context) -> EntitySet<'c, E> {
102 self.inner.new_query_result(context)
103 }
104
105 fn match_entity(&self, entity_id: EntityId<E>, context: &Context) -> bool {
106 self.inner.match_entity(entity_id, context)
107 }
108
109 fn filter_entities(&self, entities: &mut Vec<EntityId<E>>, context: &Context) {
110 self.inner.filter_entities(entities, context)
111 }
112}
113
114impl<E: Entity, T: PropertyList<E>> PropertyList<E> for EntityPropertyTuple<E, T> {
115 fn validate() -> Result<(), IxaError> {
116 T::validate()
117 }
118
119 fn contains_properties(property_type_ids: &[TypeId]) -> bool {
120 T::contains_properties(property_type_ids)
121 }
122
123 fn set_values_for_new_entity(
124 &self,
125 entity_id: EntityId<E>,
126 property_store: &mut PropertyStore<E>,
127 ) {
128 let tuple = *self;
129 tuple
130 .into_inner()
131 .set_values_for_new_entity(entity_id, property_store)
132 }
133
134 fn emit_initialized_events(
135 &self,
136 context: &mut Context,
137 entity_id: EntityId<E>,
138 subscriptions: &BitSet,
139 ) {
140 self.inner
141 .emit_initialized_events(context, entity_id, subscriptions);
142 }
143
144 fn get_values_for_entity(context: &Context, entity_id: EntityId<E>) -> Self {
145 EntityPropertyTuple::new(T::get_values_for_entity(context, entity_id))
146 }
147}
148
149impl<E: Entity, PL: PropertyList<E>> PropertyInitializationList<E> for EntityPropertyTuple<E, PL> {}
150
151#[derive(Clone, Copy, Debug, Eq, PartialEq)]
153#[doc(hidden)]
154pub struct ResolvedQuery {
155 pub(crate) index_property_id: Option<usize>,
156 #[cfg(feature = "profiling")]
157 pub(crate) identity: QueryIdentityId,
158}
159
160pub trait QueryInternal<E: Entity>: 'static {
162 type QueryParts<'a>: AsRef<[&'a dyn std::any::Any]>
164 where
165 Self: 'a;
166
167 #[must_use]
170 fn get_type_ids(&self) -> Vec<TypeId>;
171
172 fn resolve_index_property_id(&self, type_ids: &[TypeId]) -> Option<usize> {
175 type_ids_to_multi_property_id(E::id(), type_ids)
176 }
177
178 #[must_use]
180 fn resolved_query(&self) -> ResolvedQuery
181 where
182 Self: Sized,
183 {
184 static REGISTRY: OnceLock<Mutex<HashMap<(usize, TypeId), ResolvedQuery>>> = OnceLock::new();
185
186 let map = REGISTRY.get_or_init(|| Mutex::new(HashMap::default()));
187 let key = (E::id(), TypeId::of::<Self>());
188 if let Some(resolved) = map.lock().unwrap().get(&key).copied() {
189 return resolved;
190 }
191
192 let mut type_ids = self.get_type_ids();
193 type_ids.sort_unstable();
194 let resolved = ResolvedQuery {
195 index_property_id: self.resolve_index_property_id(&type_ids),
196 #[cfg(feature = "profiling")]
197 identity: intern_query_identity::<E>(&type_ids),
198 };
199
200 *map.lock().unwrap().entry(key).or_insert(resolved)
201 }
202
203 #[must_use]
206 fn multi_property_id(&self) -> Option<usize>
207 where
208 Self: Sized,
209 {
210 self.resolved_query().index_property_id
211 }
212
213 #[must_use]
215 fn is_empty_query(&self) -> bool {
216 false
217 }
218
219 #[must_use]
221 fn query_parts(&self) -> Self::QueryParts<'_>;
222
223 #[must_use]
225 fn new_query_result<'c>(&self, context: &'c Context) -> EntitySet<'c, E>;
226
227 #[must_use]
229 fn new_query_result_iterator<'c>(&self, context: &'c Context) -> EntitySetIterator<'c, E> {
230 self.new_query_result(context).into_iter()
231 }
232
233 #[must_use]
235 fn match_entity(&self, entity_id: EntityId<E>, context: &Context) -> bool;
236
237 fn filter_entities(&self, entities: &mut Vec<EntityId<E>>, context: &Context);
239}
240
241pub trait Query<E: Entity>: QueryInternal<E> {}
249
250impl<E: Entity, QI: QueryInternal<E>> Query<E> for EntityPropertyTuple<E, QI> {}
251impl<E: Entity> Query<E> for E {}
252
253#[cfg(test)]
254mod tests {
255 use super::QueryInternal;
256 #[cfg(feature = "profiling")]
257 use super::{EntityId, EntityPropertyTuple, EntitySet};
258 #[cfg(feature = "profiling")]
259 use crate::entity::multi_property::query_identity_label;
260 use crate::prelude::*;
261 use crate::{
262 define_derived_property, define_entity, define_multi_property, define_property, Context,
263 };
264
265 define_entity!(Person);
266 #[cfg(feature = "profiling")]
267 define_entity!(Case);
268
269 define_property!(struct Age(u8), Person, default_const = Age(0));
270 define_property!(struct County(u32), Person, default_const = County(0));
271 define_property!(struct Height(u32), Person, default_const = Height(0));
272 define_property!(
273 enum RiskCategory {
274 High,
275 Low,
276 },
277 Person
278 );
279
280 define_multi_property!(Person, (Age, County));
281
282 #[test]
283 fn empty_tuple_query_internal_matches_all_entities() {
284 let mut context = Context::new();
285 let person1 = context
286 .add_entity(with!(Person, Age(42), RiskCategory::High))
287 .unwrap();
288 let person2 = context
289 .add_entity(with!(Person, Age(30), RiskCategory::Low))
290 .unwrap();
291
292 assert_eq!(<() as QueryInternal<Person>>::get_type_ids(&()), Vec::new());
293 assert!(<() as QueryInternal<Person>>::is_empty_query(&()));
294 assert!(<() as QueryInternal<Person>>::query_parts(&())
295 .as_ref()
296 .is_empty());
297
298 let people = <() as QueryInternal<Person>>::new_query_result_iterator(&(), &context)
299 .collect::<Vec<_>>();
300 assert_eq!(people, vec![person1, person2]);
301 assert!(<() as QueryInternal<Person>>::match_entity(
302 &(),
303 person1,
304 &context
305 ));
306
307 let mut ids = vec![person1, person2];
308 <() as QueryInternal<Person>>::filter_entities(&(), &mut ids, &context);
309 assert_eq!(ids, vec![person1, person2]);
310 }
311
312 #[test]
313 fn entity_query_internal_has_no_type_ids() {
314 let mut context = Context::new();
315 let person1 = context
316 .add_entity(with!(Person, Age(42), RiskCategory::High))
317 .unwrap();
318 let person2 = context
319 .add_entity(with!(Person, Age(30), RiskCategory::Low))
320 .unwrap();
321
322 assert_eq!(
323 <Person as QueryInternal<Person>>::get_type_ids(&Person),
324 Vec::new()
325 );
326 assert_eq!(
327 <Person as QueryInternal<Person>>::multi_property_id(&Person),
328 None
329 );
330 assert!(<Person as QueryInternal<Person>>::query_parts(&Person)
331 .as_ref()
332 .is_empty());
333
334 let people = <Person as QueryInternal<Person>>::new_query_result(&Person, &context)
335 .into_iter()
336 .collect::<Vec<_>>();
337 assert_eq!(people, vec![person1, person2]);
338 assert!(<Person as QueryInternal<Person>>::match_entity(
339 &Person, person1, &context
340 ));
341
342 let mut ids = vec![person1, person2];
343 <Person as QueryInternal<Person>>::filter_entities(&Person, &mut ids, &context);
344 assert_eq!(ids, vec![person1, person2]);
345 }
346
347 #[cfg(feature = "profiling")]
348 #[test]
349 fn empty_queries_share_entity_all_query_identity() {
350 let empty = <() as QueryInternal<Person>>::resolved_query(&());
351 let entity = <Person as QueryInternal<Person>>::resolved_query(&Person);
352
353 assert_eq!(empty.index_property_id, None);
354 assert_eq!(entity.index_property_id, None);
355 assert_eq!(empty.identity, entity.identity);
356 assert_eq!(query_identity_label(empty.identity), "Person: All");
357 assert_eq!(
358 <() as QueryInternal<Person>>::resolved_query(&()).identity,
359 empty.identity
360 );
361 }
362
363 #[cfg(feature = "profiling")]
364 #[test]
365 fn entity_type_is_part_of_query_identity() {
366 let person = <Person as QueryInternal<Person>>::resolved_query(&Person);
367 let case = <Case as QueryInternal<Case>>::resolved_query(&Case);
368
369 assert_ne!(person.identity, case.identity);
370 }
371
372 #[test]
373 fn singleton_query_resolves_direct_property_id() {
374 let query = (Age(42),);
375 let resolved = <(Age,) as QueryInternal<Person>>::resolved_query(&query);
376
377 assert_eq!(resolved.index_property_id, Some(Age::id()));
378
379 #[cfg(feature = "profiling")]
380 {
381 assert_eq!(
382 resolved.identity,
383 <(Age,) as QueryInternal<Person>>::resolved_query(&(Age(7),)).identity
384 );
385 assert_eq!(query_identity_label(resolved.identity), "Person: (Age)");
386 }
387 }
388
389 #[cfg(feature = "profiling")]
390 #[test]
391 fn multi_property_query_identity_ignores_order_and_values() {
392 let query = (Age(42), County(1));
393 let resolved = <(Age, County) as QueryInternal<Person>>::resolved_query(&query);
394
395 assert_eq!(
396 query_identity_label(resolved.identity),
397 "Person: (Age, County)"
398 );
399
400 let reversed_query = (County(2), Age(20));
401 let reversed = <(County, Age) as QueryInternal<Person>>::resolved_query(&reversed_query);
402 assert_eq!(resolved.identity, reversed.identity);
403 assert_eq!(resolved.index_property_id, reversed.index_property_id);
404 }
405
406 #[cfg(feature = "profiling")]
407 #[test]
408 fn unregistered_multi_property_shape_still_has_an_identity() {
409 let query = (Age(42), Height(70));
410 let resolved = <(Age, Height) as QueryInternal<Person>>::resolved_query(&query);
411
412 assert_eq!(resolved.index_property_id, None);
413 assert_eq!(
414 query_identity_label(resolved.identity),
415 "Person: (Age, Height)"
416 );
417 }
418
419 #[cfg(feature = "profiling")]
420 #[test]
421 fn entity_property_tuple_delegates_complete_query_resolution() {
422 let query = EntityPropertyTuple::<Person, _>::new((Age(42), County(1)));
423 let inner = (Age(20), County(2));
424
425 assert_eq!(query.resolved_query(), inner.resolved_query());
426 }
427
428 #[cfg(feature = "profiling")]
429 #[test]
430 fn custom_query_internal_impl_can_use_default_profile_names() {
431 use std::any::{Any, TypeId};
432
433 struct CustomQuery;
434
435 impl QueryInternal<Person> for CustomQuery {
436 type QueryParts<'a>
437 = [&'a dyn Any; 0]
438 where
439 Self: 'a;
440
441 fn get_type_ids(&self) -> Vec<TypeId> {
442 Vec::new()
443 }
444
445 fn query_parts(&self) -> Self::QueryParts<'_> {
446 []
447 }
448
449 fn new_query_result<'c>(&self, context: &'c Context) -> EntitySet<'c, Person> {
450 <() as QueryInternal<Person>>::new_query_result(&(), context)
451 }
452
453 fn match_entity(&self, entity_id: EntityId<Person>, context: &Context) -> bool {
454 <() as QueryInternal<Person>>::match_entity(&(), entity_id, context)
455 }
456
457 fn filter_entities(&self, entities: &mut Vec<EntityId<Person>>, context: &Context) {
458 <() as QueryInternal<Person>>::filter_entities(&(), entities, context);
459 }
460 }
461
462 let resolved = CustomQuery.resolved_query();
463 assert_eq!(
464 resolved.identity,
465 <Person as QueryInternal<Person>>::resolved_query(&Person).identity
466 );
467 assert_eq!(query_identity_label(resolved.identity), "Person: All");
468 }
469
470 #[test]
471 fn singleton_query_result_iterator_uses_indexed_and_unindexed_paths() {
472 let mut context = Context::new();
473 let high = context
474 .add_entity(with!(Person, RiskCategory::High))
475 .unwrap();
476 let _low = context
477 .add_entity(with!(Person, RiskCategory::Low))
478 .unwrap();
479
480 let people = <(RiskCategory,) as QueryInternal<Person>>::new_query_result_iterator(
481 &(RiskCategory::High,),
482 &context,
483 )
484 .collect::<Vec<_>>();
485 assert_eq!(people, vec![high]);
486
487 let mut indexed_context = Context::new();
488 indexed_context.index_property::<Person, RiskCategory>();
489 let indexed_high = indexed_context
490 .add_entity(with!(Person, RiskCategory::High))
491 .unwrap();
492 let _indexed_low = indexed_context
493 .add_entity(with!(Person, RiskCategory::Low))
494 .unwrap();
495
496 let people = <(RiskCategory,) as QueryInternal<Person>>::new_query_result_iterator(
497 &(RiskCategory::High,),
498 &indexed_context,
499 )
500 .collect::<Vec<_>>();
501 assert_eq!(people, vec![indexed_high]);
502
503 let mut empty_index_context = Context::new();
504 empty_index_context.index_property::<Person, Age>();
505 let _ = empty_index_context
506 .add_entity(with!(Person, Age(42), RiskCategory::Low))
507 .unwrap();
508
509 let people = <(Age,) as QueryInternal<Person>>::new_query_result_iterator(
510 &(Age(99),),
511 &empty_index_context,
512 )
513 .collect::<Vec<_>>();
514 assert!(people.is_empty());
515 }
516
517 #[test]
518 fn tuple_query_result_iterator_uses_indexed_and_unindexed_paths() {
519 let mut context = Context::new();
520 let matching1 = context
521 .add_entity(with!(Person, Age(28), County(0), RiskCategory::High))
522 .unwrap();
523 let _wrong_county = context
524 .add_entity(with!(Person, Age(28), County(1), RiskCategory::Low))
525 .unwrap();
526 let _wrong_age = context
527 .add_entity(with!(Person, Age(30), County(0), RiskCategory::Low))
528 .unwrap();
529 let matching2 = context
530 .add_entity(with!(Person, Age(28), County(0), RiskCategory::Low))
531 .unwrap();
532
533 let people = <(Age, County) as QueryInternal<Person>>::new_query_result_iterator(
534 &(Age(28), County(0)),
535 &context,
536 )
537 .collect::<Vec<_>>();
538 assert_eq!(people, vec![matching1, matching2]);
539
540 let mut indexed_context = Context::new();
541 indexed_context.index_property::<Person, (Age, County)>();
542 let indexed_matching = indexed_context
543 .add_entity(with!(Person, Age(28), County(0), RiskCategory::High))
544 .unwrap();
545 let _indexed_nonmatching = indexed_context
546 .add_entity(with!(Person, Age(28), County(1), RiskCategory::Low))
547 .unwrap();
548
549 let people = <(Age, County) as QueryInternal<Person>>::new_query_result_iterator(
550 &(Age(28), County(0)),
551 &indexed_context,
552 )
553 .collect::<Vec<_>>();
554 assert_eq!(people, vec![indexed_matching]);
555
556 let people = <(Age, County) as QueryInternal<Person>>::new_query_result_iterator(
557 &(Age(99), County(99)),
558 &indexed_context,
559 )
560 .collect::<Vec<_>>();
561 assert!(people.is_empty());
562 }
563
564 #[test]
565 fn singleton_filter_entities_keeps_matching_entities() {
566 let mut context = Context::new();
567 let high1 = context
568 .add_entity(with!(Person, RiskCategory::High))
569 .unwrap();
570 let low = context
571 .add_entity(with!(Person, RiskCategory::Low))
572 .unwrap();
573 let high2 = context
574 .add_entity(with!(Person, RiskCategory::High))
575 .unwrap();
576
577 let mut people = vec![high1, low, high2];
578 context.filter_entities(&mut people, with!(Person, RiskCategory::High));
579
580 assert_eq!(people, vec![high1, high2]);
581 }
582
583 #[test]
584 fn tuple_filter_entities_falls_through_after_unsupported_multi_index_lookup() {
585 let mut context = Context::new();
586 let matching1 = context
587 .add_entity(with!(Person, Age(28), County(0), RiskCategory::High))
588 .unwrap();
589 let wrong_county = context
590 .add_entity(with!(Person, Age(28), County(1), RiskCategory::Low))
591 .unwrap();
592 let wrong_age = context
593 .add_entity(with!(Person, Age(30), County(0), RiskCategory::Low))
594 .unwrap();
595 let matching2 = context
596 .add_entity(with!(Person, Age(28), County(0), RiskCategory::Low))
597 .unwrap();
598
599 let mut people = vec![matching1, wrong_county, wrong_age, matching2];
600 context.filter_entities(&mut people, with!(Person, County(0), Age(28)));
601
602 assert_eq!(people, vec![matching1, matching2]);
603 }
604
605 #[test]
606 fn with_query_results() {
607 let mut context = Context::new();
608 let _ = context
609 .add_entity(with!(Person, RiskCategory::High))
610 .unwrap();
611
612 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
613 assert_eq!(people.into_iter().count(), 1);
614 });
615 }
616
617 #[test]
618 fn with_query_results_empty() {
619 let context = Context::new();
620
621 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
622 assert_eq!(people.into_iter().count(), 0);
623 });
624 }
625
626 #[test]
627 fn query_entity_count() {
628 let mut context = Context::new();
629 let _ = context
630 .add_entity(with!(Person, RiskCategory::High))
631 .unwrap();
632
633 assert_eq!(
634 context.query_entity_count(with!(Person, RiskCategory::High)),
635 1
636 );
637 }
638
639 #[test]
640 fn query_entity_count_empty() {
641 let context = Context::new();
642
643 assert_eq!(
644 context.query_entity_count(with!(Person, RiskCategory::High)),
645 0
646 );
647 }
648
649 #[test]
650 fn with_query_results_macro_index_first() {
651 let mut context = Context::new();
652 let _ = context
653 .add_entity(with!(Person, RiskCategory::High))
654 .unwrap();
655 context.index_property::<_, RiskCategory>();
656 assert!(context.is_property_indexed::<Person, RiskCategory>());
657
658 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
659 assert_eq!(people.into_iter().count(), 1);
660 });
661 }
662
663 #[test]
664 fn with_query_results_macro_index_second() {
665 let mut context = Context::new();
666 let _ = context.add_entity(with!(Person, RiskCategory::High));
667
668 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
669 assert_eq!(people.into_iter().count(), 1);
670 });
671 assert!(!context.is_property_indexed::<Person, RiskCategory>());
672
673 context.index_property::<Person, RiskCategory>();
674 assert!(context.is_property_indexed::<Person, RiskCategory>());
675
676 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
677 assert_eq!(people.into_iter().count(), 1);
678 });
679 }
680
681 #[test]
682 fn with_query_results_macro_change() {
683 let mut context = Context::new();
684 let person1 = context
685 .add_entity(with!(Person, RiskCategory::High))
686 .unwrap();
687
688 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
689 assert_eq!(people.into_iter().count(), 1);
690 });
691
692 context.with_query_results(with!(Person, RiskCategory::Low), &mut |people| {
693 assert_eq!(people.into_iter().count(), 0);
694 });
695
696 context.set_property(person1, RiskCategory::Low);
697 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
698 assert_eq!(people.into_iter().count(), 0);
699 });
700
701 context.with_query_results(with!(Person, RiskCategory::Low), &mut |people| {
702 assert_eq!(people.into_iter().count(), 1);
703 });
704 }
705
706 #[test]
707 fn with_query_results_index_after_add() {
708 let mut context = Context::new();
709 let _ = context
710 .add_entity(with!(Person, RiskCategory::High))
711 .unwrap();
712 context.index_property::<Person, RiskCategory>();
713 assert!(context.is_property_indexed::<Person, RiskCategory>());
714 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
715 assert_eq!(people.into_iter().count(), 1);
716 });
717 }
718
719 #[test]
720 fn with_query_results_add_after_index() {
721 let mut context = Context::new();
722 let _ = context
723 .add_entity(with!(Person, RiskCategory::High))
724 .unwrap();
725 context.index_property::<Person, RiskCategory>();
726 assert!(context.is_property_indexed::<Person, RiskCategory>());
727 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
728 assert_eq!(people.into_iter().count(), 1);
729 });
730
731 let _ = context
732 .add_entity(with!(Person, RiskCategory::High))
733 .unwrap();
734 context.with_query_results(with!(Person, RiskCategory::High), &mut |people| {
735 assert_eq!(people.into_iter().count(), 2);
736 });
737 }
738
739 #[test]
740 fn with_query_results_cast_value() {
741 let mut context = Context::new();
742 let _ = context
743 .add_entity(with!(Person, Age(42), RiskCategory::High))
744 .unwrap();
745
746 context.with_query_results(with!(Person, Age(42)), &mut |people| {
747 assert_eq!(people.into_iter().count(), 1);
748 });
749 }
750
751 #[test]
752 fn with_query_results_intersection() {
753 let mut context = Context::new();
754 let _ = context
755 .add_entity(with!(Person, Age(42), RiskCategory::High))
756 .unwrap();
757 let _ = context
758 .add_entity(with!(Person, Age(42), RiskCategory::Low))
759 .unwrap();
760 let _ = context
761 .add_entity(with!(Person, Age(40), RiskCategory::Low))
762 .unwrap();
763
764 context.with_query_results(with!(Person, Age(42), RiskCategory::High), &mut |people| {
765 assert_eq!(people.into_iter().count(), 1);
766 });
767 }
768
769 #[test]
770 fn with_query_results_intersection_non_macro() {
771 let mut context = Context::new();
772 let _ = context
773 .add_entity(with!(Person, Age(42), RiskCategory::High))
774 .unwrap();
775 let _ = context
776 .add_entity(with!(Person, Age(42), RiskCategory::Low))
777 .unwrap();
778 let _ = context
779 .add_entity(with!(Person, Age(40), RiskCategory::Low))
780 .unwrap();
781
782 context.with_query_results(with!(Person, Age(42), RiskCategory::High), &mut |people| {
783 assert_eq!(people.into_iter().count(), 1);
784 });
785 }
786
787 #[test]
788 fn with_query_results_intersection_one_indexed() {
789 let mut context = Context::new();
790 let _ = context
791 .add_entity(with!(Person, Age(42), RiskCategory::High))
792 .unwrap();
793 let _ = context
794 .add_entity(with!(Person, Age(42), RiskCategory::Low))
795 .unwrap();
796 let _ = context
797 .add_entity(with!(Person, Age(40), RiskCategory::Low))
798 .unwrap();
799
800 context.index_property::<Person, Age>();
801 context.with_query_results(with!(Person, Age(42), RiskCategory::High), &mut |people| {
802 assert_eq!(people.into_iter().count(), 1);
803 });
804 }
805
806 #[test]
807 fn query_derived_prop() {
808 let mut context = Context::new();
809 define_derived_property!(struct Senior(bool), Person, [Age], |age| Senior(age.0 >= 65));
810
811 let person = context
812 .add_entity(with!(Person, Age(64), RiskCategory::High))
813 .unwrap();
814 context
815 .add_entity(with!(Person, Age(88), RiskCategory::High))
816 .unwrap();
817
818 let mut not_seniors = Vec::new();
819 context.with_query_results(with!(Person, Senior(false)), &mut |people| {
820 not_seniors = people.to_owned_vec();
821 });
822 let mut seniors = Vec::new();
823 context.with_query_results(with!(Person, Senior(true)), &mut |people| {
824 seniors = people.to_owned_vec();
825 });
826 assert_eq!(seniors.len(), 1, "One senior");
827 assert_eq!(not_seniors.len(), 1, "One non-senior");
828
829 context.set_property(person, Age(65));
830
831 context.with_query_results(with!(Person, Senior(false)), &mut |people| {
832 not_seniors = people.to_owned_vec()
833 });
834 context.with_query_results(with!(Person, Senior(true)), &mut |people| {
835 seniors = people.to_owned_vec()
836 });
837
838 assert_eq!(seniors.len(), 2, "Two seniors");
839 assert_eq!(not_seniors.len(), 0, "No non-seniors");
840 }
841
842 #[test]
843 fn query_derived_prop_with_index() {
844 let mut context = Context::new();
845 define_derived_property!(struct Senior(bool), Person, [Age], |age| Senior(age.0 >= 65));
846
847 context.index_property::<Person, Senior>();
848 let person = context
849 .add_entity(with!(Person, Age(64), RiskCategory::Low))
850 .unwrap();
851 let _ = context.add_entity(with!(Person, Age(88), RiskCategory::Low));
852
853 let mut not_seniors = Vec::new();
854 context.with_query_results(with!(Person, Senior(false)), &mut |people| {
855 not_seniors = people.to_owned_vec()
856 });
857 let mut seniors = Vec::new();
858 context.with_query_results(with!(Person, Senior(true)), &mut |people| {
859 seniors = people.to_owned_vec()
860 });
861 assert_eq!(seniors.len(), 1, "One senior");
862 assert_eq!(not_seniors.len(), 1, "One non-senior");
863
864 context.set_property(person, Age(65));
865
866 context.with_query_results(with!(Person, Senior(false)), &mut |people| {
867 not_seniors = people.to_owned_vec()
868 });
869 context.with_query_results(with!(Person, Senior(true)), &mut |people| {
870 seniors = people.to_owned_vec()
871 });
872
873 assert_eq!(seniors.len(), 2, "Two seniors");
874 assert_eq!(not_seniors.len(), 0, "No non-seniors");
875 }
876
877 define_multi_property!(Person, (Age, County, Height));
879 define_multi_property!(Person, (County, Height));
880
881 #[test]
882 fn query_derived_prop_with_optimized_index() {
883 let mut context = Context::new();
884 define_derived_property!(
886 struct Ach(u8, u32, u32),
887 Person,
888 [Age, County, Height],
889 [],
890 |age, county, height| Ach(age.0, county.0, height.0)
891 );
892
893 let _ = context.add_entity(with!(
895 Person,
896 Age(64),
897 County(2),
898 Height(120),
899 RiskCategory::Low
900 ));
901 let _ = context.add_entity(with!(
902 Person,
903 Age(88),
904 County(2),
905 Height(130),
906 RiskCategory::Low
907 ));
908 let p2 = context
909 .add_entity(with!(
910 Person,
911 Age(8),
912 County(1),
913 Height(140),
914 RiskCategory::Low
915 ))
916 .unwrap();
917 let p3 = context
918 .add_entity(with!(
919 Person,
920 Age(28),
921 County(1),
922 Height(140),
923 RiskCategory::Low
924 ))
925 .unwrap();
926 let p4 = context
927 .add_entity(with!(
928 Person,
929 Age(28),
930 County(2),
931 Height(160),
932 RiskCategory::Low
933 ))
934 .unwrap();
935 let p5 = context
936 .add_entity(with!(
937 Person,
938 Age(28),
939 County(2),
940 Height(160),
941 RiskCategory::Low
942 ))
943 .unwrap();
944
945 context.with_query_results(with!(Person, Ach(28, 2, 160)), &mut |people| {
947 assert!(people.contains(p4));
948 assert!(people.contains(p5));
949 assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
950 });
951
952 context.with_query_results(
954 with!(Person, Age(28), County(2), Height(160)),
955 &mut |people| {
956 assert!(people.contains(p4));
957 assert!(people.contains(p5));
958 assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
959 },
960 );
961
962 context.with_query_results(
964 with!(Person, County(2), Height(160), Age(28)),
965 &mut |people| {
966 assert!(people.contains(p4));
967 assert!(people.contains(p5));
968 assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
969 },
970 );
971
972 context.with_query_results(
974 with!(Person, Height(160), County(2), Age(28)),
975 &mut |people| {
976 assert!(people.contains(p4));
977 assert!(people.contains(p5));
978 assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
979 },
980 );
981
982 context.with_query_results(
984 with!(Person, Height(140), County(1), Age(28)),
985 &mut |people| {
986 assert!(people.contains(p3));
987 assert_eq!(people.into_iter().count(), 1, "Should have 1 matches");
988 },
989 );
990
991 context.set_property(p2, Age(28));
992 context.with_query_results(
994 with!(Person, Height(140), County(1), Age(28)),
995 &mut |people| {
996 assert!(people.contains(p2));
997 assert!(people.contains(p3));
998 assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
999 },
1000 );
1001
1002 context.with_query_results(with!(Person, Height(140), County(1)), &mut |people| {
1003 assert!(people.contains(p2));
1004 assert!(people.contains(p3));
1005 assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
1006 });
1007 }
1008
1009 #[test]
1010 fn test_match_entity() {
1011 let mut context = Context::new();
1012 let person = context
1013 .add_entity(with!(
1014 Person,
1015 Age(28),
1016 County(2),
1017 Height(160),
1018 RiskCategory::Low
1019 ))
1020 .unwrap();
1021 assert!(context.match_entity(person, with!(Person, Age(28), County(2), Height(160))));
1022 assert!(!context.match_entity(person, with!(Person, Age(13), County(2), Height(160))));
1023 assert!(!context.match_entity(person, with!(Person, Age(28), County(33), Height(160))));
1024 assert!(!context.match_entity(person, with!(Person, Age(28), County(2), Height(9))));
1025 }
1026
1027 #[test]
1028 fn filter_entities_for_unindexed_query() {
1029 let mut context = Context::new();
1030 let mut people = Vec::new();
1031
1032 for idx in 0..10 {
1033 let person = context
1034 .add_entity(with!(
1035 Person,
1036 Age(28),
1037 County(idx % 2),
1038 Height(160),
1039 RiskCategory::Low
1040 ))
1041 .unwrap();
1042 people.push(person);
1043 }
1044
1045 context.filter_entities(
1046 &mut people,
1047 with!(Person, Age(28), County(0), Height(160), RiskCategory::Low),
1048 );
1049
1050 let expected = (0..5)
1051 .map(|idx| PersonId::new(idx * 2))
1052 .collect::<Vec<PersonId>>();
1053 assert_eq!(people, expected);
1054 }
1055
1056 #[test]
1057 fn filter_entities_for_indexed_query() {
1058 let mut context = Context::new();
1059 let mut people = Vec::new();
1060
1061 context.index_property::<Person, (Age, County)>();
1062
1063 for idx in 0..10 {
1064 let person = context
1065 .add_entity(with!(
1066 Person,
1067 Age(28),
1068 County(idx % 2),
1069 Height(160),
1070 RiskCategory::Low
1071 ))
1072 .unwrap();
1073 people.push(person);
1074 }
1075
1076 context.filter_entities(&mut people, with!(Person, County(0), Age(28)));
1077
1078 let expected = (0..5)
1079 .map(|idx| PersonId::new(idx * 2))
1080 .collect::<Vec<PersonId>>();
1081 assert_eq!(people, expected);
1082 }
1083
1084 #[test]
1085 fn entity_property_tuple_basic() {
1086 use super::EntityPropertyTuple;
1087
1088 let mut context = Context::new();
1089 let p1 = context
1090 .add_entity(with!(Person, Age(42), RiskCategory::High))
1091 .unwrap();
1092 let _ = context
1093 .add_entity(with!(Person, Age(42), RiskCategory::Low))
1094 .unwrap();
1095 let _ = context
1096 .add_entity(with!(Person, Age(30), RiskCategory::High))
1097 .unwrap();
1098
1099 let query: EntityPropertyTuple<Person, _> =
1101 EntityPropertyTuple::new((Age(42), RiskCategory::High));
1102
1103 context.with_query_results(query, &mut |people| {
1104 assert!(people.contains(p1));
1105 assert_eq!(people.into_iter().count(), 1);
1106 });
1107
1108 assert!(context.match_entity(p1, query));
1110
1111 assert_eq!(context.query_entity_count(query), 1);
1113 }
1114
1115 #[test]
1116 fn entity_property_tuple_empty_query() {
1117 use super::EntityPropertyTuple;
1118
1119 let mut context = Context::new();
1120 let _ = context
1121 .add_entity(with!(Person, Age(42), RiskCategory::High))
1122 .unwrap();
1123 let _ = context
1124 .add_entity(with!(Person, Age(30), RiskCategory::Low))
1125 .unwrap();
1126
1127 let query: EntityPropertyTuple<Person, _> = EntityPropertyTuple::new(());
1129
1130 assert_eq!(context.query_entity_count(query), 2);
1131 }
1132
1133 #[test]
1134 fn entity_property_tuple_singleton() {
1135 use super::EntityPropertyTuple;
1136
1137 let mut context = Context::new();
1138 let _ = context
1139 .add_entity(with!(Person, Age(42), RiskCategory::High))
1140 .unwrap();
1141 let _ = context
1142 .add_entity(with!(Person, Age(42), RiskCategory::Low))
1143 .unwrap();
1144 let _ = context
1145 .add_entity(with!(Person, Age(30), RiskCategory::High))
1146 .unwrap();
1147
1148 let query: EntityPropertyTuple<Person, _> = EntityPropertyTuple::new((Age(42),));
1150
1151 assert_eq!(context.query_entity_count(query), 2);
1152 }
1153
1154 #[test]
1155 fn entity_property_tuple_inner_access() {
1156 use super::EntityPropertyTuple;
1157
1158 let query: EntityPropertyTuple<Person, _> =
1159 EntityPropertyTuple::new((Age(42), RiskCategory::High));
1160
1161 let inner = query.inner();
1163 assert_eq!(inner.0, Age(42));
1164 assert_eq!(inner.1, RiskCategory::High);
1165
1166 let (age, risk) = query.into_inner();
1168 assert_eq!(age, Age(42));
1169 assert_eq!(risk, RiskCategory::High);
1170 }
1171
1172 #[test]
1173 fn all_macro_no_properties() {
1174 use crate::with;
1175
1176 let mut context = Context::new();
1177 let _ = context
1178 .add_entity(with!(Person, Age(42), RiskCategory::High))
1179 .unwrap();
1180 let _ = context
1181 .add_entity(with!(Person, Age(30), RiskCategory::Low))
1182 .unwrap();
1183
1184 let query = with!(Person);
1186 assert_eq!(context.query_entity_count(query), 2);
1187 }
1188
1189 #[test]
1190 fn all_macro_single_property() {
1191 use crate::with;
1192
1193 let mut context = Context::new();
1194 let _ = context
1195 .add_entity(with!(Person, Age(42), RiskCategory::High))
1196 .unwrap();
1197 let _ = context
1198 .add_entity(with!(Person, Age(42), RiskCategory::Low))
1199 .unwrap();
1200 let _ = context
1201 .add_entity(with!(Person, Age(30), RiskCategory::High))
1202 .unwrap();
1203
1204 let query = with!(Person, Age(42));
1206 assert_eq!(context.query_entity_count(query), 2);
1207 }
1208
1209 #[test]
1210 fn all_macro_multiple_properties() {
1211 use crate::with;
1212
1213 let mut context = Context::new();
1214 let p1 = context
1215 .add_entity(with!(Person, Age(42), RiskCategory::High))
1216 .unwrap();
1217 let _ = context
1218 .add_entity(with!(Person, Age(42), RiskCategory::Low))
1219 .unwrap();
1220 let _ = context
1221 .add_entity(with!(Person, Age(30), RiskCategory::High))
1222 .unwrap();
1223
1224 let query = with!(Person, Age(42), RiskCategory::High);
1226 assert_eq!(context.query_entity_count(query), 1);
1227
1228 context.with_query_results(query, &mut |people| {
1229 assert!(people.contains(p1));
1230 });
1231 }
1232
1233 #[test]
1234 fn all_macro_with_trailing_comma() {
1235 use crate::with;
1236
1237 let mut context = Context::new();
1238 let _ = context
1239 .add_entity(with!(Person, Age(42), RiskCategory::High))
1240 .unwrap();
1241
1242 let query = with!(Person, Age(42));
1244 assert_eq!(context.query_entity_count(query), 1);
1245
1246 let query = with!(Person, Age(42), RiskCategory::High);
1247 assert_eq!(context.query_entity_count(query), 1);
1248 }
1249
1250 #[test]
1251 fn entity_property_tuple_as_property_list() {
1252 use super::EntityPropertyTuple;
1253 use crate::entity::property_list::PropertyList;
1254
1255 assert!(EntityPropertyTuple::<Person, (Age,)>::validate().is_ok());
1257 assert!(EntityPropertyTuple::<Person, (Age, RiskCategory)>::validate().is_ok());
1258
1259 assert!(EntityPropertyTuple::<Person, (Age,)>::contains_properties(
1261 &[Age::type_id()]
1262 ));
1263 assert!(
1264 EntityPropertyTuple::<Person, (Age, RiskCategory)>::contains_properties(&[
1265 Age::type_id()
1266 ])
1267 );
1268 assert!(
1269 EntityPropertyTuple::<Person, (Age, RiskCategory)>::contains_properties(&[
1270 Age::type_id(),
1271 RiskCategory::type_id()
1272 ])
1273 );
1274 }
1275
1276 #[test]
1277 fn all_macro_as_property_list_for_add_entity() {
1278 use crate::with;
1279
1280 let mut context = Context::new();
1281
1282 let props = with!(Person, Age(42), RiskCategory::High);
1284 let person = context.add_entity(props).unwrap();
1285
1286 assert_eq!(context.get_property::<Person, Age>(person), Age(42));
1288 assert_eq!(
1289 context.get_property::<Person, RiskCategory>(person),
1290 RiskCategory::High
1291 );
1292 }
1293}