ixa/macros/
property_impl.rs

1/*!
2
3Macros for implementing properties.
4
5# [`define_property!`][macro@crate::define_property]
6
7For the most common cases, use the [`define_property!`][macro@crate::define_property] macro. This macro defines a struct or enum
8with the standard derives required by the [`Property`][crate::entity::property::Property] trait and implements [`Property`][crate::entity::property::Property] (via
9[`impl_property!`][macro@crate::impl_property]) for you.
10
11```rust,ignore
12define_property!(struct Age(u8), Person);
13define_property!(struct Location(City, State), Person);
14define_property!(
15    enum InfectionStatus {
16        Susceptible,
17        Infectious,
18        Recovered,
19    },
20    Person,
21    default_const = InfectionStatus::Susceptible
22);
23```
24
25Notice the convenient `default_const = <default_value>` keyword argument that allows you to
26define a compile-time constant default value for the property. This is an optional argument.
27If it is omitted, a value for the property must be supplied upon entity creation.
28
29The primary advantage of using this macro is that it automatically derives the list of traits every
30[`Property`][crate::entity::property::Property] needs to derive for you. You don't have to remember them. You also get a cute syntax for
31specifying the default value, but it's not much harder to specify default values using other macros.
32
33If you need the macro to generate `Eq` and/or `Hash` manually instead of deriving them, use the
34optional `impl_eq_hash = ...` argument with one of the following values: `Eq`, `Hash`, `both`, or
35`neither`.
36
37Notice you need to use the `struct` or `enum` keywords, but you don't need to
38specify the visibility. A `pub` visibility is added automatically to the struct
39and to inner fields of tuple structs in the expansion.
40
41# [`impl_property!`][macro@crate::impl_property]
42
43You can implement [`Property`][crate::entity::property::Property] for existing types using the
44[`impl_property!`][macro@crate::impl_property] macro. This macro defines the
45[`Property`][crate::entity::property::Property] trait implementation for you but doesn't take care
46of the `#[derive(..)]` boilerplate, so you have to remember to derive or implement the traits
47required by [`AnyProperty`][crate::entity::property::AnyProperty] for your type: `Copy`, `Clone`,
48`Debug`, `PartialEq`, `Eq`, and `Hash`.
49
50Some examples:
51
52```rust,ignore
53define_entity!(Person);
54
55// The `define_property!` automatically adds `pub` visibility to the struct and its tuple fields. If
56// we want to restrict the visibility of our `Property` type, we can use the `impl_property!` macro
57// instead. The only catch is, we have to remember to derive or implement the traits required by
58// `AnyProperty`.
59#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
60struct Age(pub u8);
61impl_property!(Age, Person);
62
63// Here we derive `Default`, which also requires an attribute on one
64// of the variants. (`Property` has its own independent mechanism for
65// assigning default values for entities unrelated to the `Default` trait.)
66#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
67enum InfectionStatus {
68    #[default]
69    Susceptible,
70    Infected,
71    Recovered,
72}
73// We also specify the default value explicitly for entities.
74impl_property!(InfectionStatus, Person, default_const = InfectionStatus::Susceptible);
75
76// Exactly equivalent to
77//    `define_property!(struct Vaccinated(pub bool), Person, default_const = Vaccinated(false));`
78#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
79pub struct Vaccinated(pub bool);
80impl_property!(Vaccinated, Person, default_const = Vaccinated(false));
81```
82
83# [`impl_property!`][macro@crate::impl_property] with options
84
85The [`impl_property!`][macro@crate::impl_property] macro gives you much more control over the implementation of your
86property type. It takes optional keyword arguments for things like the default value,
87initialization strategy, and how the property is converted to a string for display.
88
89Non-derived properties either have a default constant value for new entities
90(`default_const = ...`), or a value is required to be provided for new entities
91(no `default_const`).
92
93```rust,ignore
94impl_property!(
95    InfectionStatus,
96    Person,
97    default_const = InfectionStatus::Susceptible,
98    display_impl = |v| format!("status: {v:?}")
99);
100```
101
102## Use case: `Property::CanonicalValue` different from `Self`
103
104The `Property::CanonicalValue` type is used to store the property value in
105the index. If the property type is different from the value type, you can
106specify a custom canonical type using the `canonical_value` parameter, but
107you also must provide a conversion function to and from the canonical type.
108Because the canonical value participates directly in index hashing and equality,
109it must satisfy [`AnyProperty`][crate::entity::property::AnyProperty].
110
111```rust,ignore
112define_entity!(WeatherStation);
113
114#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
115pub struct DegreesFahrenheit(pub f64);
116
117#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
118pub struct DegreesCelsius(pub f64);
119
120// Custom canonical type
121impl_property!(
122    DegreesFahrenheit,
123    WeatherStation,
124    canonical_value = DegreesCelsius,
125    make_canonical = |s: &DegreesFahrenheit| DegreesCelsius((s.0 - 32.0) * 5.0 / 9.0),
126    make_uncanonical = |v: DegreesCelsius| DegreesFahrenheit(v.0 * 9.0 / 5.0 + 32.0),
127    display_impl = |v| format!("{:.1} °C", v.0)
128);
129```
130
131*/
132
133/// Defines a `struct` or `enum` with a standard set of derives and automatically invokes
134/// [`impl_property!`][macro@crate::impl_property] for it. This macro provides a concise shorthand for defining
135/// simple property types that follow the same derive and implementation pattern.
136///
137/// The macro supports the following forms:
138///
139/// ### 1. Tuple Structs
140/// ```rust
141/// # use ixa::{define_entity, define_property};
142/// # define_entity!(Person);
143/// define_property!(struct Age(u8), Person);
144/// ```
145/// Expands to:
146/// ```rust
147/// # use ixa::{impl_property, define_entity};
148/// # define_entity!(Person);
149/// #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, serde::Serialize, serde::Deserialize)]
150/// pub struct Age(u8);
151/// impl_property!(Age, Person);
152/// ```
153///
154/// You can define multiple tuple fields:
155/// ```rust,ignore
156/// define_property!(struct Location(City, State), Person);
157/// ```
158///
159/// ### 2. Named-field Structs
160/// ```rust
161/// # use ixa::{define_property, define_entity};
162/// # define_entity!(Person);
163/// define_property!(struct Coordinates { x: i32, y: i32 }, Person);
164/// ```
165/// Expands to:
166/// ```rust
167/// # use ixa::{impl_property, define_entity};
168/// # define_entity!(Person);
169/// #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, serde::Serialize, serde::Deserialize)]
170/// pub struct Coordinates { x: i32, y: i32 }
171/// impl_property!(Coordinates, Person);
172/// ```
173///
174/// ### 3. Enums
175/// ```rust
176/// # use ixa::{define_property, define_entity};
177/// # define_entity!(Person);
178/// define_property!(
179///     enum InfectionStatus {
180///         Susceptible,
181///         Infectious,
182///         Recovered,
183///     },
184///     Person
185/// );
186/// ```
187/// Expands to:
188/// ```rust
189/// # use ixa::{impl_property, define_entity};
190/// # define_entity!(Person);
191/// #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, serde::Serialize, serde::Deserialize)]
192/// pub enum InfectionStatus {
193///     Susceptible,
194///     Infectious,
195///     Recovered,
196/// }
197/// impl_property!(InfectionStatus, Person);
198/// ```
199///
200/// ### Notes
201///
202/// - By default, the generated type derives `Debug`, `PartialEq`, `Eq`, `Hash`, `Clone`, and `Copy`.
203/// - Use the optional `default_const = <default_value>` argument to define a compile-time constant
204///   default for the property.
205/// - Use `impl_eq_hash = Eq`, `Hash`, `both`, or `neither` as the first optional argument to suppress the default
206///   `Eq`/`Hash` derives and switch to generated or user-supplied implementations.
207/// - Remaining optional arguments follow the same ordering as [`impl_property!`][macro@crate::impl_property].
208/// - If you need a more complex type definition (e.g., generics, attributes, or non-`Copy`
209///   fields), define the type manually and then call [`impl_property!`][macro@crate::impl_property] directly.
210#[macro_export]
211macro_rules! define_property {
212    // Implementation Notes
213    //
214    // To implement the optional `impl_eq_hash` keyword argument, we have the following choices:
215    //
216    // 1. Have a single public match branch per type form with `$(, impl_eq_hash =
217    //    $impl_eq_hash:ident)?`, but explicitly list all the keyword options. This option disallows
218    //    the `$(, $($extra:tt)+)*` pattern for the tail.
219    // 2. Have two branches per type form, one with the `impl_eq_hash = ...` keyword present and one
220    //    with it absent, and use the `$(, $($extra:tt)+)*` pattern for the tail. This duplicates the
221    //    number of public match arms, but it allows us to keep the keyword arguments defined in
222    //    `impl_property!` instead of repeated throughout the code.
223    // 3. Use a proc macro or "TT munching", both of which are more heavy weight.
224    //
225    // We choose the second option. Unfortunately, this doesn't completely eliminate repetition of
226    // the list of keyword arguments. We still have them in the
227    // `impl_derived_property!(@with_option_display_default ...)` and
228    // `impl_property!(@with_option_display_default ...)` subcommands.
229
230    (
231        struct $name:ident ( $visibility:vis Option<$inner_ty:ty> ),
232        $entity:ident,
233        impl_eq_hash = $impl_eq_hash:ident
234        $(, $($extra:tt)*)?
235    ) => {
236        $crate::define_property!(
237            @apply_property_decoration $impl_eq_hash,
238            pub struct $name(pub Option<$inner_ty>);,
239            $name
240        );
241        $crate::impl_property!(@with_option_display_default $name, $entity $(, $($extra)*)?);
242    };
243    (
244        struct $name:ident ( $visibility:vis Option<$inner_ty:ty> ),
245        $entity:ident
246        $(, $($extra:tt)*)?
247    ) => {
248        $crate::define_property!(
249            @apply_property_decoration ,
250            pub struct $name(pub Option<$inner_ty>);,
251            $name
252        );
253        $crate::impl_property!(@with_option_display_default $name, $entity $(, $($extra)*)?);
254    };
255
256    (
257        struct $name:ident ( $($visibility:vis $field_ty:ty),* $(,)? ),
258        $entity:ident,
259        impl_eq_hash = $impl_eq_hash:ident
260        $(, $($extra:tt)*)?
261    ) => {
262        $crate::define_property!(
263            @apply_property_decoration $impl_eq_hash,
264            pub struct $name($(pub $field_ty),*);,
265            $name
266        );
267        $crate::impl_property!($name, $entity $(, $($extra)*)?);
268    };
269    (
270        struct $name:ident ( $($visibility:vis $field_ty:ty),* $(,)? ),
271        $entity:ident
272        $(, $($extra:tt)*)?
273    ) => {
274        $crate::define_property!(
275            @apply_property_decoration ,
276            pub struct $name($(pub $field_ty),*);,
277            $name
278        );
279        $crate::impl_property!($name, $entity $(, $($extra)*)?);
280    };
281
282    (
283        struct $name:ident { $($visibility:vis $field_name:ident : $field_ty:ty),* $(,)? },
284        $entity:ident,
285        impl_eq_hash = $impl_eq_hash:ident
286        $(, $($extra:tt)*)?
287    ) => {
288        $crate::define_property!(
289            @apply_property_decoration $impl_eq_hash,
290            pub struct $name { $(pub $field_name : $field_ty),* },
291            $name
292        );
293        $crate::impl_property!($name, $entity $(, $($extra)*)?);
294    };
295    (
296        struct $name:ident { $($visibility:vis $field_name:ident : $field_ty:ty),* $(,)? },
297        $entity:ident
298        $(, $($extra:tt)*)?
299    ) => {
300        $crate::define_property!(
301            @apply_property_decoration ,
302            pub struct $name { $(pub $field_name : $field_ty),* },
303            $name
304        );
305        $crate::impl_property!($name, $entity $(, $($extra)*)?);
306    };
307
308    (
309        enum $name:ident {
310            $($variant:ident),* $(,)?
311        },
312        $entity:ident,
313        impl_eq_hash = $impl_eq_hash:ident
314        $(, $($extra:tt)*)?
315    ) => {
316        $crate::define_property!(
317            @apply_property_decoration $impl_eq_hash,
318            pub enum $name { $($variant),* },
319            $name
320        );
321        $crate::impl_property!($name, $entity $(, $($extra)*)?);
322    };
323    (
324        enum $name:ident {
325            $($variant:ident),* $(,)?
326        },
327        $entity:ident
328        $(, $($extra:tt)*)?
329    ) => {
330        $crate::define_property!(
331            @apply_property_decoration ,
332            pub enum $name { $($variant),* },
333            $name
334        );
335        $crate::impl_property!($name, $entity $(, $($extra)*)?);
336    };
337
338    // Both `define_property!` and `define_derived_property!` need to attach derives to a
339    // concrete item, so the mode table lives here as a shared internal subcommand.
340    (@apply_property_decoration , $item:item, $name:ident) => {
341        #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, serde::Serialize, serde::Deserialize)]
342        $item
343    };
344    (@apply_property_decoration Eq, $item:item, $name:ident) => {
345        #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, $crate::rkyv::Archive, $crate::rkyv::Serialize)]
346        #[rkyv(crate = $crate::rkyv)]
347        $item
348        $crate::define_property!(@apply_property_decoration_eq_impl $name);
349    };
350    (@apply_property_decoration Hash, $item:item, $name:ident) => {
351        #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, $crate::rkyv::Archive, $crate::rkyv::Serialize)]
352        #[rkyv(crate = $crate::rkyv)]
353        $item
354        $crate::define_property!(@apply_property_decoration_hash_impl $name);
355    };
356    (@apply_property_decoration both, $item:item, $name:ident) => {
357        #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, $crate::rkyv::Archive, $crate::rkyv::Serialize)]
358        #[rkyv(crate = $crate::rkyv)]
359        $item
360        $crate::define_property!(@apply_property_decoration_eq_impl $name);
361        $crate::define_property!(@apply_property_decoration_hash_impl $name);
362    };
363    (@apply_property_decoration neither, $item:item, $name:ident) => {
364        #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
365        $item
366    };
367    (@apply_property_decoration $mode:ident, $item:item, $name:ident) => {
368        compile_error!("`impl_eq_hash` must be one of `Eq`, `Hash`, `both`, or `neither`");
369    };
370
371    (@apply_property_decoration_eq_impl $name:ident) => {
372        impl core::cmp::PartialEq for $name {
373            fn eq(&self, other: &Self) -> bool {
374                const N: usize = core::mem::size_of::<<$name as $crate::rkyv::Archive>::Archived>();
375
376                let left = $crate::rkyv::api::high::to_bytes_in::<_, $crate::rkyv::rancor::Error>(
377                    self,
378                    $crate::hashing::EqualityBufferWriter::<N>::new(),
379                )
380                .expect("serializing left value for equality comparison failed");
381
382                let right = $crate::rkyv::api::high::to_bytes_in::<_, $crate::rkyv::rancor::Error>(
383                    other,
384                    $crate::hashing::EqualityBufferWriter::<N>::new(),
385                )
386                .expect("serializing right value for equality comparison failed");
387
388                left.as_written() == right.as_written()
389            }
390        }
391
392        impl core::cmp::Eq for $name {}
393    };
394    (@apply_property_decoration_hash_impl $name:ident) => {
395        impl core::hash::Hash for $name {
396            fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
397                $crate::rkyv::api::high::to_bytes_in::<_, $crate::rkyv::rancor::Error>(
398                    self,
399                    $crate::hashing::HasherWriter::new(state),
400                )
401                .expect("serialization failed while hashing");
402            }
403        }
404    };
405
406}
407
408/// Implements the [`Property`][crate::entity::property::Property] trait for the given property type and entity.
409///
410/// Use this macro when you want to implement the `Property<E: Entity>` trait for a type you have declared yourself.
411/// You might want to declare your own property type yourself instead of using the [`define_property!`][macro@crate::define_property] macro if
412/// - you want a visibility other than `pub`
413/// - you want to derive additional traits
414/// - your type definition requires attribute proc-macros or other special syntax (for example, deriving
415///   `Default` on an enum requires an attribute on one of the variants)
416///
417/// Example:
418///
419/// In this example, in addition to the set of derives required for all property types, we also derive the `Default`
420/// trait for an enum type, which requires the proc-macro attribute `#[default]` on one of the variants.
421///
422/// ```rust
423/// # use ixa::{impl_property, define_entity};
424/// # define_entity!(Person);
425/// #[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)]
426/// pub enum InfectionStatus {
427///     #[default]
428///     Susceptible,
429///     Infectious,
430///     Recovered,
431/// }
432/// // We also specify that this property is assigned a default value for new entities if a value isn't provided.
433/// // Here we have it coincide with `Default::default()`, but this isn't required.
434/// impl_property!(InfectionStatus, Person, default_const = InfectionStatus::Susceptible);
435/// ```
436///
437/// # Parameters
438///
439/// Parameters must be given in the correct order.
440///
441/// * `$property`: The identifier for the type implementing [`Property`][crate::entity::property::Property].
442/// * `$entity`: The entity type this property is associated with.
443/// * Optional parameters (each may be omitted; defaults will be used):
444///   * `compute_derived_fn = <expr>` — Function used to compute derived properties. Use `define_derived_property!` or
445///     `impl_derived_property!` instead of using this option directly.
446///   * `default_const = <expr>` — Constant default value if the property has one; implies a non-derived property.
447///   * `display_impl = <expr>` — Function converting the property value to a string; defaults to `|v| format!("{v:?}")`.
448///   * `canonical_value = <type>` — If the type stored in the index differs from the property's value type; defaults to
449///     `Self`. If this option is supplied, you will also want to supply `make_canonical` and `make_uncanonical`.
450///   * `make_canonical = <expr>` — Function converting from `Self` to `CanonicalValue`; defaults to `std::convert::identity`.
451///   * `make_uncanonical = <expr>` — Function converting from `CanonicalValue` to `Self`; defaults to `std::convert::identity`.
452/// * Optional parameters that should generally be left alone, used internally to implement derived properties and
453///   multi-properties:
454///   * `index_id_fn = <expr>` — Function used to initialize the property index id; defaults to
455///     `<Self as $crate::entity::property::Property<$entity>>::id()`.
456///   * `collect_deps_fn = <expr>` — Function used to collect property dependencies; defaults to an empty implementation.
457///   * `ctor_registration = <expr>` — Code run in the `ctor` for property registration.
458///
459/// # Semantics
460/// - If `compute_derived_fn` is provided, the property is derived. In this case, `default_const` must be absent, and
461///   calling `Property::default_const()` results in a panic. Use `define_derived_property!` or `impl_derived_property!`
462///   instead of using this option directly.
463/// - If `default_const` is provided, the property is a non-derived constant property. In this case,
464///   `compute_derived_fn` must be absent, and calling `Property::compute_derived()` results in a panic.
465/// - If neither is provided, the property is non-derived and required/explicit; both `Property::default_const()` and
466///   `Property::compute_derived()` panic.
467/// - If both are provided, a compile-time error is emitted.
468#[macro_export]
469macro_rules! impl_property {
470    (
471        $property:ident,
472        $entity:ident
473        $(, compute_derived_fn = $compute_derived_fn:expr)?
474        $(, default_const = $default_const:expr)?
475        $(, canonical_value = $canonical_value:ty)?
476        $(, make_canonical = $make_canonical:expr)?
477        $(, make_uncanonical = $make_uncanonical:expr)?
478        $(, index_id_fn = $index_id_fn:expr)?
479        $(, collect_deps_fn = $collect_deps_fn:expr)?
480        $(, display_impl = $display_impl:expr)?
481        $(, ctor_registration = $ctor_registration:expr)?
482    ) => {
483        // Enforce mutual exclusivity at compile time.
484        $crate::impl_property!(@assert_not_both $($compute_derived_fn)? ; $($default_const)?);
485
486        $crate::impl_property!(
487            @__impl_property_common
488            $property,
489            $entity,
490
491            // canonical value
492            $crate::impl_property!(@unwrap_or_ty $($canonical_value)?, $property),
493
494            // initialization_kind (implicit)
495            $crate::impl_property!(@select_initialization_kind $($compute_derived_fn)? ; $($default_const)?),
496
497            // compute_derived_fn (panic unless explicitly provided)
498            $crate::impl_property!(
499                @unwrap_or
500                $($compute_derived_fn)?,
501                |_, _| panic!("property {} is not derived", stringify!($property))
502            ),
503
504            // default_const (panic unless explicitly provided)
505            $crate::impl_property!(
506                @unwrap_or
507                $($default_const)?,
508                panic!("property {} has no default value", stringify!($property))
509            ),
510
511            // make_canonical
512            $crate::impl_property!(@unwrap_or $($make_canonical)?, std::convert::identity),
513
514            // make_uncanonical
515            $crate::impl_property!(@unwrap_or $($make_uncanonical)?, std::convert::identity),
516
517            // query_parts_type
518            [&'a dyn std::any::Any; 1],
519
520            // canonical_from_sorted_query_parts_fn
521            {
522                |parts: &[&dyn std::any::Any]| -> Option<<$property as $crate::entity::property::Property<$entity>>::CanonicalValue> {
523                    let [part] = parts else {
524                        return None;
525                    };
526                    part.downcast_ref::<$property>()
527                        .copied()
528                        .map(<$property as $crate::entity::property::Property<$entity>>::make_canonical)
529                }
530            },
531
532            // query_parts_for_value_fn
533            {
534                fn default_query_parts_for_value<'a>(value: &'a $property) -> [&'a dyn std::any::Any; 1] {
535                    [value as &'a dyn std::any::Any]
536                }
537
538                default_query_parts_for_value
539            },
540
541            // display_impl
542            $crate::impl_property!(@unwrap_or $($display_impl)?, |v| format!("{v:?}")),
543
544            // index_id_fn
545            $crate::impl_property!(@unwrap_or $($index_id_fn)?, {
546                <Self as $crate::entity::property::Property<$entity>>::id()
547            }),
548
549            // collect_deps_fn
550            $crate::impl_property!(
551                @unwrap_or
552                $($collect_deps_fn)?,
553                |_| {/* Do nothing */}
554            ),
555
556            // ctor_registration
557            $crate::impl_property!(@unwrap_or $($ctor_registration)?, {
558                $crate::entity::property_store::add_to_property_registry::<$entity, $property>();
559            }),
560        );
561    };
562
563    (
564        @with_option_display_default
565        $property:ident,
566        $entity:ident
567        $(, compute_derived_fn = $compute_derived_fn:expr)?
568        $(, default_const = $default_const:expr)?
569        $(, canonical_value = $canonical_value:ty)?
570        $(, make_canonical = $make_canonical:expr)?
571        $(, make_uncanonical = $make_uncanonical:expr)?
572        $(, index_id_fn = $index_id_fn:expr)?
573        $(, collect_deps_fn = $collect_deps_fn:expr)?
574        $(, display_impl = $display_impl:expr)?
575        $(, ctor_registration = $ctor_registration:expr)?
576    ) => {
577        $crate::impl_property!(
578            $property,
579            $entity
580            $(, compute_derived_fn = $compute_derived_fn)?
581            $(, default_const = $default_const)?
582            $(, canonical_value = $canonical_value)?
583            $(, make_canonical = $make_canonical)?
584            $(, make_uncanonical = $make_uncanonical)?
585            $(, index_id_fn = $index_id_fn)?
586            $(, collect_deps_fn = $collect_deps_fn)?
587            , display_impl = $crate::impl_property!(@unwrap_or $($display_impl)?, |value: &Self| {
588                match value.0 {
589                    Some(v) => format!("{:?}", v),
590                    None => "None".to_string(),
591                }
592            })
593            $(, ctor_registration = $ctor_registration)?
594        );
595    };
596
597    (
598        @multi_property
599        $property:ident,
600        $entity:ident,
601        ( $($dependency:ident),+ )
602        $(, compute_derived_fn = $compute_derived_fn:expr)?
603        $(, default_const = $default_const:expr)?
604        $(, canonical_value = $canonical_value:ty)?
605        $(, make_canonical = $make_canonical:expr)?
606        $(, make_uncanonical = $make_uncanonical:expr)?
607        $(, index_id_fn = $index_id_fn:expr)?
608        $(, collect_deps_fn = $collect_deps_fn:expr)?
609        $(, display_impl = $display_impl:expr)?
610        $(, ctor_registration = $ctor_registration:expr)?
611    ) => {
612        $crate::impl_property!(@assert_not_both $($compute_derived_fn)? ; $($default_const)?);
613
614        $crate::impl_property!(
615            @__impl_property_common
616            $property,
617            $entity,
618            $crate::impl_property!(@unwrap_or_ty $($canonical_value)?, $property),
619            $crate::impl_property!(@select_initialization_kind $($compute_derived_fn)? ; $($default_const)?),
620            $crate::impl_property!(
621                @unwrap_or
622                $($compute_derived_fn)?,
623                |_, _| panic!("property {} is not derived", stringify!($property))
624            ),
625            $crate::impl_property!(
626                @unwrap_or
627                $($default_const)?,
628                panic!("property {} has no default value", stringify!($property))
629            ),
630            $crate::impl_property!(@unwrap_or $($make_canonical)?, std::convert::identity),
631            $crate::impl_property!(@unwrap_or $($make_uncanonical)?, std::convert::identity),
632            [&'a dyn std::any::Any; $crate::impl_property!(@count_tts $($dependency),+)],
633            $crate::canonical_from_sorted_query_parts_closure!(( $($dependency),+ )),
634            {
635                $crate::paste::paste! {
636                    fn multi_property_query_parts_for_value<'a>(
637                        value: &'a $property,
638                    ) -> [&'a dyn std::any::Any; $crate::impl_property!(@count_tts $($dependency),+)] {
639                        let keys = [
640                            $(
641                                <$dependency as $crate::entity::property::Property<$entity>>::name(),
642                            )+
643                        ];
644                        let ( $( [<_ $dependency:lower>] ),+ ) = value;
645                        let mut parts = [
646                            $(
647                                [<_ $dependency:lower>] as &'a dyn std::any::Any,
648                            )+
649                        ];
650                        $crate::entity::multi_property::static_reorder_by_keys(&keys, &mut parts);
651                        parts
652                    }
653
654                    multi_property_query_parts_for_value
655                }
656            },
657            $crate::impl_property!(@unwrap_or $($display_impl)?, |v| format!("{v:?}")),
658            $crate::impl_property!(@unwrap_or $($index_id_fn)?, {
659                <Self as $crate::entity::property::Property<$entity>>::id()
660            }),
661            $crate::impl_property!(@unwrap_or $($collect_deps_fn)?, |_| {/* Do nothing */}),
662            $crate::impl_property!(@unwrap_or $($ctor_registration)?, {
663                $crate::entity::property_store::add_to_property_registry::<$entity, $property>();
664            }),
665        );
666    };
667
668    // Compile-time mutual exclusivity check.
669    (@assert_not_both $compute_derived_fn:expr ; $default_const:expr) => {
670        compile_error!(
671            "impl_property!: `compute_derived_fn = ...` (derived property) and `default_const = ...` \
672             (non-derived property default constant) are mutually exclusive. Remove one of them."
673        );
674    };
675    (@assert_not_both $compute_derived_fn:expr ; ) => {};
676    (@assert_not_both ; $default_const:expr) => {};
677    (@assert_not_both ; ) => {};
678
679    // Select initialization kind (implicit).
680    (@select_initialization_kind $compute_derived_fn:expr ; $default_const:expr) => {
681        // This arm should be unreachable because @assert_not_both triggers first, but keep it
682        // as a backstop if the macro is used incorrectly.
683        compile_error!(
684            "impl_property!: cannot select initialization kind because both `compute_derived_fn` \
685             and `default_const` are present"
686        )
687    };
688    (@select_initialization_kind $compute_derived_fn:expr ; ) => {
689        $crate::entity::property::PropertyInitializationKind::Derived
690    };
691    (@select_initialization_kind ; $default_const:expr) => {
692        $crate::entity::property::PropertyInitializationKind::Constant
693    };
694    (@select_initialization_kind ; ) => {
695        $crate::entity::property::PropertyInitializationKind::Explicit
696    };
697
698    // Helpers for defaults, a pair per macro parameter type (`expr`, `ty`).
699    (@unwrap_or $value:expr, $_default:expr) => { $value };
700    (@unwrap_or, $default:expr) => { $default };
701
702    (@unwrap_or_ty $ty:ty, $_default:ty) => { $ty };
703    (@unwrap_or_ty, $default:ty) => { $default };
704
705    (@replace_with_unit $_tt:tt) => { () };
706    (@count_tts $($tt:tt),* $(,)?) => {
707        <[()]>::len(&[$($crate::impl_property!(@replace_with_unit $tt)),*])
708    };
709
710    // This is the purely syntactic implementation.
711    (
712        @__impl_property_common
713        $property:ident,           // The name of the type we are implementing `Property` for
714        $entity:ident,             // The entity type this property is associated with
715        $canonical_value:ty,       // If the type stored in the index is different from Self, the name of that type
716        $initialization_kind:expr, // The kind of initialization this property has (implicit selection)
717        $compute_derived_fn:expr,  // If the property is derived, the function that computes the value
718        $default_const:expr,       // If the property has a constant default initial value, the default value
719        $make_canonical:expr,      // A function that takes a value and returns a canonical value
720        $make_uncanonical:expr,    // A function that takes a canonical value and returns a value
721        $query_parts_type:ty,
722        $canonical_from_sorted_query_parts_fn:expr,
723        $query_parts_for_value_fn:expr,
724        $display_impl:expr,        // A function that takes a canonical value and returns a string representation of this property
725        $index_id_fn:expr,         // Code that returns the unique index for this property
726        $collect_deps_fn:expr,     // If the property is derived, the function that computes the value
727        $ctor_registration:expr,   // Code that runs in a ctor for property registration
728    ) => {
729        impl $crate::entity::property::Property<$entity> for $property {
730            type CanonicalValue = $canonical_value;
731            type QueryParts<'a> = $query_parts_type where Self: 'a;
732
733            const NAME: &'static str = stringify!($property);
734
735            fn initialization_kind() -> $crate::entity::property::PropertyInitializationKind {
736                $initialization_kind
737            }
738
739            fn compute_derived(
740                _context: &$crate::Context,
741                _entity_id: $crate::entity::EntityId<$entity>,
742            ) -> Self {
743                ($compute_derived_fn)(_context, _entity_id)
744            }
745
746            fn default_const() -> Self {
747                $default_const
748            }
749
750            fn make_canonical(self) -> Self::CanonicalValue {
751                ($make_canonical)(self)
752            }
753
754            fn make_uncanonical(value: Self::CanonicalValue) -> Self {
755                ($make_uncanonical)(value)
756            }
757
758            fn canonical_from_sorted_query_parts(
759                parts: &[&dyn std::any::Any],
760            ) -> Option<Self::CanonicalValue> {
761                ($canonical_from_sorted_query_parts_fn)(parts)
762            }
763
764            fn query_parts_for_value(value: &Self) -> Self::QueryParts<'_> {
765                ($query_parts_for_value_fn)(value)
766            }
767
768            fn get_display(&self) -> String {
769                ($display_impl)(self)
770            }
771
772            fn id() -> usize {
773                // This static must be initialized with a compile-time constant expression.
774                // We use `usize::MAX` as a sentinel to mean "uninitialized". This
775                // static variable is shared among all instances of this concrete item type.
776                static INDEX: std::sync::atomic::AtomicUsize =
777                    std::sync::atomic::AtomicUsize::new(usize::MAX);
778
779                // Fast path: already initialized.
780                let index = INDEX.load(std::sync::atomic::Ordering::Relaxed);
781                if index != usize::MAX {
782                    return index;
783                }
784
785                // Slow path: initialize it.
786                $crate::entity::property_store::initialize_property_id::<$entity>(&INDEX)
787            }
788
789            fn index_id() -> usize {
790                $index_id_fn
791            }
792
793            fn collect_non_derived_dependencies(result: &mut $crate::HashSet<usize>) {
794                ($collect_deps_fn)(result)
795            }
796        }
797
798        $crate::paste::paste! {
799            $crate::ctor::declarative::ctor!{
800                #[ctor(unsafe)]
801                fn [<_register_property_ $entity:snake _ $property:snake>]() {
802                    $ctor_registration
803                }
804            }
805        }
806    };
807}
808
809/// The "derived" variant of [`define_property!`][macro@crate::define_property] for defining simple derived property types.
810/// Defines a `struct` or `enum` with a standard set of derives and automatically invokes
811/// [`impl_derived_property!`][macro@crate::impl_derived_property] for it.
812///
813/// Defines a derived property with the following parameters:
814/// * Property type declaration: A struct or enum declaration.
815/// * `$entity`: The name of the entity of which the new type is a property.
816/// * `[$($dependency),+]`: A list of person properties the derived property depends on.
817/// * `[$(global_dependency),*]`: A list of global properties the derived property depends on. Can optionally be omitted if empty.
818/// * `$calculate`: A closure that takes the values of each dependency and returns the derived value.
819/// * Optional parameters: The same optional parameters accepted by [`impl_property!`][macro@crate::impl_property],
820///   plus `impl_eq_hash = Eq | Hash | both | neither` to control whether `Eq`/`Hash` are derived or generated
821///   for the declared type, mirroring [`define_property!`][macro@crate::define_property].
822#[macro_export]
823macro_rules! define_derived_property {
824    // Implementation Notes
825    //
826    // We reuse `define_property!`'s shared decoration helper, then delegate the derived-property
827    // behavior to `impl_derived_property!`.
828    //
829    // See `derive_property!` implementation notes for why each type form is duplicated.
830
831    // Struct (tuple) with single Option<T> field
832    (
833        struct $name:ident ( $visibility:vis Option<$inner_ty:ty> ),
834        $entity:ident,
835        [$($dependency:ident),*]
836        $(, [$($global_dependency:ident),*])?,
837        |$($param:ident),+| $derive_fn:expr,
838        impl_eq_hash = $impl_eq_hash:ident
839        $(, $($extra:tt)+)*
840    ) => {
841        $crate::define_property!(
842            @apply_property_decoration
843            $impl_eq_hash,
844            pub struct $name(pub Option<$inner_ty>);,
845            $name
846        );
847
848        $crate::impl_derived_property!(
849            @with_option_display_default
850            $name,
851            $entity,
852            [$($dependency),*],
853            [$($($global_dependency),*)?],
854            |$($param),+| $derive_fn
855            $(, $($extra)+)*
856        );
857    };
858    (
859        struct $name:ident ( $visibility:vis Option<$inner_ty:ty> ),
860        $entity:ident,
861        [$($dependency:ident),*]
862        $(, [$($global_dependency:ident),*])?,
863        |$($param:ident),+| $derive_fn:expr
864        $(, $($extra:tt)+)*
865    ) => {
866        $crate::define_property!(
867            @apply_property_decoration
868            ,
869            pub struct $name(pub Option<$inner_ty>);,
870            $name
871        );
872
873        $crate::impl_derived_property!(
874            @with_option_display_default
875            $name,
876            $entity,
877            [$($dependency),*],
878            [$($($global_dependency),*)?],
879            |$($param),+| $derive_fn
880            $(, $($extra)+)*
881        );
882    };
883
884    // Struct (tuple)
885    (
886        struct $name:ident ( $($visibility:vis $field_ty:ty),* $(,)? ),
887        $entity:ident,
888        [$($dependency:ident),*]
889        $(, [$($global_dependency:ident),*])?,
890        |$($param:ident),+| $derive_fn:expr,
891        impl_eq_hash = $impl_eq_hash:ident
892        $(, $($extra:tt)+)*
893    ) => {
894        $crate::define_property!(
895            @apply_property_decoration
896            $impl_eq_hash,
897            pub struct $name( $(pub $field_ty),* );,
898            $name
899        );
900
901        $crate::impl_derived_property!(
902            $name,
903            $entity,
904            [$($dependency),*],
905            [$($($global_dependency),*)?],
906            |$($param),+| $derive_fn
907            $(, $($extra)+)*
908        );
909    };
910    (
911        struct $name:ident ( $($visibility:vis $field_ty:ty),* $(,)? ),
912        $entity:ident,
913        [$($dependency:ident),*]
914        $(, [$($global_dependency:ident),*])?,
915        |$($param:ident),+| $derive_fn:expr
916        $(, $($extra:tt)+)*
917    ) => {
918        $crate::define_property!(
919            @apply_property_decoration
920            ,
921            pub struct $name( $(pub $field_ty),* );,
922            $name
923        );
924
925        $crate::impl_derived_property!(
926            $name,
927            $entity,
928            [$($dependency),*],
929            [$($($global_dependency),*)?],
930            |$($param),+| $derive_fn
931            $(, $($extra)+)*
932        );
933    };
934
935    // Struct (named fields)
936    (
937        struct $name:ident { $($visibility:vis $field_name:ident : $field_ty:ty),* $(,)? },
938        $entity:ident,
939        [$($dependency:ident),*]
940        $(, [$($global_dependency:ident),*])?,
941        |$($param:ident),+| $derive_fn:expr,
942        impl_eq_hash = $impl_eq_hash:ident
943        $(, $($extra:tt)+)*
944    ) => {
945        $crate::define_property!(
946            @apply_property_decoration
947            $impl_eq_hash,
948            pub struct $name { $($visibility $field_name : $field_ty),* },
949            $name
950        );
951
952        $crate::impl_derived_property!(
953            $name,
954            $entity,
955            [$($dependency),*],
956            [$($($global_dependency),*)?],
957            |$($param),+| $derive_fn
958            $(, $($extra)+)*
959        );
960    };
961    (
962        struct $name:ident { $($visibility:vis $field_name:ident : $field_ty:ty),* $(,)? },
963        $entity:ident,
964        [$($dependency:ident),*]
965        $(, [$($global_dependency:ident),*])?,
966        |$($param:ident),+| $derive_fn:expr
967        $(, $($extra:tt)+)*
968    ) => {
969        $crate::define_property!(
970            @apply_property_decoration
971            ,
972            pub struct $name { $($visibility $field_name : $field_ty),* },
973            $name
974        );
975
976        $crate::impl_derived_property!(
977            $name,
978            $entity,
979            [$($dependency),*],
980            [$($($global_dependency),*)?],
981            |$($param),+| $derive_fn
982            $(, $($extra)+)*
983        );
984    };
985
986    // Enum
987    (
988        enum $name:ident {
989            $($variant:ident),* $(,)?
990        },
991        $entity:ident,
992        [$($dependency:ident),*]
993        $(, [$($global_dependency:ident),*])?,
994        |$($param:ident),+| $derive_fn:expr,
995        impl_eq_hash = $impl_eq_hash:ident
996        $(, $($extra:tt)+)*
997    ) => {
998        $crate::define_property!(
999            @apply_property_decoration
1000            $impl_eq_hash,
1001            pub enum $name {
1002                $($variant),*
1003            },
1004            $name
1005        );
1006
1007        $crate::impl_derived_property!(
1008            $name,
1009            $entity,
1010            [$($dependency),*],
1011            [$($($global_dependency),*)?],
1012            |$($param),+| $derive_fn
1013            $(, $($extra)+)*
1014        );
1015    };
1016    (
1017        enum $name:ident {
1018            $($variant:ident),* $(,)?
1019        },
1020        $entity:ident,
1021        [$($dependency:ident),*]
1022        $(, [$($global_dependency:ident),*])?,
1023        |$($param:ident),+| $derive_fn:expr
1024        $(, $($extra:tt)+)*
1025    ) => {
1026        $crate::define_property!(
1027            @apply_property_decoration
1028            ,
1029            pub enum $name {
1030                $($variant),*
1031            },
1032            $name
1033        );
1034
1035        $crate::impl_derived_property!(
1036            $name,
1037            $entity,
1038            [$($dependency),*],
1039            [$($($global_dependency),*)?],
1040            |$($param),+| $derive_fn
1041            $(, $($extra)+)*
1042        );
1043    };
1044}
1045
1046/// Implements the [`Property`][crate::entity::property::Property] trait for an existing type as a derived property.
1047///
1048/// Accepts the same parameters as [`define_derived_property!`][macro@crate::define_derived_property], except the first parameter is the name of a
1049/// type assumed to already be declared rather than a type declaration. This is the derived property equivalent
1050/// of [`impl_property!`][macro@crate::impl_property]. It calls [`impl_property!`][macro@crate::impl_property] with the appropriate derived property parameters.
1051#[macro_export]
1052macro_rules! impl_derived_property {
1053    (
1054        $name:ident,
1055        $entity:ident,
1056        [$($dependency:ident),*]
1057        $(, [$($global_dependency:ident),*])?,
1058        |$($param:ident),+| $derive_fn:expr
1059        $(, $($extra:tt)+)*
1060    ) => {
1061        $crate::impl_property!(
1062            $name,
1063            $entity,
1064            compute_derived_fn = $crate::impl_derived_property!(
1065                @construct_compute_fn
1066                $entity,
1067                [$($dependency),*],
1068                [$($($global_dependency),*)?],
1069                |$($param),+| $derive_fn
1070            ),
1071            collect_deps_fn = | deps: &mut $crate::HashSet<usize> | {
1072                $(
1073                    if <$dependency as $crate::entity::property::Property<$entity>>::is_derived() {
1074                        <$dependency as $crate::entity::property::Property<$entity>>::collect_non_derived_dependencies(deps);
1075                    } else {
1076                        deps.insert(<$dependency as $crate::entity::property::Property<$entity>>::id());
1077                    }
1078                )*
1079            }
1080            $(, $($extra)+)*
1081        );
1082    };
1083
1084    // Internal branch to construct the compute function.
1085    (
1086        @construct_compute_fn
1087        $entity:ident,
1088        [$($dependency:ident),*],
1089        [$($global_dependency:ident),*],
1090        |$($param:ident),+| $derive_fn:expr
1091    ) => {
1092        |context: &$crate::Context, entity_id| {
1093            #[allow(unused_imports)]
1094            use $crate::global_properties::ContextGlobalPropertiesExt;
1095            #[allow(unused_parens)]
1096            let ($($param,)*) = (
1097                $(context.get_property::<$entity, $dependency>(entity_id)),*,
1098                $(
1099                    context.get_global_property_value($global_dependency)
1100                        .expect(&format!("Global property {} not initialized", stringify!($global_dependency)))
1101                ),*
1102            );
1103            $derive_fn
1104        }
1105    };
1106
1107    (@unwrap_or $value:expr, $_default:expr) => { $value };
1108    (@unwrap_or, $default:expr) => { $default };
1109
1110    (
1111        @with_option_display_default
1112        $name:ident,
1113        $entity:ident,
1114        [$($dependency:ident),*],
1115        [$($global_dependency:ident),*],
1116        |$($param:ident),+| $derive_fn:expr
1117        $(, default_const = $default_const:expr)?
1118        $(, display_impl = $display_impl:expr)?
1119        $(, canonical_value = $canonical_value:ty)?
1120        $(, make_canonical = $make_canonical:expr)?
1121        $(, make_uncanonical = $make_uncanonical:expr)?
1122        $(, index_id_fn = $index_id_fn:expr)?
1123        $(, collect_deps_fn = $collect_deps_fn:expr)?
1124        $(, ctor_registration = $ctor_registration:expr)?
1125    ) => {
1126        $crate::impl_derived_property!(
1127            $name,
1128            $entity,
1129            [$($dependency),*],
1130            [$($global_dependency),*],
1131            |$($param),+| $derive_fn
1132            $(, default_const = $default_const)?
1133            , display_impl = $crate::impl_derived_property!(@unwrap_or $($display_impl)?, |value: &$name| {
1134                match value.0 {
1135                    Some(v) => format!("{:?}", v),
1136                    None => "None".to_string(),
1137                }
1138            })
1139            $(, canonical_value = $canonical_value)?
1140            $(, make_canonical = $make_canonical)?
1141            $(, make_uncanonical = $make_uncanonical)?
1142            $(, index_id_fn = $index_id_fn)?
1143            $(, collect_deps_fn = $collect_deps_fn)?
1144            $(, ctor_registration = $ctor_registration)?
1145        );
1146    };
1147
1148}
1149
1150/// Defines a derived property consisting of a (named) tuple of other properties. The primary use case
1151/// is for indexing and querying properties jointly.
1152///
1153/// The index subsystem is smart enough to reuse indexes for multi-properties that are equivalent up to
1154/// reordering of the component properties. The querying subsystem is able to detect when its multiple
1155/// component properties are equivalent to an indexed multi-property and use that index to perform the
1156/// query.
1157///
1158/// Components must be the underlying property type, not a type alias (see issue #843):
1159///
1160/// ```compile_fail
1161/// use ixa::{define_entity, define_property, define_multi_property};
1162/// define_entity!(Person);
1163/// define_property!(struct Age(u8), Person, default_const = Age(0));
1164/// define_property!(struct Height(u8), Person, default_const = Height(0));
1165/// type Years = Age;
1166/// define_multi_property!((Years, Height), Person);
1167/// ```
1168#[macro_export]
1169macro_rules! define_multi_property {
1170        (
1171            ( $($dependency:ident),+ ),
1172            $entity:ident
1173        ) => {
1174            $crate::paste::paste! {
1175                type [<$($dependency)*>] = ( $($dependency),+ );
1176
1177                // Reject type aliases; see issue #843.
1178                $(
1179                    const _: () = assert!(
1180                        $crate::entity::property::const_str_eq(
1181                            stringify!($dependency),
1182                            <$dependency as $crate::entity::property::Property<$entity>>::NAME,
1183                        ),
1184                        concat!(
1185                            "define_multi_property!: `",
1186                            stringify!($dependency),
1187                            "` is a type alias; use the underlying property type (see issue #843)."
1188                        ),
1189                    );
1190                )+
1191
1192                $crate::impl_property!(
1193                    @multi_property
1194                    [<$($dependency)*>],
1195                    $entity,
1196                    ( $($dependency),+ ),
1197                    compute_derived_fn = |context: &$crate::Context, entity_id: $crate::entity::EntityId<$entity>| {
1198                        (
1199                            $(context.get_property::<$entity, $dependency>(entity_id)),+
1200                        )
1201                    },
1202                    canonical_value = $crate::sorted_tag!(( $($dependency),+ )),
1203                    make_canonical = $crate::reorder_closure!(( $($dependency),+ )),
1204                    make_uncanonical = $crate::unreorder_closure!(( $($dependency),+ )),
1205
1206                    index_id_fn = {
1207                        // This static must be initialized with a compile-time constant expression.
1208                        // We use `usize::MAX` as a sentinel to mean "uninitialized". This
1209                        // static variable is shared among all instances of this concrete item type.
1210                        static INDEX_ID: std::sync::atomic::AtomicUsize =
1211                            std::sync::atomic::AtomicUsize::new(usize::MAX);
1212
1213                        // Fast path: already initialized.
1214                        let index_id = INDEX_ID.load(std::sync::atomic::Ordering::Relaxed);
1215                        if index_id != usize::MAX {
1216                            return index_id;
1217                        }
1218
1219                        // Slow path: initialize it.
1220                        // Multi-properties report a single index ID for all equivalent multi-properties,
1221                        // because they share a single `Index<E, P>` instance.
1222                        let mut type_ids = [$( <$dependency as $crate::entity::property::Property<$entity>>::type_id() ),+];
1223                        type_ids.sort_unstable();
1224                        // Check if an index has already been assigned to this property set.
1225                        match $crate::entity::multi_property::type_ids_to_multi_property_index(&type_ids) {
1226                            Some(index) => {
1227                                // An index exists. Reuse it for our own index.
1228                                INDEX_ID.store(index, std::sync::atomic::Ordering::Relaxed);
1229                                index
1230                            },
1231                            None => {
1232                                // An index ID is not yet assigned. We will use our own index for this property.
1233                                let index = <Self as $crate::entity::property::Property<$entity>>::id();
1234                                INDEX_ID.store(index, std::sync::atomic::Ordering::Relaxed);
1235                                // And register the new index with this property set.
1236                                $crate::entity::multi_property::register_type_ids_to_multi_property_index(
1237                                    &type_ids,
1238                                    index
1239                                );
1240                                index
1241                            }
1242                        }
1243                    },
1244
1245                    collect_deps_fn = | deps: &mut $crate::HashSet<usize> | {
1246                        $(
1247                            if <$dependency as $crate::entity::property::Property<$entity>>::is_derived() {
1248                                <$dependency as $crate::entity::property::Property<$entity>>::collect_non_derived_dependencies(deps);
1249                            } else {
1250                                deps.insert(<$dependency as $crate::entity::property::Property<$entity>>::id());
1251                            }
1252                        )*
1253                    },
1254
1255                    display_impl = |val: &( $($dependency),+ )| {
1256                        let ( $( [<_ $dependency:lower>] ),+ ) = val;
1257                        let mut displayed = String::from("(");
1258                        $(
1259                            displayed.push_str(
1260                                &<$dependency as $crate::entity::property::Property<$entity>>::get_display([<_ $dependency:lower>])
1261                            );
1262                            displayed.push_str(", ");
1263                        )+
1264                        displayed.truncate(displayed.len() - 2);
1265                        displayed.push_str(")");
1266                        displayed
1267                    },
1268
1269                    ctor_registration = {
1270                        // Ensure the property's `index_id()` is initialized at startup.
1271                        let _ = < [<$($dependency)*>] as $crate::entity::property::Property::<$entity> >::index_id();
1272                        $crate::entity::property_store::add_to_property_registry::<$entity, [<$($dependency)*>]>();
1273                    }
1274                );
1275
1276            }
1277        };
1278    }
1279
1280#[cfg(test)]
1281mod tests {
1282    // We define unused properties to test macro implementation.
1283    #![allow(dead_code)]
1284
1285    use crate::entity::{PropertyIndexType, QueryInternal};
1286    use crate::prelude::*;
1287    use crate::with;
1288
1289    define_entity!(Person);
1290    define_entity!(Group);
1291
1292    define_property!(struct Pu32(u32), Person, default_const = Pu32(0));
1293    define_property!(struct POu32(Option<u32>), Person, default_const = POu32(None));
1294    define_property!(
1295        struct POFloat(Option<f64>),
1296        Person,
1297        impl_eq_hash = both,
1298        default_const = POFloat(None)
1299    );
1300    define_property!(
1301        struct POu32Custom(Option<u32>),
1302        Person,
1303        default_const = POu32Custom(None),
1304        display_impl = |value: &POu32Custom| match value.0 {
1305            Some(v) => format!("custom:{v}"),
1306            None => "custom:none".to_string(),
1307        }
1308    );
1309    define_property!(struct Name(&'static str), Person, default_const = Name(""));
1310    define_property!(struct Age(u8), Person, default_const = Age(0));
1311    define_property!(struct Weight(f64), Person, impl_eq_hash = both, default_const = Weight(0.0));
1312
1313    // A struct with named fields
1314    define_property!(
1315        struct Innocculation {
1316            time: f64,
1317            dose: u8,
1318        },
1319        Person,
1320        impl_eq_hash = both,
1321        default_const = Innocculation { time: 0.0, dose: 0 }
1322    );
1323
1324    // An enum non-derived property
1325    define_property!(
1326        enum InfectionStatus {
1327            Susceptible,
1328            Infected,
1329            Recovered,
1330        },
1331        Person,
1332        default_const = InfectionStatus::Susceptible
1333    );
1334
1335    // An enum derived property
1336    define_derived_property!(
1337        enum AgeGroup {
1338            Child,
1339            Adult,
1340            Senior,
1341        },
1342        Person,
1343        [Age], // Depends only on age
1344        |age| {
1345            let age: Age = age;
1346            if age.0 < 18 {
1347                AgeGroup::Child
1348            } else if age.0 < 65 {
1349                AgeGroup::Adult
1350            } else {
1351                AgeGroup::Senior
1352            }
1353        }
1354    );
1355
1356    // Derived property - computed from other properties
1357    define_derived_property!(struct DerivedProp(bool), Person, [Age],
1358        |age| {
1359            DerivedProp(age.0 % 2 == 0)
1360        }
1361    );
1362
1363    define_derived_property!(
1364        struct DerivedMaybeAge(Option<u8>),
1365        Person,
1366        [Age],
1367        |age| DerivedMaybeAge((age.0 != 0).then_some(age.0))
1368    );
1369
1370    define_derived_property!(
1371        struct DerivedMaybeWeight(Option<f64>),
1372        Person,
1373        [Age],
1374        |age| DerivedMaybeWeight((age.0 != 0).then_some(age.0 as f64)),
1375        impl_eq_hash = both
1376    );
1377
1378    define_derived_property!(
1379        struct DerivedMaybeAgeCustom(Option<u8>),
1380        Person,
1381        [Age],
1382        |age| DerivedMaybeAgeCustom((age.0 != 0).then_some(age.0)),
1383        display_impl = |value: &DerivedMaybeAgeCustom| match value.0 {
1384            Some(v) => format!("derived:{v}"),
1385            None => "derived:none".to_string(),
1386        }
1387    );
1388
1389    define_derived_property!(
1390        struct DerivedWeight(f64),
1391        Person,
1392        [Age],
1393        |age| DerivedWeight(age.0 as f64),
1394        impl_eq_hash = both
1395    );
1396
1397    // A property type for two distinct entities.
1398    #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
1399    pub enum InfectionKind {
1400        Respiratory,
1401        Genetic,
1402        Superficial,
1403    }
1404    impl_property!(
1405        InfectionKind,
1406        Person,
1407        default_const = InfectionKind::Respiratory
1408    );
1409    impl_property!(InfectionKind, Group, default_const = InfectionKind::Genetic);
1410
1411    define_multi_property!((Name, Age, Weight), Person);
1412    define_multi_property!((Age, Weight, Name), Person);
1413    define_multi_property!((Weight, Age, Name), Person);
1414
1415    // For convenience
1416    type ProfileNAW = (Name, Age, Weight);
1417    type ProfileAWN = (Age, Weight, Name);
1418    type ProfileWAN = (Weight, Age, Name);
1419
1420    #[test]
1421    fn test_multi_property_ordering() {
1422        let a = (Name("Jane"), Age(22), Weight(180.5));
1423        let b = (Age(22), Weight(180.5), Name("Jane"));
1424        let c = (Weight(180.5), Age(22), Name("Jane"));
1425
1426        // Multi-properties share the same index
1427        assert_eq!(ProfileNAW::index_id(), ProfileAWN::index_id());
1428        assert_eq!(ProfileNAW::index_id(), ProfileWAN::index_id());
1429
1430        let a_canonical: <ProfileNAW as Property<_>>::CanonicalValue =
1431            ProfileNAW::make_canonical(a);
1432        let b_canonical: <ProfileAWN as Property<_>>::CanonicalValue =
1433            ProfileAWN::make_canonical(b);
1434        let c_canonical: <ProfileWAN as Property<_>>::CanonicalValue =
1435            ProfileWAN::make_canonical(c);
1436
1437        assert_eq!(a_canonical, b_canonical);
1438        assert_eq!(a_canonical, c_canonical);
1439
1440        // Equivalent multi-properties must hash canonical values identically.
1441        assert_eq!(
1442            crate::hashing::one_shot_128(&a_canonical),
1443            crate::hashing::one_shot_128(&b_canonical)
1444        );
1445        assert_eq!(
1446            crate::hashing::one_shot_128(&a_canonical),
1447            crate::hashing::one_shot_128(&c_canonical)
1448        );
1449
1450        // Since the canonical values are the same, we could have used any single one, but this
1451        // demonstrates that we can convert from one order to another.
1452        assert_eq!(ProfileNAW::make_uncanonical(b_canonical), a);
1453        assert_eq!(ProfileAWN::make_uncanonical(c_canonical), b);
1454        assert_eq!(ProfileWAN::make_uncanonical(a_canonical), c);
1455    }
1456
1457    #[test]
1458    fn test_multi_property_vs_property_query() {
1459        let mut context = Context::new();
1460
1461        context
1462            .add_entity(with!(Person, Name("John"), Age(42), Weight(220.5)))
1463            .unwrap();
1464        context
1465            .add_entity(with!(Person, Name("Jane"), Age(22), Weight(180.5)))
1466            .unwrap();
1467        context
1468            .add_entity(with!(Person, Name("Bob"), Age(32), Weight(190.5)))
1469            .unwrap();
1470        context
1471            .add_entity(with!(Person, Name("Alice"), Age(22), Weight(170.5)))
1472            .unwrap();
1473
1474        context.index_property::<_, ProfileNAW>();
1475
1476        // Check that all equivalent multi-properties are indexed...
1477        assert!(context.is_property_indexed::<Person, ProfileNAW>());
1478        assert!(context.is_property_indexed::<Person, ProfileAWN>());
1479        assert!(context.is_property_indexed::<Person, ProfileWAN>());
1480        // ...but only one `Index<E, P>` instance was created.
1481        let mut indexed_count = 0;
1482        if context
1483            .get_property_value_store::<Person, ProfileNAW>()
1484            .index_type()
1485            != PropertyIndexType::Unindexed
1486        {
1487            indexed_count += 1;
1488        }
1489        if context
1490            .get_property_value_store::<Person, ProfileAWN>()
1491            .index_type()
1492            != PropertyIndexType::Unindexed
1493        {
1494            indexed_count += 1;
1495        }
1496        if context
1497            .get_property_value_store::<Person, ProfileWAN>()
1498            .index_type()
1499            != PropertyIndexType::Unindexed
1500        {
1501            indexed_count += 1;
1502        }
1503        assert_eq!(indexed_count, 1);
1504
1505        {
1506            let example_query = (Name("Alice"), Age(22), Weight(170.5));
1507            let query_multi_property_id =
1508                <(Name, Age, Weight) as QueryInternal<Person>>::multi_property_id(&example_query);
1509            assert!(query_multi_property_id.is_some());
1510            assert_eq!(ProfileNAW::index_id(), query_multi_property_id.unwrap());
1511            let query_parts = QueryInternal::query_parts(&example_query);
1512            assert_eq!(
1513                ProfileNAW::canonical_from_sorted_query_parts(query_parts.as_ref()),
1514                Some((Name("Alice"), Age(22), Weight(170.5)).make_canonical())
1515            );
1516        }
1517
1518        context.with_query_results(
1519            with!(Person, (Name("John"), Age(42), Weight(220.5))),
1520            &mut |results| {
1521                assert_eq!(results.into_iter().count(), 1);
1522            },
1523        );
1524    }
1525
1526    #[test]
1527    fn test_derived_property() {
1528        let mut context = Context::new();
1529
1530        let senior = context
1531            .add_entity::<Person, _>(with!(Person, Age(92)))
1532            .unwrap();
1533        let child = context
1534            .add_entity::<Person, _>(with!(Person, Age(12)))
1535            .unwrap();
1536        let adult = context
1537            .add_entity::<Person, _>(with!(Person, Age(44)))
1538            .unwrap();
1539
1540        let senior_group: AgeGroup = context.get_property(senior);
1541        let child_group: AgeGroup = context.get_property(child);
1542        let adult_group: AgeGroup = context.get_property(adult);
1543
1544        assert_eq!(senior_group, AgeGroup::Senior);
1545        assert_eq!(child_group, AgeGroup::Child);
1546        assert_eq!(adult_group, AgeGroup::Adult);
1547
1548        // Age has no dependencies (only dependents)
1549        assert!(Age::non_derived_dependencies().is_empty());
1550        // AgeGroup depends only on Age
1551        assert_eq!(AgeGroup::non_derived_dependencies(), [Age::id()]);
1552
1553        // Age has several dependents. This assert may break if you add or remove the properties in this test module.
1554        let mut expected_dependents = [
1555            AgeGroup::id(),
1556            DerivedProp::id(),
1557            DerivedMaybeAge::id(),
1558            DerivedMaybeWeight::id(),
1559            DerivedMaybeAgeCustom::id(),
1560            DerivedWeight::id(),
1561            ProfileNAW::id(),
1562            ProfileAWN::id(),
1563            ProfileWAN::id(),
1564        ];
1565        expected_dependents.sort_unstable();
1566        assert_eq!(Age::dependents(), expected_dependents);
1567    }
1568
1569    #[test]
1570    fn test_get_display() {
1571        let mut context = Context::new();
1572        let person = context
1573            .add_entity(with!(Person, POu32(Some(42)), Pu32(22)))
1574            .unwrap();
1575        assert_eq!(
1576            format!(
1577                "{:}",
1578                POu32::get_display(&context.get_property::<_, POu32>(person))
1579            ),
1580            "42"
1581        );
1582        assert_eq!(
1583            format!(
1584                "{:}",
1585                Pu32::get_display(&context.get_property::<_, Pu32>(person))
1586            ),
1587            "Pu32(22)"
1588        );
1589        let person2 = context
1590            .add_entity(with!(Person, POu32(None), Pu32(11)))
1591            .unwrap();
1592        assert_eq!(
1593            format!(
1594                "{:}",
1595                POu32::get_display(&context.get_property::<_, POu32>(person2))
1596            ),
1597            "None"
1598        );
1599    }
1600
1601    #[test]
1602    fn test_option_property_display_patterns() {
1603        let mut context = Context::new();
1604
1605        let some_person = context
1606            .add_entity(with!(
1607                Person,
1608                POu32(Some(42)),
1609                POFloat(Some(3.5)),
1610                POu32Custom(Some(7)),
1611                Pu32(1),
1612            ))
1613            .unwrap();
1614        let none_person = context
1615            .add_entity(with!(
1616                Person,
1617                POu32(None),
1618                POFloat(None),
1619                POu32Custom(None),
1620                Pu32(2)
1621            ))
1622            .unwrap();
1623
1624        assert_eq!(
1625            POu32::get_display(&context.get_property::<_, POu32>(some_person)),
1626            "42"
1627        );
1628        assert_eq!(
1629            POu32::get_display(&context.get_property::<_, POu32>(none_person)),
1630            "None"
1631        );
1632
1633        assert_eq!(
1634            POFloat::get_display(&context.get_property::<_, POFloat>(some_person)),
1635            "3.5"
1636        );
1637        assert_eq!(
1638            POFloat::get_display(&context.get_property::<_, POFloat>(none_person)),
1639            "None"
1640        );
1641
1642        assert_eq!(
1643            POu32Custom::get_display(&context.get_property::<_, POu32Custom>(some_person)),
1644            "custom:7"
1645        );
1646        assert_eq!(
1647            POu32Custom::get_display(&context.get_property::<_, POu32Custom>(none_person)),
1648            "custom:none"
1649        );
1650    }
1651
1652    #[test]
1653    fn test_option_derived_property_display_patterns() {
1654        let mut context = Context::new();
1655
1656        let some_person = context
1657            .add_entity::<Person, _>(with!(Person, Age(42)))
1658            .unwrap();
1659        let none_person = context
1660            .add_entity::<Person, _>(with!(Person, Age(0)))
1661            .unwrap();
1662
1663        assert_eq!(
1664            DerivedMaybeAge::get_display(&context.get_property::<_, DerivedMaybeAge>(some_person)),
1665            "42"
1666        );
1667        assert_eq!(
1668            DerivedMaybeAge::get_display(&context.get_property::<_, DerivedMaybeAge>(none_person)),
1669            "None"
1670        );
1671
1672        assert_eq!(
1673            DerivedMaybeWeight::get_display(
1674                &context.get_property::<_, DerivedMaybeWeight>(some_person)
1675            ),
1676            "42.0"
1677        );
1678        assert_eq!(
1679            DerivedMaybeWeight::get_display(
1680                &context.get_property::<_, DerivedMaybeWeight>(none_person)
1681            ),
1682            "None"
1683        );
1684
1685        assert_eq!(
1686            DerivedMaybeAgeCustom::get_display(
1687                &context.get_property::<_, DerivedMaybeAgeCustom>(some_person)
1688            ),
1689            "derived:42"
1690        );
1691        assert_eq!(
1692            DerivedMaybeAgeCustom::get_display(
1693                &context.get_property::<_, DerivedMaybeAgeCustom>(none_person)
1694            ),
1695            "derived:none"
1696        );
1697    }
1698
1699    #[test]
1700    fn test_debug_trait() {
1701        let property = Pu32(11);
1702        let debug_str = format!("{:?}", property);
1703        assert_eq!(debug_str, "Pu32(11)");
1704
1705        let property = POu32(Some(22));
1706        let debug_str = format!("{:?}", property);
1707        assert_eq!(debug_str, "POu32(Some(22))");
1708    }
1709
1710    #[test]
1711    fn test_define_derived_property_impl_eq_hash() {
1712        let mut values = crate::HashSet::default();
1713        values.insert(DerivedWeight(3.0));
1714        assert!(values.contains(&DerivedWeight(3.0)));
1715    }
1716}