ixa/entity/
property_list.rs

1/*!
2
3A [`PropertyList<E>`] is just a tuple of distinct properties of the same [`Entity`] `E`. It
4is used in two distinct places: as an initialization list for a new entity, and as a query.
5
6Both use cases have the following two constraints:
7
81. The properties are properties of the same entity.
92. The properties are distinct.
10
11We enforce the first constraint with the type system by only implementing `PropertyList<E>`
12for tuples of types implementing `Property<E>` (of length up to some max). Using properties
13for mismatched entities will result in a nice compile-time error at the point of use.
14
15Unfortunately, the second constraint has to be enforced at runtime. We implement `PropertyList::validate()` to do this.
16
17For both use cases, the order in which the properties appear is
18unimportant in spite of the Rust language semantics of tuple types.
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::IxaError;
30
31pub trait PropertyList<E: Entity>: Copy + 'static {
32    /// Validates that the properties are distinct. If not, returns an error describing the problematic properties.
33    fn validate() -> Result<(), IxaError>;
34
35    /// Checks that this property list includes all properties in the given list.
36    fn contains_properties(property_type_ids: &[TypeId]) -> bool;
37
38    /// Checks that this property list contains all required properties of the entity.
39    fn contains_required_properties() -> bool {
40        Self::contains_properties(E::required_property_ids())
41    }
42
43    /// Assigns the given entity the property values in `self` in the `property_store`.
44    /// This method does NOT emit property change events, as it is called upon entity creation.
45    fn set_values_for_entity(&self, entity_id: EntityId<E>, property_store: &PropertyStore<E>);
46}
47
48// The empty tuple is an empty `PropertyList<E>` for every `E: Entity`.
49impl<E: Entity> PropertyList<E> for () {
50    fn validate() -> Result<(), IxaError> {
51        Ok(())
52    }
53    fn contains_properties(property_type_ids: &[TypeId]) -> bool {
54        property_type_ids.is_empty()
55    }
56    fn set_values_for_entity(&self, _entity_id: EntityId<E>, _property_store: &PropertyStore<E>) {
57        // No values to assign.
58    }
59}
60
61// ToDo(RobertJacobsonCDC): The following is a fundamental limitation in Rust. If downstream code *can* implement a
62//     trait impl that will cause conflicting implementations with some blanket impl, it disallows it, regardless of
63//     whether the conflict actually exists.
64// A single `Property` is a `PropertyList` of length 1
65// impl<E: Entity, P: Property<E>> PropertyList<E> for P {
66//     fn validate() -> Result<(), String> {
67//         Ok(())
68//     }
69//     fn contains_properties(property_type_ids: &[TypeId]) -> bool {
70//         property_type_ids.len() == 0
71//             || property_type_ids.len() == 1 && property_type_ids[0] == P::type_id()
72//     }
73//     fn set_values_for_entity(&self, entity_id: EntityId<E>, property_store: &PropertyStore<E>) {
74//         let property_value_store = property_store.get::<P>();
75//         property_value_store.set(entity_id, *self);
76//     }
77// }
78
79// A single `Property` tuple is a `PropertyList` of length 1
80impl<E: Entity, P: Property<E>> PropertyList<E> for (P,) {
81    fn validate() -> Result<(), IxaError> {
82        Ok(())
83    }
84    fn contains_properties(property_type_ids: &[TypeId]) -> bool {
85        property_type_ids.is_empty()
86            || property_type_ids.len() == 1 && property_type_ids[0] == P::type_id()
87    }
88    fn set_values_for_entity(&self, entity_id: EntityId<E>, property_store: &PropertyStore<E>) {
89        let property_value_store = property_store.get::<P>();
90        property_value_store.set(entity_id, self.0);
91    }
92}
93
94// Used only within this module.
95macro_rules! impl_property_list {
96    ($ct:literal) => {
97        seq!(N in 0..$ct {
98            impl<E: Entity, #( P~N: Property<E>,)*> PropertyList<E> for (#(P~N, )*){
99                fn validate() -> Result<(), IxaError> {
100                    // For `Property` distinctness check
101                    let property_type_ids: [TypeId; $ct] = [#(P~N::type_id(),)*];
102
103                    for i in 0..$ct - 1 {
104                        for j in (i + 1)..$ct {
105                            if property_type_ids[i] == property_type_ids[j] {
106                                return Err(format!(
107                                    "the same property appears in both position {} and {} in the property list",
108                                    i,
109                                    j
110                                ).into());
111                            }
112                        }
113                    }
114
115                    Ok(())
116                }
117
118                fn contains_properties(property_type_ids: &[TypeId]) -> bool {
119                    let self_property_type_ids: [TypeId; $ct] = [#(P~N::type_id(),)*];
120
121                    property_type_ids.len() <= $ct && property_type_ids.iter().all(|id| self_property_type_ids.contains(id))
122                }
123
124                fn set_values_for_entity(&self, entity_id: EntityId<E>, property_store: &PropertyStore<E>){
125                    #({
126                        let property_value_store = property_store.get::<P~N>();
127                        property_value_store.set(entity_id, self.N);
128                    })*
129                }
130            }
131        });
132    };
133}
134
135// Generate impls for tuple lengths 2 through 10.
136seq!(Z in 2..=5 {
137    impl_property_list!(Z);
138});