ixa/macros/
with.rs

1/// Creates an entity-scoped bundle of property values for queries and entity initialization.
2///
3/// # Examples
4///
5/// ```ignore
6/// // Add an entity with selected property values
7/// let person = context.add_entity(with!(Person, Age(12), RiskCategory::High))?;
8///
9/// // Query the whole population by passing the entity type directly
10/// let count = context.query_entity_count(Person);
11///
12/// // An inline query matching one property
13/// let person = context.sample_entity(MyRng, with!(Person, Age(12)))?;
14///
15/// // A query matching multiple properties
16/// let query = with!(Person, Age(12), RiskCategory::High);
17/// let count = context.count_entities(query);
18/// ```
19#[macro_export]
20macro_rules! with {
21    // No properties - generates empty tuple query
22    ($entity:ty) => {
23        $crate::EntityPropertyTuple::<$entity, _>::new(())
24    };
25    // One or more properties
26    ($entity:ty, $($prop:expr),+ $(,)?) => {
27        $crate::EntityPropertyTuple::<$entity, _>::new(($($prop,)+))
28    };
29}
30
31#[cfg(test)]
32mod tests {
33    use crate::context::Context;
34    use crate::entity::ContextEntitiesExt;
35    use crate::random::ContextRandomExt;
36    use crate::{define_entity, define_property, define_rng, impl_property};
37
38    define_entity!(TestPerson);
39    define_property!(struct Age(u8), TestPerson, default_const = Age(0));
40    define_property!(
41        enum Risk {
42            High,
43            Low,
44        },
45        TestPerson
46    );
47    define_rng!(AllMacroTestRng);
48
49    #[test]
50    fn all_macro_with_add_entity() {
51        let mut context = Context::new();
52
53        // Use with! macro to add an entity
54        let person = context
55            .add_entity(with!(TestPerson, Age(42), Risk::High))
56            .unwrap();
57
58        // Verify properties were set correctly
59        assert_eq!(context.get_property::<TestPerson, Age>(person), Age(42));
60        assert_eq!(context.get_property::<TestPerson, Risk>(person), Risk::High);
61    }
62
63    #[test]
64    fn all_macro_with_sample_entity() {
65        let mut context = Context::new();
66        context.init_random(42);
67
68        // Add some entities
69        let p1 = context
70            .add_entity(with!(TestPerson, Age(30), Risk::High))
71            .unwrap();
72        let _ = context
73            .add_entity(with!(TestPerson, Age(30), Risk::Low))
74            .unwrap();
75        let _ = context
76            .add_entity(with!(TestPerson, Age(25), Risk::High))
77            .unwrap();
78
79        // Sample from entities matching the query
80        let sampled =
81            context.sample_entity(AllMacroTestRng, with!(TestPerson, Age(30), Risk::High));
82        assert_eq!(sampled, Some(p1));
83    }
84
85    #[test]
86    fn all_macro_with_sample_entity_no_match() {
87        let mut context = Context::new();
88        context.init_random(42);
89
90        // Add some entities that don't match the query
91        let _ = context
92            .add_entity(with!(TestPerson, Age(30), Risk::Low))
93            .unwrap();
94
95        // Sample should return None when no entities match
96        let sampled =
97            context.sample_entity(AllMacroTestRng, with!(TestPerson, Age(30), Risk::High));
98        assert_eq!(sampled, None);
99    }
100
101    // Demonstrate that `with!` can disambiguate entities in otherwise ambiguous cases.
102    use crate::entity::EntityId;
103    define_entity!(TestMammal);
104    define_entity!(TestAvian);
105    define_property!(struct IsBipedal(bool), TestMammal, default_const = IsBipedal(false));
106    impl_property!(IsBipedal, TestAvian, default_const = IsBipedal(true));
107
108    #[test]
109    fn all_macro_disambiguates_entities() {
110        let mut context = Context::new();
111
112        context
113            .add_entity(with!(TestAvian, IsBipedal(true)))
114            .unwrap();
115        context
116            .add_entity(with!(TestMammal, IsBipedal(true)))
117            .unwrap();
118
119        let result = context.query_result_iterator(with!(TestMammal, IsBipedal(true)));
120        assert_eq!(result.count(), 1);
121
122        let sampled_id = context
123            .sample_entity(AllMacroTestRng, with!(TestAvian, IsBipedal(true)))
124            .unwrap();
125        let expected_id: EntityId<TestAvian> = EntityId::new(0);
126        assert_eq!(sampled_id, expected_id);
127    }
128}