ixa/entity/property_list.rs
1/*!
2
3This module supports two user-facing patterns:
4
51. initializing a new entity with [`ContextEntitiesExt::add_entity`], and
62. specifying strata for value-change counting APIs such as
7 [`ContextEntitiesExt::track_periodic_value_change_counts`].
8
9For `add_entity`, pass either:
10
11- the entity type directly, such as `Person`, to use default property values, or
12- [`with!`](crate::with) to provide one or more initial property values, such as
13 `with!(Person, Age(25), InfectionStatus::Infected)`.
14
15For value-change counting APIs, use tuple types in the generic parameter list, such as
16`(InfectionStatus,)` or `(AgeGroup, InfectionStatus)`.
17
18In both cases, all properties must belong to the same entity, and property values must be distinct.
19
20*/
21
22use std::any::TypeId;
23
24use seq_macro::seq;
25
26use super::entity::{Entity, EntityId};
27use super::property::Property;
28use super::property_store::PropertyStore;
29use crate::entity::ContextEntitiesExt;
30use crate::{Context, IxaError};
31
32pub trait PropertyList<E: Entity>: Copy + 'static {
33 /// Validates that the properties are distinct. If not, returns an error describing the problematic properties.
34 fn validate() -> Result<(), IxaError>;
35
36 /// Checks that this property list includes all properties in the given list.
37 fn contains_properties(property_type_ids: &[TypeId]) -> bool;
38
39 /// Checks that this property list contains all required properties of the entity.
40 fn contains_required_properties() -> bool {
41 Self::contains_properties(E::required_property_ids())
42 }
43
44 /// Assigns the given entity the property values in `self` in the `property_store`.
45 /// This method does NOT emit property change events, as it is called upon entity creation.
46 fn set_values_for_new_entity(
47 &self,
48 entity_id: EntityId<E>,
49 property_store: &mut PropertyStore<E>,
50 );
51
52 /// Gets the tuple of property values for the given entity.
53 fn get_values_for_entity(context: &Context, entity_id: EntityId<E>) -> Self;
54}
55
56/// Values accepted by [`ContextEntitiesExt::add_entity`].
57pub trait PropertyInitializationList<E: Entity>: PropertyList<E> {}
58
59// The empty tuple is an empty `PropertyList<E>` for every `E: Entity`.
60impl<E: Entity> PropertyList<E> for () {
61 fn validate() -> Result<(), IxaError> {
62 Ok(())
63 }
64 fn contains_properties(property_type_ids: &[TypeId]) -> bool {
65 property_type_ids.is_empty()
66 }
67 fn set_values_for_new_entity(
68 &self,
69 _entity_id: EntityId<E>,
70 _property_store: &mut PropertyStore<E>,
71 ) {
72 // No values to assign.
73 }
74
75 fn get_values_for_entity(_context: &Context, _entity_id: EntityId<E>) -> Self {}
76}
77
78// An Entity ZST itself is an empty `PropertyList` for that entity.
79// This allows `context.add_entity(Person)` instead of `context.add_entity(())`.
80impl<E: Entity + Copy> PropertyList<E> for E {
81 fn validate() -> Result<(), IxaError> {
82 Ok(())
83 }
84 fn contains_properties(property_type_ids: &[TypeId]) -> bool {
85 property_type_ids.is_empty()
86 }
87 fn set_values_for_new_entity(
88 &self,
89 _entity_id: EntityId<E>,
90 _property_store: &mut PropertyStore<E>,
91 ) {
92 // No values to assign.
93 }
94
95 fn get_values_for_entity(_context: &Context, _entity_id: EntityId<E>) -> E {
96 E::default()
97 }
98}
99
100impl<E: Entity + Copy> PropertyInitializationList<E> for E {}
101
102// ToDo(RobertJacobsonCDC): The following is a fundamental limitation in Rust. If downstream code *can* implement a
103// trait impl that will cause conflicting implementations with some blanket impl, it disallows it, regardless of
104// whether the conflict actually exists.
105// A single `Property` is a `PropertyList` of length 1
106// impl<E: Entity, P: Property<E>> PropertyList<E> for P {
107// fn validate() -> Result<(), String> {
108// Ok(())
109// }
110// fn contains_properties(property_type_ids: &[TypeId]) -> bool {
111// property_type_ids.len() == 0
112// || property_type_ids.len() == 1 && property_type_ids[0] == P::type_id()
113// }
114// fn set_values_for_new_entity(&self, entity_id: EntityId<E>, property_store: &mut PropertyStore<E>) {
115// let property_value_store = property_store.get_mut::<P>();
116// property_value_store.set(entity_id, *self);
117// }
118// }
119
120// A single `Property` tuple is a `PropertyList` of length 1. This supports internal tuple
121// machinery, but naked tuples are not accepted directly by `add_entity`.
122impl<E: Entity, P: Property<E>> PropertyList<E> for (P,) {
123 fn validate() -> Result<(), IxaError> {
124 Ok(())
125 }
126 fn contains_properties(property_type_ids: &[TypeId]) -> bool {
127 property_type_ids.is_empty()
128 || property_type_ids.len() == 1 && property_type_ids[0] == P::type_id()
129 }
130 fn set_values_for_new_entity(
131 &self,
132 entity_id: EntityId<E>,
133 property_store: &mut PropertyStore<E>,
134 ) {
135 let property_value_store = property_store.get_mut::<P>();
136 property_value_store.set(entity_id, self.0);
137 }
138
139 fn get_values_for_entity(context: &Context, entity_id: EntityId<E>) -> Self {
140 (context.get_property::<E, P>(entity_id),)
141 }
142}
143
144// Used only within this module.
145macro_rules! impl_property_list {
146 ($ct:literal) => {
147 seq!(N in 0..$ct {
148 impl<E: Entity, #( P~N: Property<E>,)*> PropertyList<E> for (#(P~N, )*){
149 fn validate() -> Result<(), IxaError> {
150 // For `Property` distinctness check
151 let property_type_ids: [TypeId; $ct] = [#(<P~N as $crate::entity::property::Property<E>>::type_id(),)*];
152
153 for i in 0..$ct - 1 {
154 for j in (i + 1)..$ct {
155 if property_type_ids[i] == property_type_ids[j] {
156 return Err(IxaError::DuplicatePropertyInPropertyList {
157 first_index: i,
158 second_index: j,
159 });
160 }
161 }
162 }
163
164 Ok(())
165 }
166
167 fn contains_properties(property_type_ids: &[TypeId]) -> bool {
168 let self_property_type_ids: [TypeId; $ct] = [#(<P~N as $crate::entity::property::Property<E>>::type_id(),)*];
169
170 property_type_ids.len() <= $ct && property_type_ids.iter().all(|id| self_property_type_ids.contains(id))
171 }
172
173 fn set_values_for_new_entity(&self, entity_id: EntityId<E>, property_store: &mut PropertyStore<E>){
174 #({
175 let property_value_store = property_store.get_mut::<P~N>();
176 property_value_store.set(entity_id, self.N);
177 })*
178 }
179
180 fn get_values_for_entity(context: &Context, entity_id: EntityId<E>) -> Self {
181 (#(context.get_property::<E, P~N>(entity_id), )*)
182 }
183 }
184 });
185 };
186}
187
188// Generate impls for tuple lengths 2 through 20. These tuple impls remain available for internal
189// initialization/query machinery and for type-level strata lists, but not as direct `add_entity`
190// inputs.
191seq!(Z in 2..=20 {
192 impl_property_list!(Z);
193});