Skip to main content

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