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::events::PropertyInitializedEvent;
28use super::property::{Property, PropertyInitializationKind};
29use super::property_store::PropertyStore;
30use crate::data_structures::bit_set::BitSet;
31use crate::entity::ContextEntitiesExt;
32use crate::{Context, IxaError};
33
34pub trait PropertyList<E: Entity>: Copy + 'static {
35    /// Validates that the properties are distinct. If not, returns an error describing the problematic properties.
36    fn validate() -> Result<(), IxaError>;
37
38    /// Checks that this property list includes all properties in the given list.
39    #[must_use]
40    fn contains_properties(property_type_ids: &[TypeId]) -> bool;
41
42    /// Checks that this property list contains all required properties of the entity.
43    #[must_use]
44    fn contains_required_properties() -> bool {
45        Self::contains_properties(E::required_property_ids())
46    }
47
48    /// Assigns the given entity the property values in `self` in the `property_store`.
49    /// This method does NOT emit property change events, as it is called upon entity creation.
50    fn set_values_for_new_entity(
51        &self,
52        entity_id: EntityId<E>,
53        property_store: &mut PropertyStore<E>,
54    );
55
56    /// Emits initialization events for the explicitly supplied property values.
57    #[doc(hidden)]
58    fn emit_initialized_events(
59        &self,
60        context: &mut Context,
61        entity_id: EntityId<E>,
62        subscriptions: &BitSet,
63    );
64
65    /// Gets the tuple of property values for the given entity.
66    #[must_use]
67    fn get_values_for_entity(context: &Context, entity_id: EntityId<E>) -> Self;
68}
69
70/// Values accepted by [`ContextEntitiesExt::add_entity`].
71pub trait PropertyInitializationList<E: Entity>: PropertyList<E> {}
72
73// The empty tuple is an empty `PropertyList<E>` for every `E: Entity`.
74impl<E: Entity> PropertyList<E> for () {
75    fn validate() -> Result<(), IxaError> {
76        Ok(())
77    }
78    fn contains_properties(property_type_ids: &[TypeId]) -> bool {
79        property_type_ids.is_empty()
80    }
81    fn set_values_for_new_entity(
82        &self,
83        _entity_id: EntityId<E>,
84        _property_store: &mut PropertyStore<E>,
85    ) {
86        // No values to assign.
87    }
88
89    fn emit_initialized_events(
90        &self,
91        _context: &mut Context,
92        _entity_id: EntityId<E>,
93        _subscriptions: &BitSet,
94    ) {
95    }
96
97    fn get_values_for_entity(_context: &Context, _entity_id: EntityId<E>) -> Self {}
98}
99
100// An Entity ZST itself is an empty `PropertyList` for that entity.
101// This allows `context.add_entity(Person)` instead of `context.add_entity(())`.
102impl<E: Entity + Copy> PropertyList<E> for E {
103    fn validate() -> Result<(), IxaError> {
104        Ok(())
105    }
106    fn contains_properties(property_type_ids: &[TypeId]) -> bool {
107        property_type_ids.is_empty()
108    }
109    fn set_values_for_new_entity(
110        &self,
111        _entity_id: EntityId<E>,
112        _property_store: &mut PropertyStore<E>,
113    ) {
114        // No values to assign.
115    }
116
117    fn emit_initialized_events(
118        &self,
119        _context: &mut Context,
120        _entity_id: EntityId<E>,
121        _subscriptions: &BitSet,
122    ) {
123    }
124
125    fn get_values_for_entity(_context: &Context, _entity_id: EntityId<E>) -> E {
126        E::default()
127    }
128}
129
130impl<E: Entity + Copy> PropertyInitializationList<E> for E {}
131
132fn emit_initialized_event<E, P>(context: &mut Context, entity_id: EntityId<E>, value: P)
133where
134    E: Entity,
135    P: Property<E>,
136{
137    match P::initialization_kind() {
138        PropertyInitializationKind::Derived => {}
139        PropertyInitializationKind::Explicit => {
140            context.emit_event(PropertyInitializedEvent::new(entity_id, value));
141        }
142        PropertyInitializationKind::Constant if value != P::default_const() => {
143            context.emit_event(PropertyInitializedEvent::new(entity_id, value));
144        }
145        PropertyInitializationKind::Constant => {}
146    }
147}
148
149// ToDo(RobertJacobsonCDC): The following is a fundamental limitation in Rust. If downstream code *can* implement a
150//     trait impl that will cause conflicting implementations with some blanket impl, it disallows it, regardless of
151//     whether the conflict actually exists.
152// A single `Property` is a `PropertyList` of length 1
153// impl<E: Entity, P: Property<E>> PropertyList<E> for P {
154//     fn validate() -> Result<(), String> {
155//         Ok(())
156//     }
157//     fn contains_properties(property_type_ids: &[TypeId]) -> bool {
158//         property_type_ids.len() == 0
159//             || property_type_ids.len() == 1 && property_type_ids[0] == P::type_id()
160//     }
161//     fn set_values_for_new_entity(&self, entity_id: EntityId<E>, property_store: &mut PropertyStore<E>) {
162//         let property_value_store = property_store.get_mut::<P>();
163//         property_value_store.set(entity_id, *self);
164//     }
165// }
166
167// A single `Property` tuple is a `PropertyList` of length 1. This supports internal tuple
168// machinery, but naked tuples are not accepted directly by `add_entity`.
169impl<E: Entity, P: Property<E>> PropertyList<E> for (P,) {
170    fn validate() -> Result<(), IxaError> {
171        Ok(())
172    }
173    fn contains_properties(property_type_ids: &[TypeId]) -> bool {
174        property_type_ids.is_empty()
175            || property_type_ids.len() == 1 && property_type_ids[0] == P::type_id()
176    }
177    fn set_values_for_new_entity(
178        &self,
179        entity_id: EntityId<E>,
180        property_store: &mut PropertyStore<E>,
181    ) {
182        let property_value_store = property_store.get_mut::<P>();
183        property_value_store.set(entity_id, self.0);
184    }
185
186    fn emit_initialized_events(
187        &self,
188        context: &mut Context,
189        entity_id: EntityId<E>,
190        subscriptions: &BitSet,
191    ) {
192        if subscriptions.get(P::id()) {
193            emit_initialized_event(context, entity_id, self.0);
194        }
195    }
196
197    fn get_values_for_entity(context: &Context, entity_id: EntityId<E>) -> Self {
198        (context.get_property::<E, P>(entity_id),)
199    }
200}
201
202// Used only within this module.
203macro_rules! impl_property_list {
204    ($ct:literal) => {
205        seq!(N in 0..$ct {
206            impl<E: Entity, #( P~N: Property<E>,)*> PropertyList<E> for (#(P~N, )*){
207                fn validate() -> Result<(), IxaError> {
208                    // For `Property` distinctness check
209                    let property_type_ids: [TypeId; $ct] = [#(<P~N as $crate::entity::property::Property<E>>::type_id(),)*];
210
211                    for i in 0..$ct - 1 {
212                        for j in (i + 1)..$ct {
213                            if property_type_ids[i] == property_type_ids[j] {
214                                return Err(IxaError::DuplicatePropertyInPropertyList {
215                                    first_index: i,
216                                    second_index: j,
217                                });
218                            }
219                        }
220                    }
221
222                    Ok(())
223                }
224
225                fn contains_properties(property_type_ids: &[TypeId]) -> bool {
226                    let self_property_type_ids: [TypeId; $ct] = [#(<P~N as $crate::entity::property::Property<E>>::type_id(),)*];
227
228                    property_type_ids.len() <= $ct && property_type_ids.iter().all(|id| self_property_type_ids.contains(id))
229                }
230
231                fn set_values_for_new_entity(&self, entity_id: EntityId<E>, property_store: &mut PropertyStore<E>){
232                    #({
233                        let property_value_store = property_store.get_mut::<P~N>();
234                        property_value_store.set(entity_id, self.N);
235                    })*
236                }
237
238                fn emit_initialized_events(
239                    &self,
240                    context: &mut Context,
241                    entity_id: EntityId<E>,
242                    subscriptions: &BitSet,
243                ) {
244                    #(
245                        if subscriptions.get(P~N::id()) {
246                            emit_initialized_event(context, entity_id, self.N);
247                        }
248                    )*
249                }
250
251                fn get_values_for_entity(context: &Context, entity_id: EntityId<E>) -> Self {
252                    (#(context.get_property::<E, P~N>(entity_id), )*)
253                }
254            }
255        });
256    };
257}
258
259// Generate impls for tuple lengths 2 through 20. These tuple impls remain available for internal
260// initialization/query machinery and for type-level strata lists, but not as direct `add_entity`
261// inputs.
262seq!(Z in 2..=20 {
263    impl_property_list!(Z);
264});