1mod query_impls;
2
3use std::any::TypeId;
4use std::marker::PhantomData;
5use std::sync::{Mutex, OnceLock};
6
7use crate::entity::entity_set::{EntitySet, EntitySetIterator};
8use crate::entity::multi_property::type_ids_to_multi_property_index;
9use crate::entity::property_list::PropertyList;
10use crate::entity::property_store::PropertyStore;
11use crate::entity::{Entity, HashValueType};
12use crate::hashing::HashMap;
13use crate::prelude::EntityId;
14use crate::{Context, IxaError};
15
16pub struct EntityPropertyTuple<E: Entity, T> {
34 inner: T,
35 _marker: PhantomData<E>,
36}
37
38impl<E: Entity, T: Copy> Copy for EntityPropertyTuple<E, T> {}
40
41impl<E: Entity, T: Clone> Clone for EntityPropertyTuple<E, T> {
42 fn clone(&self) -> Self {
43 Self {
44 inner: self.inner.clone(),
45 _marker: PhantomData,
46 }
47 }
48}
49
50impl<E: Entity, T: std::fmt::Debug> std::fmt::Debug for EntityPropertyTuple<E, T> {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 f.debug_struct("EntityPropertyTuple")
53 .field("inner", &self.inner)
54 .finish()
55 }
56}
57
58impl<E: Entity, T> EntityPropertyTuple<E, T> {
59 pub fn new(inner: T) -> Self {
61 Self {
62 inner,
63 _marker: PhantomData,
64 }
65 }
66
67 pub fn inner(&self) -> &T {
69 &self.inner
70 }
71
72 pub fn into_inner(self) -> T {
74 self.inner
75 }
76}
77
78impl<E: Entity, T: Query<E>> Query<E> for EntityPropertyTuple<E, T> {
79 fn get_query(&self) -> Vec<(usize, HashValueType)> {
80 self.inner.get_query()
81 }
82
83 fn get_type_ids(&self) -> Vec<TypeId> {
84 self.inner.get_type_ids()
85 }
86
87 fn multi_property_id(&self) -> Option<usize> {
88 self.inner.multi_property_id()
89 }
90
91 fn multi_property_value_hash(&self) -> HashValueType {
92 self.inner.multi_property_value_hash()
93 }
94
95 fn new_query_result<'c>(&self, context: &'c Context) -> EntitySet<'c, E> {
96 self.inner.new_query_result(context)
97 }
98
99 fn match_entity(&self, entity_id: EntityId<E>, context: &Context) -> bool {
100 self.inner.match_entity(entity_id, context)
101 }
102
103 fn filter_entities(&self, entities: &mut Vec<EntityId<E>>, context: &Context) {
104 self.inner.filter_entities(entities, context)
105 }
106}
107
108impl<E: Entity, T: PropertyList<E>> PropertyList<E> for EntityPropertyTuple<E, T> {
109 fn validate() -> Result<(), IxaError> {
110 T::validate()
111 }
112
113 fn contains_properties(property_type_ids: &[TypeId]) -> bool {
114 T::contains_properties(property_type_ids)
115 }
116
117 fn set_values_for_new_entity(
118 &self,
119 entity_id: EntityId<E>,
120 property_store: &mut PropertyStore<E>,
121 ) {
122 let tuple = *self;
123 tuple
124 .into_inner()
125 .set_values_for_new_entity(entity_id, property_store)
126 }
127
128 fn get_values_for_entity(context: &Context, entity_id: EntityId<E>) -> Self {
129 EntityPropertyTuple::new(T::get_values_for_entity(context, entity_id))
130 }
131}
132
133pub trait Query<E: Entity>: Copy + 'static {
140 fn get_query(&self) -> Vec<(usize, HashValueType)>;
143
144 fn get_type_ids(&self) -> Vec<TypeId>;
146
147 fn multi_property_id(&self) -> Option<usize> {
149 static REGISTRY: OnceLock<Mutex<HashMap<TypeId, &'static Option<usize>>>> = OnceLock::new();
152
153 let map = REGISTRY.get_or_init(|| Mutex::new(HashMap::default()));
154 let mut map = map.lock().unwrap();
155 let type_id = TypeId::of::<Self>();
156 let entry = *map.entry(type_id).or_insert_with(|| {
157 let mut types = self.get_type_ids();
158 types.sort_unstable();
159 Box::leak(Box::new(type_ids_to_multi_property_index(types.as_slice())))
160 });
161
162 *entry
163 }
164
165 fn multi_property_value_hash(&self) -> HashValueType;
168
169 fn new_query_result<'c>(&self, context: &'c Context) -> EntitySet<'c, E>;
171
172 fn new_query_result_iterator<'c>(&self, context: &'c Context) -> EntitySetIterator<'c, E> {
174 self.new_query_result(context).into_iter()
175 }
176
177 fn match_entity(&self, entity_id: EntityId<E>, context: &Context) -> bool;
179
180 fn filter_entities(&self, entities: &mut Vec<EntityId<E>>, context: &Context);
182}
183
184#[cfg(test)]
185mod tests {
186
187 use crate::prelude::*;
188 use crate::{
189 define_derived_property, define_entity, define_multi_property, define_property, Context,
190 };
191
192 define_entity!(Person);
193
194 define_property!(struct Age(u8), Person, default_const = Age(0));
195 define_property!(struct County(u32), Person, default_const = County(0));
196 define_property!(struct Height(u32), Person, default_const = Height(0));
197 define_property!(
198 enum RiskCategory {
199 High,
200 Low,
201 },
202 Person
203 );
204
205 define_multi_property!((Age, County), Person);
206
207 #[test]
208 fn with_query_results() {
209 let mut context = Context::new();
210 let _ = context.add_entity((RiskCategory::High,)).unwrap();
211
212 context.with_query_results((RiskCategory::High,), &mut |people| {
213 assert_eq!(people.into_iter().count(), 1);
214 });
215 }
216
217 #[test]
218 fn with_query_results_empty() {
219 let context = Context::new();
220
221 context.with_query_results((RiskCategory::High,), &mut |people| {
222 assert_eq!(people.into_iter().count(), 0);
223 });
224 }
225
226 #[test]
227 fn query_entity_count() {
228 let mut context = Context::new();
229 let _ = context.add_entity((RiskCategory::High,)).unwrap();
230
231 assert_eq!(context.query_entity_count((RiskCategory::High,)), 1);
232 }
233
234 #[test]
235 fn query_entity_count_empty() {
236 let context = Context::new();
237
238 assert_eq!(context.query_entity_count((RiskCategory::High,)), 0);
239 }
240
241 #[test]
242 fn with_query_results_macro_index_first() {
243 let mut context = Context::new();
244 let _ = context.add_entity((RiskCategory::High,)).unwrap();
245 context.index_property::<_, RiskCategory>();
246 assert!(context.is_property_indexed::<Person, RiskCategory>());
247
248 context.with_query_results((RiskCategory::High,), &mut |people| {
249 assert_eq!(people.into_iter().count(), 1);
250 });
251 }
252
253 #[test]
254 fn with_query_results_macro_index_second() {
255 let mut context = Context::new();
256 let _ = context.add_entity((RiskCategory::High,));
257
258 context.with_query_results((RiskCategory::High,), &mut |people| {
259 assert_eq!(people.into_iter().count(), 1);
260 });
261 assert!(!context.is_property_indexed::<Person, RiskCategory>());
262
263 context.index_property::<Person, RiskCategory>();
264 assert!(context.is_property_indexed::<Person, RiskCategory>());
265
266 context.with_query_results((RiskCategory::High,), &mut |people| {
267 assert_eq!(people.into_iter().count(), 1);
268 });
269 }
270
271 #[test]
272 fn with_query_results_macro_change() {
273 let mut context = Context::new();
274 let person1 = context.add_entity((RiskCategory::High,)).unwrap();
275
276 context.with_query_results((RiskCategory::High,), &mut |people| {
277 assert_eq!(people.into_iter().count(), 1);
278 });
279
280 context.with_query_results((RiskCategory::Low,), &mut |people| {
281 assert_eq!(people.into_iter().count(), 0);
282 });
283
284 context.set_property(person1, RiskCategory::Low);
285 context.with_query_results((RiskCategory::High,), &mut |people| {
286 assert_eq!(people.into_iter().count(), 0);
287 });
288
289 context.with_query_results((RiskCategory::Low,), &mut |people| {
290 assert_eq!(people.into_iter().count(), 1);
291 });
292 }
293
294 #[test]
295 fn with_query_results_index_after_add() {
296 let mut context = Context::new();
297 let _ = context.add_entity((RiskCategory::High,)).unwrap();
298 context.index_property::<Person, RiskCategory>();
299 assert!(context.is_property_indexed::<Person, RiskCategory>());
300 context.with_query_results((RiskCategory::High,), &mut |people| {
301 assert_eq!(people.into_iter().count(), 1);
302 });
303 }
304
305 #[test]
306 fn with_query_results_add_after_index() {
307 let mut context = Context::new();
308 let _ = context.add_entity((RiskCategory::High,)).unwrap();
309 context.index_property::<Person, RiskCategory>();
310 assert!(context.is_property_indexed::<Person, RiskCategory>());
311 context.with_query_results((RiskCategory::High,), &mut |people| {
312 assert_eq!(people.into_iter().count(), 1);
313 });
314
315 let _ = context.add_entity((RiskCategory::High,)).unwrap();
316 context.with_query_results((RiskCategory::High,), &mut |people| {
317 assert_eq!(people.into_iter().count(), 2);
318 });
319 }
320
321 #[test]
322 fn with_query_results_cast_value() {
323 let mut context = Context::new();
324 let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap();
325
326 context.with_query_results((Age(42),), &mut |people| {
327 assert_eq!(people.into_iter().count(), 1);
328 });
329 }
330
331 #[test]
332 fn with_query_results_intersection() {
333 let mut context = Context::new();
334 let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap();
335 let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap();
336 let _ = context.add_entity((Age(40), RiskCategory::Low)).unwrap();
337
338 context.with_query_results((Age(42), RiskCategory::High), &mut |people| {
339 assert_eq!(people.into_iter().count(), 1);
340 });
341 }
342
343 #[test]
344 fn with_query_results_intersection_non_macro() {
345 let mut context = Context::new();
346 let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap();
347 let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap();
348 let _ = context.add_entity((Age(40), RiskCategory::Low)).unwrap();
349
350 context.with_query_results((Age(42), RiskCategory::High), &mut |people| {
351 assert_eq!(people.into_iter().count(), 1);
352 });
353 }
354
355 #[test]
356 fn with_query_results_intersection_one_indexed() {
357 let mut context = Context::new();
358 let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap();
359 let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap();
360 let _ = context.add_entity((Age(40), RiskCategory::Low)).unwrap();
361
362 context.index_property::<Person, Age>();
363 context.with_query_results((Age(42), RiskCategory::High), &mut |people| {
364 assert_eq!(people.into_iter().count(), 1);
365 });
366 }
367
368 #[test]
369 fn query_derived_prop() {
370 let mut context = Context::new();
371 define_derived_property!(struct Senior(bool), Person, [Age], |age| Senior(age.0 >= 65));
372
373 let person = context.add_entity((Age(64), RiskCategory::High)).unwrap();
374 context.add_entity((Age(88), RiskCategory::High)).unwrap();
375
376 let mut not_seniors = Vec::new();
377 context.with_query_results((Senior(false),), &mut |people| {
378 not_seniors = people.to_owned_vec();
379 });
380 let mut seniors = Vec::new();
381 context.with_query_results((Senior(true),), &mut |people| {
382 seniors = people.to_owned_vec();
383 });
384 assert_eq!(seniors.len(), 1, "One senior");
385 assert_eq!(not_seniors.len(), 1, "One non-senior");
386
387 context.set_property(person, Age(65));
388
389 context.with_query_results((Senior(false),), &mut |people| {
390 not_seniors = people.to_owned_vec()
391 });
392 context.with_query_results((Senior(true),), &mut |people| {
393 seniors = people.to_owned_vec()
394 });
395
396 assert_eq!(seniors.len(), 2, "Two seniors");
397 assert_eq!(not_seniors.len(), 0, "No non-seniors");
398 }
399
400 #[test]
401 fn query_derived_prop_with_index() {
402 let mut context = Context::new();
403 define_derived_property!(struct Senior(bool), Person, [Age], |age| Senior(age.0 >= 65));
404
405 context.index_property::<Person, Senior>();
406 let person = context.add_entity((Age(64), RiskCategory::Low)).unwrap();
407 let _ = context.add_entity((Age(88), RiskCategory::Low));
408
409 let mut not_seniors = Vec::new();
410 context.with_query_results((Senior(false),), &mut |people| {
411 not_seniors = people.to_owned_vec()
412 });
413 let mut seniors = Vec::new();
414 context.with_query_results((Senior(true),), &mut |people| {
415 seniors = people.to_owned_vec()
416 });
417 assert_eq!(seniors.len(), 1, "One senior");
418 assert_eq!(not_seniors.len(), 1, "One non-senior");
419
420 context.set_property(person, Age(65));
421
422 context.with_query_results((Senior(false),), &mut |people| {
423 not_seniors = people.to_owned_vec()
424 });
425 context.with_query_results((Senior(true),), &mut |people| {
426 seniors = people.to_owned_vec()
427 });
428
429 assert_eq!(seniors.len(), 2, "Two seniors");
430 assert_eq!(not_seniors.len(), 0, "No non-seniors");
431 }
432
433 define_multi_property!((Age, County, Height), Person);
435 define_multi_property!((County, Height), Person);
436
437 #[test]
438 fn query_derived_prop_with_optimized_index() {
439 let mut context = Context::new();
440 define_derived_property!(
442 struct Ach(u8, u32, u32),
443 Person,
444 [Age, County, Height],
445 [],
446 |age, county, height| Ach(age.0, county.0, height.0)
447 );
448
449 let _ = context.add_entity((Age(64), County(2), Height(120), RiskCategory::Low));
451 let _ = context.add_entity((Age(88), County(2), Height(130), RiskCategory::Low));
452 let p2 = context
453 .add_entity((Age(8), County(1), Height(140), RiskCategory::Low))
454 .unwrap();
455 let p3 = context
456 .add_entity((Age(28), County(1), Height(140), RiskCategory::Low))
457 .unwrap();
458 let p4 = context
459 .add_entity((Age(28), County(2), Height(160), RiskCategory::Low))
460 .unwrap();
461 let p5 = context
462 .add_entity((Age(28), County(2), Height(160), RiskCategory::Low))
463 .unwrap();
464
465 context.with_query_results((Ach(28, 2, 160),), &mut |people| {
467 assert!(people.contains(p4));
468 assert!(people.contains(p5));
469 assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
470 });
471
472 context.with_query_results((Age(28), County(2), Height(160)), &mut |people| {
474 assert!(people.contains(p4));
475 assert!(people.contains(p5));
476 assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
477 });
478
479 context.with_query_results((County(2), Height(160), Age(28)), &mut |people| {
481 assert!(people.contains(p4));
482 assert!(people.contains(p5));
483 assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
484 });
485
486 context.with_query_results((Height(160), County(2), Age(28)), &mut |people| {
488 assert!(people.contains(p4));
489 assert!(people.contains(p5));
490 assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
491 });
492
493 context.with_query_results((Height(140), County(1), Age(28)), &mut |people| {
495 assert!(people.contains(p3));
496 assert_eq!(people.into_iter().count(), 1, "Should have 1 matches");
497 });
498
499 context.set_property(p2, Age(28));
500 context.with_query_results((Height(140), County(1), Age(28)), &mut |people| {
502 assert!(people.contains(p2));
503 assert!(people.contains(p3));
504 assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
505 });
506
507 context.with_query_results((Height(140), County(1)), &mut |people| {
508 assert!(people.contains(p2));
509 assert!(people.contains(p3));
510 assert_eq!(people.into_iter().count(), 2, "Should have 2 matches");
511 });
512 }
513
514 #[test]
515 fn test_match_entity() {
516 let mut context = Context::new();
517 let person = context
518 .add_entity((Age(28), County(2), Height(160), RiskCategory::Low))
519 .unwrap();
520 assert!(context.match_entity(person, (Age(28), County(2), Height(160))));
521 assert!(!context.match_entity(person, (Age(13), County(2), Height(160))));
522 assert!(!context.match_entity(person, (Age(28), County(33), Height(160))));
523 assert!(!context.match_entity(person, (Age(28), County(2), Height(9))));
524 }
525
526 #[test]
527 fn filter_entities_for_unindexed_query() {
528 let mut context = Context::new();
529 let mut people = Vec::new();
530
531 for idx in 0..10 {
532 let person = context
533 .add_entity((Age(28), County(idx % 2), Height(160), RiskCategory::Low))
534 .unwrap();
535 people.push(person);
536 }
537
538 context.filter_entities(
539 &mut people,
540 (Age(28), County(0), Height(160), RiskCategory::Low),
541 );
542
543 let expected = (0..5)
544 .map(|idx| PersonId::new(idx * 2))
545 .collect::<Vec<PersonId>>();
546 assert_eq!(people, expected);
547 }
548
549 #[test]
550 fn filter_entities_for_indexed_query() {
551 let mut context = Context::new();
552 let mut people = Vec::new();
553
554 context.index_property::<Person, (Age, County)>();
555
556 for idx in 0..10 {
557 let person = context
558 .add_entity((Age(28), County(idx % 2), Height(160), RiskCategory::Low))
559 .unwrap();
560 people.push(person);
561 }
562
563 context.filter_entities(&mut people, (County(0), Age(28)));
564
565 let expected = (0..5)
566 .map(|idx| PersonId::new(idx * 2))
567 .collect::<Vec<PersonId>>();
568 assert_eq!(people, expected);
569 }
570
571 #[test]
572 fn entity_property_tuple_basic() {
573 use super::EntityPropertyTuple;
574
575 let mut context = Context::new();
576 let p1 = context.add_entity((Age(42), RiskCategory::High)).unwrap();
577 let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap();
578 let _ = context.add_entity((Age(30), RiskCategory::High)).unwrap();
579
580 let query: EntityPropertyTuple<Person, _> =
582 EntityPropertyTuple::new((Age(42), RiskCategory::High));
583
584 context.with_query_results(query, &mut |people| {
585 assert!(people.contains(p1));
586 assert_eq!(people.into_iter().count(), 1);
587 });
588
589 assert!(context.match_entity(p1, query));
591
592 assert_eq!(context.query_entity_count(query), 1);
594 }
595
596 #[test]
597 fn entity_property_tuple_empty_query() {
598 use super::EntityPropertyTuple;
599
600 let mut context = Context::new();
601 let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap();
602 let _ = context.add_entity((Age(30), RiskCategory::Low)).unwrap();
603
604 let query: EntityPropertyTuple<Person, _> = EntityPropertyTuple::new(());
606
607 assert_eq!(context.query_entity_count(query), 2);
608 }
609
610 #[test]
611 fn entity_property_tuple_singleton() {
612 use super::EntityPropertyTuple;
613
614 let mut context = Context::new();
615 let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap();
616 let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap();
617 let _ = context.add_entity((Age(30), RiskCategory::High)).unwrap();
618
619 let query: EntityPropertyTuple<Person, _> = EntityPropertyTuple::new((Age(42),));
621
622 assert_eq!(context.query_entity_count(query), 2);
623 }
624
625 #[test]
626 fn entity_property_tuple_inner_access() {
627 use super::EntityPropertyTuple;
628
629 let query: EntityPropertyTuple<Person, _> =
630 EntityPropertyTuple::new((Age(42), RiskCategory::High));
631
632 let inner = query.inner();
634 assert_eq!(inner.0, Age(42));
635 assert_eq!(inner.1, RiskCategory::High);
636
637 let (age, risk) = query.into_inner();
639 assert_eq!(age, Age(42));
640 assert_eq!(risk, RiskCategory::High);
641 }
642
643 #[test]
644 fn all_macro_no_properties() {
645 use crate::with;
646
647 let mut context = Context::new();
648 let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap();
649 let _ = context.add_entity((Age(30), RiskCategory::Low)).unwrap();
650
651 let query = with!(Person);
653 assert_eq!(context.query_entity_count(query), 2);
654 }
655
656 #[test]
657 fn all_macro_single_property() {
658 use crate::with;
659
660 let mut context = Context::new();
661 let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap();
662 let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap();
663 let _ = context.add_entity((Age(30), RiskCategory::High)).unwrap();
664
665 let query = with!(Person, Age(42));
667 assert_eq!(context.query_entity_count(query), 2);
668 }
669
670 #[test]
671 fn all_macro_multiple_properties() {
672 use crate::with;
673
674 let mut context = Context::new();
675 let p1 = context.add_entity((Age(42), RiskCategory::High)).unwrap();
676 let _ = context.add_entity((Age(42), RiskCategory::Low)).unwrap();
677 let _ = context.add_entity((Age(30), RiskCategory::High)).unwrap();
678
679 let query = with!(Person, Age(42), RiskCategory::High);
681 assert_eq!(context.query_entity_count(query), 1);
682
683 context.with_query_results(query, &mut |people| {
684 assert!(people.contains(p1));
685 });
686 }
687
688 #[test]
689 fn all_macro_with_trailing_comma() {
690 use crate::with;
691
692 let mut context = Context::new();
693 let _ = context.add_entity((Age(42), RiskCategory::High)).unwrap();
694
695 let query = with!(Person, Age(42));
697 assert_eq!(context.query_entity_count(query), 1);
698
699 let query = with!(Person, Age(42), RiskCategory::High);
700 assert_eq!(context.query_entity_count(query), 1);
701 }
702
703 #[test]
704 fn entity_property_tuple_as_property_list() {
705 use super::EntityPropertyTuple;
706 use crate::entity::property_list::PropertyList;
707
708 assert!(EntityPropertyTuple::<Person, (Age,)>::validate().is_ok());
710 assert!(EntityPropertyTuple::<Person, (Age, RiskCategory)>::validate().is_ok());
711
712 assert!(EntityPropertyTuple::<Person, (Age,)>::contains_properties(
714 &[Age::type_id()]
715 ));
716 assert!(
717 EntityPropertyTuple::<Person, (Age, RiskCategory)>::contains_properties(&[
718 Age::type_id()
719 ])
720 );
721 assert!(
722 EntityPropertyTuple::<Person, (Age, RiskCategory)>::contains_properties(&[
723 Age::type_id(),
724 RiskCategory::type_id()
725 ])
726 );
727 }
728
729 #[test]
730 fn all_macro_as_property_list_for_add_entity() {
731 use crate::with;
732
733 let mut context = Context::new();
734
735 let props = with!(Person, Age(42), RiskCategory::High);
737 let person = context.add_entity(props).unwrap();
738
739 assert_eq!(context.get_property::<Person, Age>(person), Age(42));
741 assert_eq!(
742 context.get_property::<Person, RiskCategory>(person),
743 RiskCategory::High
744 );
745 }
746}