Skip to main content

ixa/entity/
property.rs

1/*!
2
3A `Property` is the value type for properties associated to an `Entity`.
4
5The `Property` trait should be implemented only with one of the macros `define_property!`, `impl_property!`,
6`define_derived_property!`, `impl_derived_property!`, or `define_multi_property!` to ensure correct and consistent
7implementation.
8
9*/
10
11use std::any::{Any, TypeId};
12use std::fmt::Debug;
13use std::hash::Hash;
14
15use crate::entity::property_store::get_property_dependents_static;
16use crate::entity::{Entity, EntityId};
17use crate::{Context, HashSet};
18
19/// The kind of initialization that a property has.
20#[derive(Copy, Clone, Eq, PartialEq, Debug)]
21pub enum PropertyInitializationKind {
22    /// The property is not derived and has no initial value. Its initialization is _explicit_, meaning it must be set
23    /// by client code at time of creation. Initialization is _explicit_ if and only if the property is _required_,
24    /// that is, if a value for the property must be supplied at time of entity creation.
25    Explicit,
26
27    /// The property is a derived property (it's value is computed dynamically from other property values). It cannot
28    /// be set explicitly.
29    Derived,
30
31    /// The property is given a constant initial value. Its initialization does not
32    /// trigger a change event.
33    Constant,
34}
35
36/// `const fn` string equality — `==` on `&str` isn't `const` on stable.
37#[must_use]
38pub const fn const_str_eq(a: &str, b: &str) -> bool {
39    if a.len() != b.len() {
40        return false;
41    }
42    let a = a.as_bytes();
43    let b = b.as_bytes();
44    let mut i = 0;
45    while i < a.len() {
46        if a[i] != b[i] {
47            return false;
48        }
49        i += 1;
50    }
51    true
52}
53
54/// All properties must implement this trait using one of the `define_property` macros.
55///
56/// Property values must be copyable and comparable for equality so storage and unindexed
57/// query scans can operate on them. Indexed properties must additionally implement
58/// [`IndexableProperty`].
59pub trait Property<E: Entity>: Copy + Debug + PartialEq + 'static {
60    /// Allocation-free representation of the query parts contributed by a property value.
61    type QueryParts<'a>: AsRef<[&'a dyn Any]>
62    where
63        Self: 'a;
64
65    /// Source-level name, set by the macros to `stringify!($property)`.
66    const NAME: &'static str;
67
68    #[must_use]
69    fn name() -> &'static str {
70        Self::NAME
71    }
72
73    /// The kind of initialization this property has.
74    #[must_use]
75    fn initialization_kind() -> PropertyInitializationKind;
76
77    #[must_use]
78    #[inline]
79    fn is_derived() -> bool {
80        Self::initialization_kind() == PropertyInitializationKind::Derived
81    }
82
83    #[must_use]
84    #[inline]
85    fn is_required() -> bool {
86        Self::initialization_kind() == PropertyInitializationKind::Explicit
87    }
88
89    /// Compute the value of the property, possibly by accessing the context and using the entity's ID.
90    #[must_use]
91    fn compute_derived(context: &Context, entity_id: EntityId<E>) -> Self;
92
93    /// Return the default initial constant value.
94    #[must_use]
95    fn default_const() -> Self;
96
97    /// Returns a string representation of the property value, e.g. for writing to a CSV file.
98    #[must_use]
99    fn get_display(&self) -> String;
100
101    /// Reconstruct the property value used for indexed lookup.
102    ///
103    /// Ordinary properties expect a single query part containing `Self`. Multi-properties override
104    /// this to rebuild their declared tuple value directly from already-sorted type-erased query
105    /// parts.
106    #[must_use]
107    fn value_from_query_parts(parts: &[&dyn Any]) -> Option<Self> {
108        let [part] = parts else {
109            return None;
110        };
111        part.downcast_ref::<Self>().copied()
112    }
113
114    /// Expose the query parts for a concrete property value without allocating.
115    ///
116    /// Ordinary properties contribute a single value. Multi-properties override this so singleton
117    /// queries over a multi-property can still be matched against the representative
118    /// multi-property for the equivalent component set.
119    #[must_use]
120    fn query_parts_for_value(value: &Self) -> Self::QueryParts<'_>;
121
122    /// The logical type identity for this property.
123    #[must_use]
124    fn type_id() -> TypeId {
125        TypeId::of::<Self>()
126    }
127
128    /// For implementing the registry pattern
129    #[must_use]
130    fn id() -> usize;
131
132    /// Returns the process-local profiling identity for this singleton property query.
133    ///
134    /// This is macro support and is not a stable identifier. Property implementations generated
135    /// by Ixa macros override it with a per-concrete-implementation cache.
136    #[doc(hidden)]
137    fn query_identity_id() -> usize {
138        panic!("Property implementations must be created with an Ixa property macro")
139    }
140
141    /// Returns a vector of transitive non-derived dependencies. If the property is not derived, the
142    /// Vec will be empty. The dependencies are represented by their `Property<E>::id()` value.
143    ///
144    /// This function is only used to construct the static dependency graph
145    /// within property `ctor`s, after which time the dependents of a property
146    /// are accessible through `Property<E>::dependents()` as a `&'static [usize]`.
147    #[must_use]
148    fn non_derived_dependencies() -> Vec<usize> {
149        let mut result = HashSet::default();
150        Self::collect_non_derived_dependencies(&mut result);
151        result.into_iter().collect()
152    }
153
154    /// An auxiliary helper for `non_derived_dependencies` above.
155    fn collect_non_derived_dependencies(result: &mut HashSet<usize>);
156
157    /// Get a list of derived properties that depend on this property. The properties are
158    /// represented by their `Property::id()`. The list is pre-computed in `ctor`s.
159    #[must_use]
160    fn dependents() -> &'static [usize] {
161        get_property_dependents_static::<E>(Self::id())
162    }
163}
164
165/// Marker trait for properties that can be keyed in property indexes.
166///
167/// Property indexes are hash maps keyed by the property value itself, so indexable
168/// properties must add `Eq` and `Hash` to the general [`Property`] requirements.
169pub trait IndexableProperty<E: Entity>: Property<E> + Eq + Hash {}
170
171impl<E, P> IndexableProperty<E> for P
172where
173    E: Entity,
174    P: Property<E> + Eq + Hash,
175{
176}
177
178#[cfg(test)]
179mod tests {
180    use std::any::Any;
181
182    use super::*;
183    use crate::entity::QueryInternal;
184    use crate::{define_entity, define_property};
185
186    define_entity!(PropertyTestPerson);
187    define_property!(struct PropertyTestAge(u8), PropertyTestPerson);
188
189    #[test]
190    fn const_str_eq_compares_lengths_and_bytes() {
191        assert!(const_str_eq("Age", "Age"));
192        assert!(!const_str_eq("Age", "Ages"));
193        assert!(!const_str_eq("Age", "Axe"));
194    }
195
196    #[test]
197    fn default_property_query_helpers_use_single_value() {
198        let value = PropertyTestAge(42);
199        let parts = [&value as &dyn Any];
200
201        assert_eq!(
202            <PropertyTestAge as Property<PropertyTestPerson>>::value_from_query_parts(&parts),
203            Some(value)
204        );
205        assert_eq!(
206            <PropertyTestAge as Property<PropertyTestPerson>>::value_from_query_parts(&[]),
207            None
208        );
209        assert_eq!(
210            <(PropertyTestAge,) as QueryInternal<PropertyTestPerson>>::multi_property_id(&(value,)),
211            Some(<PropertyTestAge as Property<PropertyTestPerson>>::id())
212        );
213    }
214}