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
33Notice you need to use the `struct` or `enum` keywords, but you don't need to
34specify the visibility. A `pub` visibility is added automatically to the struct
35and to inner fields of tuple structs in the expansion.
36
37# [`impl_property!`][macro@crate::impl_property]
38
39You can implement [`Property`][crate::entity::property::Property] for existing types using the [`impl_property!`][macro@crate::impl_property] macro. This macro defines the
40[`Property`][crate::entity::property::Property] trait implementation for you but doesn't take care of the `#[derive(..)]` boilerplate, so you
41have to remember to `derive` all of `Copy, Clone, Debug, PartialEq, Serialize` in your type declaration.
42
43Some examples:
44
45```rust,ignore
46define_entity!(Person);
47
48// The `define_property!` automatically adds `pub` visibility to the struct and its tuple
49// fields. If we want to restrict the visibility of our `Property` type, we can use the
50// `impl_property!` macro instead. The only
51// catch is, we have to remember to `derive` all of `Copy, Clone, Debug, PartialEq, Serialize`.
52#[derive(Copy, Clone, Debug, PartialEq, Serialize)]
53struct Age(pub u8);
54impl_property!(Age, Person);
55
56// Here we derive `Default`, which also requires an attribute on one
57// of the variants. (`Property` has its own independent mechanism for
58// assigning default values for entities unrelated to the `Default` trait.)
59#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize)]
60enum InfectionStatus {
61    #[default]
62    Susceptible,
63    Infected,
64    Recovered,
65}
66// We also specify the default value explicitly for entities.
67impl_property!(InfectionStatus, Person, default_const = InfectionStatus::Susceptible);
68
69// Exactly equivalent to
70//    `define_property!(struct Vaccinated(pub bool), Person, default_const = Vaccinated(false));`
71#[derive(Copy, Clone, Debug, PartialEq, Serialize)]
72pub struct Vaccinated(pub bool);
73impl_property!(Vaccinated, Person, default_const = Vaccinated(false));
74```
75
76# [`impl_property!`][macro@crate::impl_property] with options
77
78The [`impl_property!`][macro@crate::impl_property] macro gives you much more control over the implementation of your
79property type. It takes optional keyword arguments for things like the default value,
80initialization strategy, and how the property is converted to a string for display.
81
82Non-derived properties either have a default constant value for new entities
83(`default_const = ...`), or a value is required to be provided for new entities
84(no `default_const`).
85
86```rust,ignore
87impl_property!(
88    InfectionStatus,
89    Person,
90    default_const = InfectionStatus::Susceptible,
91    display_impl = |v| format!("status: {v:?}")
92);
93```
94
95## Use case: `Property::CanonicalValue` different from `Self`
96
97The `Property::CanonicalValue` type is used to store the property value in
98the index. If the property type is different from the value type, you can
99specify a custom canonical type using the `canonical_value` parameter, but
100you also must provide a conversion function to and from the canonical type.
101
102```rust,ignore
103define_entity!(WeatherStation);
104
105#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)]
106pub struct DegreesFahrenheit(pub f64);
107
108#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)]
109pub struct DegreesCelsius(pub f64);
110
111// Custom canonical type
112impl_property!(
113    DegreesFahrenheit,
114    WeatherStation,
115    canonical_value = DegreesCelsius,
116    make_canonical = |s: &DegreesFahrenheit| DegreesCelsius((s.0 - 32.0) * 5.0 / 9.0),
117    make_uncanonical = |v: DegreesCelsius| DegreesFahrenheit(v.0 * 9.0 / 5.0 + 32.0),
118    display_impl = |v| format!("{:.1} °C", v.0)
119);
120```
121
122*/
123
124/// Defines a `struct` or `enum` with a standard set of derives and automatically invokes
125/// [`impl_property!`][macro@crate::impl_property] for it. This macro provides a concise shorthand for defining
126/// simple property types that follow the same derive and implementation pattern.
127///
128/// The macro supports the following forms:
129///
130/// ### 1. Tuple Structs
131/// ```rust
132/// # use ixa::{define_entity, define_property};
133/// # define_entity!(Person);
134/// define_property!(struct Age(u8), Person);
135/// ```
136/// Expands to:
137/// ```rust
138/// # use ixa::{impl_property, define_entity};
139/// # use serde::Serialize;
140/// # define_entity!(Person);
141/// #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)]
142/// pub struct Age(u8);
143/// impl_property!(Age, Person);
144/// ```
145///
146/// You can define multiple tuple fields:
147/// ```rust,ignore
148/// define_property!(struct Location(City, State), Person);
149/// ```
150///
151/// ### 2. Named-field Structs
152/// ```rust
153/// # use ixa::{define_property, define_entity};
154/// # define_entity!(Person);
155/// define_property!(struct Coordinates { x: i32, y: i32 }, Person);
156/// ```
157/// Expands to:
158/// ```rust
159/// # use ixa::{impl_property, define_entity};
160/// # use serde::Serialize;
161/// # define_entity!(Person);
162/// #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)]
163/// pub struct Coordinates { x: i32, y: i32 }
164/// impl_property!(Coordinates, Person);
165/// ```
166///
167/// ### 3. Enums
168/// ```rust
169/// # use ixa::{define_property, define_entity};
170/// # define_entity!(Person);
171/// define_property!(
172///     enum InfectionStatus {
173///         Susceptible,
174///         Infectious,
175///         Recovered,
176///     },
177///     Person
178/// );
179/// ```
180/// Expands to:
181/// ```rust
182/// # use ixa::{impl_property, define_entity};
183/// # use serde::Serialize;
184/// # define_entity!(Person);
185/// #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)]
186/// pub enum InfectionStatus {
187///     Susceptible,
188///     Infectious,
189///     Recovered,
190/// }
191/// impl_property!(InfectionStatus, Person);
192/// ```
193///
194/// ### Notes
195///
196/// - The generated type always derives the following traits:
197///   `Debug`, `PartialEq`, `Eq`, `Clone`, `Copy`, and `Serialize`.
198/// - Use the optional `default_const = <default_value>` argument to define a compile-time constant
199///   default for the property.
200/// - If you need a more complex type definition (e.g., generics, attributes, or non-`Copy`
201///   fields), define the type manually and then call [`impl_property!`][macro@crate::impl_property] directly.
202#[macro_export]
203macro_rules! define_property {
204    // Struct (tuple) with single Option<T> field (special case)
205    (
206        struct $name:ident ( $visibility:vis Option<$inner_ty:ty> ),
207        $entity:ident
208        $(, $($extra:tt)+),*
209    ) => {
210        #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)]
211        pub struct $name(pub Option<$inner_ty>);
212
213        // Use impl_property! to provide a custom display implementation
214        $crate::impl_property!(
215            $name,
216            $entity
217            $(, $($extra)+)*
218            , display_impl = |value: &Self| {
219                match value.0 {
220                    Some(v) => format!("{:?}", v),
221                    None => "None".to_string(),
222                }
223            }
224        );
225    };
226
227    // Struct (tuple)
228    (
229        struct $name:ident ( $($visibility:vis $field_ty:ty),* $(,)? ),
230        $entity:ident
231        $(, $($extra:tt)+),*
232    ) => {
233        #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)]
234        pub struct $name($(pub $field_ty),*);
235        $crate::impl_property!($name, $entity $(, $($extra)+)*);
236    };
237
238    // Struct (named fields)
239    (
240        struct $name:ident { $($visibility:vis $field_name:ident : $field_ty:ty),* $(,)? },
241        $entity:ident
242        $(, $($extra:tt)+),*
243    ) => {
244        #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)]
245        pub struct $name { $(pub $field_name : $field_ty),* }
246        $crate::impl_property!($name, $entity $(, $($extra)+)*);
247    };
248
249    // Enum
250    (
251        enum $name:ident {
252            $($variant:ident),* $(,)?
253        },
254        $entity:ident
255        $(, $($extra:tt)+),*
256    ) => {
257        #[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)]
258        pub enum $name {
259            $($variant),*
260        }
261        $crate::impl_property!($name, $entity $(, $($extra)+)*);
262    };
263}
264
265/// Implements the [`Property`][crate::entity::property::Property] trait for the given property type and entity.
266///
267/// Use this macro when you want to implement the `Property<E: Entity>` trait for a type you have declared yourself.
268/// You might want to declare your own property type yourself instead of using the [`define_property!`][macro@crate::define_property] macro if
269/// - you want a visibility other than `pub`
270/// - you want to derive additional traits
271/// - your type definition requires attribute proc-macros or other special syntax (for example, deriving
272///   `Default` on an enum requires an attribute on one of the variants)
273///
274/// Example:
275///
276/// In this example, in addition to the set of derives required for all property types, we also derive the `Default`
277/// trait for an enum type, which requires the proc-macro attribute `#[default]` on one of the variants.
278///
279/// ```rust
280/// # use ixa::{impl_property, define_entity};
281/// # use serde::Serialize;
282/// # define_entity!(Person);
283/// #[derive(Default, Debug, PartialEq, Eq, Clone, Copy, Serialize)]
284/// pub enum InfectionStatus {
285///     #[default]
286///     Susceptible,
287///     Infectious,
288///     Recovered,
289/// }
290/// // We also specify that this property is assigned a default value for new entities if a value isn't provided.
291/// // Here we have it coincide with `Default::default()`, but this isn't required.
292/// impl_property!(InfectionStatus, Person, default_const = InfectionStatus::Susceptible);
293/// ```
294///
295/// # Parameters
296///
297/// Parameters must be given in the correct order.
298///
299/// * `$property`: The identifier for the type implementing [`Property`][crate::entity::property::Property].
300/// * `$entity`: The entity type this property is associated with.
301/// * Optional parameters (each may be omitted; defaults will be used):
302///   * `compute_derived_fn = <expr>` — Function used to compute derived properties. Use `define_derived_property!` or
303///     `impl_derived_property!` instead of using this option directly.
304///   * `default_const = <expr>` — Constant default value if the property has one; implies a non-derived property.
305///   * `display_impl = <expr>` — Function converting the property value to a string; defaults to `|v| format!("{v:?}")`.
306///   * `canonical_value = <type>` — If the type stored in the index differs from the property's value type; defaults to
307///     `Self`. If this option is supplied, you will also want to supply `make_canonical` and `make_uncanonical`.
308///   * `make_canonical = <expr>` — Function converting from `Self` to `CanonicalValue`; defaults to `std::convert::identity`.
309///   * `make_uncanonical = <expr>` — Function converting from `CanonicalValue` to `Self`; defaults to `std::convert::identity`.
310/// * Optional parameters that should generally be left alone, used internally to implement derived properties and
311///   multi-properties:
312///   * `index_id_fn = <expr>` — Function used to initialize the property index id; defaults to `Self::id()`.
313///   * `collect_deps_fn = <expr>` — Function used to collect property dependencies; defaults to an empty implementation.
314///   * `ctor_registration = <expr>` — Code run in the `ctor` for property registration.
315///
316/// # Semantics
317/// - If `compute_derived_fn` is provided, the property is derived. In this case, `default_const` must be absent, and
318///   calling `Property::default_const()` results in a panic. Use `define_derived_property!` or `impl_derived_property!`
319///   instead of using this option directly.
320/// - If `default_const` is provided, the property is a non-derived constant property. In this case,
321///   `compute_derived_fn` must be absent, and calling `Property::compute_derived()` results in a panic.
322/// - If neither is provided, the property is non-derived and required/explicit; both `Property::default_const()` and
323///   `Property::compute_derived()` panic.
324/// - If both are provided, a compile-time error is emitted.
325#[macro_export]
326macro_rules! impl_property {
327    (
328        $property:ident,
329        $entity:ident
330        $(, compute_derived_fn = $compute_derived_fn:expr)?
331        $(, default_const = $default_const:expr)?
332        $(, display_impl = $display_impl:expr)?
333        $(, canonical_value = $canonical_value:ty)?
334        $(, make_canonical = $make_canonical:expr)?
335        $(, make_uncanonical = $make_uncanonical:expr)?
336        $(, index_id_fn = $index_id_fn:expr)?
337        $(, collect_deps_fn = $collect_deps_fn:expr)?
338        $(, ctor_registration = $ctor_registration:expr)?
339    ) => {
340        // Enforce mutual exclusivity at compile time.
341        $crate::impl_property!(@assert_not_both $($compute_derived_fn)? ; $($default_const)?);
342
343        $crate::impl_property!(
344            @__impl_property_common
345            $property,
346            $entity,
347
348            // canonical value
349            $crate::impl_property!(@unwrap_or_ty $($canonical_value)?, $property),
350
351            // initialization_kind (implicit)
352            $crate::impl_property!(@select_initialization_kind $($compute_derived_fn)? ; $($default_const)?),
353
354            // compute_derived_fn (panic unless explicitly provided)
355            $crate::impl_property!(
356                @unwrap_or
357                $($compute_derived_fn)?,
358                |_, _| panic!("property {} is not derived", stringify!($property))
359            ),
360
361            // default_const (panic unless explicitly provided)
362            $crate::impl_property!(
363                @unwrap_or
364                $($default_const)?,
365                panic!("property {} has no default value", stringify!($property))
366            ),
367
368            // make_canonical
369            $crate::impl_property!(@unwrap_or $($make_canonical)?, std::convert::identity),
370
371            // make_uncanonical
372            $crate::impl_property!(@unwrap_or $($make_uncanonical)?, std::convert::identity),
373
374            // display_impl
375            $crate::impl_property!(@unwrap_or $($display_impl)?, |v| format!("{v:?}")),
376
377            // index_id_fn
378            $crate::impl_property!(@unwrap_or $($index_id_fn)?, {
379                <Self as $crate::entity::property::Property<$entity>>::id()
380            }),
381
382            // collect_deps_fn
383            $crate::impl_property!(
384                @unwrap_or
385                $($collect_deps_fn)?,
386                |_| {/* Do nothing */}
387            ),
388
389            // ctor_registration
390            $crate::impl_property!(@unwrap_or $($ctor_registration)?, {
391                $crate::entity::property_store::add_to_property_registry::<$entity, $property>();
392            }),
393        );
394    };
395
396    // Compile-time mutual exclusivity check.
397    (@assert_not_both $compute_derived_fn:expr ; $default_const:expr) => {
398        compile_error!(
399            "impl_property!: `compute_derived_fn = ...` (derived property) and `default_const = ...` \
400             (non-derived property default constant) are mutually exclusive. Remove one of them."
401        );
402    };
403    (@assert_not_both $compute_derived_fn:expr ; ) => {};
404    (@assert_not_both ; $default_const:expr) => {};
405    (@assert_not_both ; ) => {};
406
407    // Select initialization kind (implicit).
408    (@select_initialization_kind $compute_derived_fn:expr ; $default_const:expr) => {
409        // This arm should be unreachable because @assert_not_both triggers first, but keep it
410        // as a backstop if the macro is used incorrectly.
411        compile_error!(
412            "impl_property!: cannot select initialization kind because both `compute_derived_fn` \
413             and `default_const` are present"
414        )
415    };
416    (@select_initialization_kind $compute_derived_fn:expr ; ) => {
417        $crate::entity::property::PropertyInitializationKind::Derived
418    };
419    (@select_initialization_kind ; $default_const:expr) => {
420        $crate::entity::property::PropertyInitializationKind::Constant
421    };
422    (@select_initialization_kind ; ) => {
423        $crate::entity::property::PropertyInitializationKind::Explicit
424    };
425
426    // Helpers for defaults, a pair per macro parameter type (`expr`, `ty`).
427    (@unwrap_or $value:expr, $_default:expr) => { $value };
428    (@unwrap_or, $default:expr) => { $default };
429
430    (@unwrap_or_ty $ty:ty, $_default:ty) => { $ty };
431    (@unwrap_or_ty, $default:ty) => { $default };
432
433    // This is the purely syntactic implementation.
434    (
435        @__impl_property_common
436        $property:ident,           // The name of the type we are implementing `Property` for
437        $entity:ident,             // The entity type this property is associated with
438        $canonical_value:ty,       // If the type stored in the index is different from Self, the name of that type
439        $initialization_kind:expr, // The kind of initialization this property has (implicit selection)
440        $compute_derived_fn:expr,  // If the property is derived, the function that computes the value
441        $default_const:expr,       // If the property has a constant default initial value, the default value
442        $make_canonical:expr,      // A function that takes a value and returns a canonical value
443        $make_uncanonical:expr,    // A function that takes a canonical value and returns a value
444        $display_impl:expr,        // A function that takes a canonical value and returns a string representation of this property
445        $index_id_fn:expr,         // Code that returns the unique index for this property
446        $collect_deps_fn:expr,     // If the property is derived, the function that computes the value
447        $ctor_registration:expr,   // Code that runs in a ctor for property registration
448    ) => {
449        impl $crate::entity::property::Property<$entity> for $property {
450            type CanonicalValue = $canonical_value;
451
452            fn initialization_kind() -> $crate::entity::property::PropertyInitializationKind {
453                $initialization_kind
454            }
455
456            fn compute_derived(
457                _context: &$crate::Context,
458                _entity_id: $crate::entity::EntityId<$entity>,
459            ) -> Self {
460                ($compute_derived_fn)(_context, _entity_id)
461            }
462
463            fn default_const() -> Self {
464                $default_const
465            }
466
467            fn make_canonical(self) -> Self::CanonicalValue {
468                ($make_canonical)(self)
469            }
470
471            fn make_uncanonical(value: Self::CanonicalValue) -> Self {
472                ($make_uncanonical)(value)
473            }
474
475            fn get_display(&self) -> String {
476                ($display_impl)(self)
477            }
478
479            fn id() -> usize {
480                // This static must be initialized with a compile-time constant expression.
481                // We use `usize::MAX` as a sentinel to mean "uninitialized". This
482                // static variable is shared among all instances of this concrete item type.
483                static INDEX: std::sync::atomic::AtomicUsize =
484                    std::sync::atomic::AtomicUsize::new(usize::MAX);
485
486                // Fast path: already initialized.
487                let index = INDEX.load(std::sync::atomic::Ordering::Relaxed);
488                if index != usize::MAX {
489                    return index;
490                }
491
492                // Slow path: initialize it.
493                $crate::entity::property_store::initialize_property_id::<$entity>(&INDEX)
494            }
495
496            fn index_id() -> usize {
497                $index_id_fn
498            }
499
500            fn collect_non_derived_dependencies(result: &mut $crate::HashSet<usize>) {
501                ($collect_deps_fn)(result)
502            }
503        }
504
505        $crate::paste::paste! {
506            $crate::ctor::declarative::ctor!{
507                #[ctor]
508                fn [<_register_property_ $entity:snake _ $property:snake>]() {
509                    $ctor_registration
510                }
511            }
512        }
513    };
514}
515
516/// The "derived" variant of [`define_property!`][macro@crate::define_property] for defining simple derived property types.
517/// Defines a `struct` or `enum` with a standard set of derives and automatically invokes
518/// [`impl_derived_property!`][macro@crate::impl_derived_property] for it.
519///
520/// Defines a derived property with the following parameters:
521/// * Property type declaration: A struct or enum declaration.
522/// * `$entity`: The name of the entity of which the new type is a property.
523/// * `[$($dependency),+]`: A list of person properties the derived property depends on.
524/// * `[$(global_dependency),*]`: A list of global properties the derived property depends on. Can optionally be omitted if empty.
525/// * `$calculate`: A closure that takes the values of each dependency and returns the derived value.
526/// * Optional parameters: The same optional parameters accepted by [`impl_property!`][macro@crate::impl_property].
527#[macro_export]
528macro_rules! define_derived_property {
529    // The calls to `$crate::impl_derived_property!` are all the same except for
530    // this first case of a newtype for an `Option<T>`, which has a special `display_impl`.
531
532    // Struct (tuple) with single Option<T> field (special case)
533    (
534        struct $name:ident ( $visibility:vis Option<$inner_ty:ty> ),
535        $entity:ident,
536        [$($dependency:ident),*]
537        $(, [$($global_dependency:ident),*])?,
538        |$($param:ident),+| $derive_fn:expr
539        // For `canonical_value` implementations:
540        $(, $($extra:tt)+),*
541    ) => {
542        #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize)]
543        pub struct $name(pub Option<$inner_ty>);
544
545        // Use impl_derived_property! to provide a custom display implementation
546        $crate::impl_derived_property!(
547            $name,
548            $entity,
549            [$($dependency),*],
550            [$($($global_dependency),*)?],
551            |$($param),+| $derive_fn,
552            display_impl = |value: &Option<$inner_ty>| {
553                match value {
554                    Some(v) => format!("{:?}", v),
555                    None => "None".to_string(),
556                }
557            }
558            $(, $($extra)+)*
559        );
560    };
561
562    // Struct (tuple)
563    (
564        struct $name:ident ( $($visibility:vis $field_ty:ty),* $(,)? ),
565        $entity:ident,
566        [$($dependency:ident),*]
567        $(, [$($global_dependency:ident),*])?,
568        |$($param:ident),+| $derive_fn:expr
569        // For `canonical_value` implementations:
570        $(, $($extra:tt)+),*
571    ) => {
572        #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize)]
573        pub struct $name( $(pub $field_ty),* );
574
575        $crate::impl_derived_property!(
576            $name,
577            $entity,
578            [$($dependency),*],
579            [$($($global_dependency),*)?],
580            |$($param),+| $derive_fn
581            $(, $($extra)+)*
582        );
583    };
584
585    // Struct (named fields)
586    (
587        struct $name:ident { $($visibility:vis $field_name:ident : $field_ty:ty),* $(,)? },
588        $entity:ident,
589        [$($dependency:ident),*]
590        $(, [$($global_dependency:ident),*])?,
591        |$($param:ident),+| $derive_fn:expr
592        // For `canonical_value` implementations:
593        $(, $($extra:tt)+),*
594    ) => {
595        #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize)]
596        pub struct $name { $($visibility $field_name : $field_ty),* }
597
598        $crate::impl_derived_property!(
599            $name,
600            $entity,
601            [$($dependency),*],
602            [$($($global_dependency),*)?],
603            |$($param),+| $derive_fn
604            $(, $($extra)+)*
605        );
606    };
607
608    // Enum
609    (
610        enum $name:ident {
611            $($variant:ident),* $(,)?
612        },
613        $entity:ident,
614        [$($dependency:ident),*]
615        $(, [$($global_dependency:ident),*])?,
616        |$($param:ident),+| $derive_fn:expr
617        // For `canonical_value` implementations:
618        $(, $($extra:tt)+),*
619    ) => {
620        #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize)]
621        pub enum $name {
622            $($variant),*
623        }
624
625        $crate::impl_derived_property!(
626            $name,
627            $entity,
628            [$($dependency),*],
629            [$($($global_dependency),*)?],
630            |$($param),+| $derive_fn
631            $(, $($extra)+)*
632        );
633    };
634
635    // Internal branch to construct the compute function.
636    (
637        @construct_compute_fn
638        $entity:ident,
639        [$($dependency:ident),*],
640        [$($global_dependency:ident),*],
641        |$($param:ident),+| $derive_fn:expr
642    ) => {
643        |context: &$crate::Context, entity_id| {
644            #[allow(unused_imports)]
645            use $crate::global_properties::ContextGlobalPropertiesExt;
646            #[allow(unused_parens)]
647            let ($($param,)*) = (
648                $(context.get_property::<$entity, $dependency>(entity_id)),*,
649                $(
650                    context.get_global_property_value($global_dependency)
651                        .expect(&format!("Global property {} not initialized", stringify!($global_dependency)))
652                ),*
653            );
654            $derive_fn
655        }
656    };
657}
658
659/// Implements the [`Property`][crate::entity::property::Property] trait for an existing type as a derived property.
660///
661/// Accepts the same parameters as [`define_derived_property!`][macro@crate::define_derived_property], except the first parameter is the name of a
662/// type assumed to already be declared rather than a type declaration. This is the derived property equivalent
663/// of [`impl_property!`][macro@crate::impl_property]. It calls [`impl_property!`][macro@crate::impl_property] with the appropriate derived property parameters.
664#[macro_export]
665macro_rules! impl_derived_property {
666        (
667            $name:ident,
668            $entity:ident,
669            [$($dependency:ident),*]
670            $(, [$($global_dependency:ident),*])?,
671            |$($param:ident),+| $derive_fn:expr
672            $(, $($extra:tt)+)*
673        ) => {
674            $crate::impl_property!(
675                $name,
676                $entity,
677                compute_derived_fn = $crate::impl_derived_property!(
678                    @construct_compute_fn
679                    $entity,
680                    [$($dependency),*],
681                    [$($($global_dependency),*)?],
682                    |$($param),+| $derive_fn
683                ),
684                collect_deps_fn = | deps: &mut $crate::HashSet<usize> | {
685                    $(
686                        if $dependency::is_derived() {
687                            $dependency::collect_non_derived_dependencies(deps);
688                        } else {
689                            deps.insert($dependency::id());
690                        }
691                    )*
692                }
693                $(, $($extra)+)*
694            );
695        };
696
697        // Internal branch to construct the compute function.
698        (
699            @construct_compute_fn
700            $entity:ident,
701            [$($dependency:ident),*],
702            [$($global_dependency:ident),*],
703            |$($param:ident),+| $derive_fn:expr
704        ) => {
705            |context: &$crate::Context, entity_id| {
706                #[allow(unused_imports)]
707                use $crate::global_properties::ContextGlobalPropertiesExt;
708                #[allow(unused_parens)]
709                let ($($param,)*) = (
710                    $(context.get_property::<$entity, $dependency>(entity_id)),*,
711                    $(
712                        context.get_global_property_value($global_dependency)
713                            .expect(&format!("Global property {} not initialized", stringify!($global_dependency)))
714                    ),*
715                );
716                $derive_fn
717            }
718        };
719    }
720
721/// Defines a derived property consisting of a (named) tuple of other properties. The primary use case
722/// is for indexing and querying properties jointly.
723///
724/// The index subsystem is smart enough to reuse indexes for multi-properties that are equivalent up to
725/// reordering of the component properties. The querying subsystem is able to detect when its multiple
726/// component properties are equivalent to an indexed multi-property and use that index to perform the
727/// query.
728#[macro_export]
729macro_rules! define_multi_property {
730        (
731            ( $($dependency:ident),+ ),
732            $entity:ident
733        ) => {
734            $crate::paste::paste! {
735                type [<$($dependency)*>] = ( $($dependency),+ );
736
737                $crate::impl_property!(
738                    [<$($dependency)*>],
739                    $entity,
740                    compute_derived_fn = |context: &$crate::Context, entity_id: $crate::entity::EntityId<$entity>| {
741                        (
742                            $(context.get_property::<$entity, $dependency>(entity_id)),+
743                        )
744                    },
745                    display_impl = |val: &( $($dependency),+ )| {
746                        let ( $( [<_ $dependency:lower>] ),+ ) = val;
747                        let mut displayed = String::from("(");
748                        $(
749                            displayed.push_str(
750                                &<$dependency as $crate::entity::property::Property<$entity>>::get_display([<_ $dependency:lower>])
751                            );
752                            displayed.push_str(", ");
753                        )+
754                        displayed.truncate(displayed.len() - 2);
755                        displayed.push_str(")");
756                        displayed
757                    },
758                    canonical_value = $crate::sorted_tag!(( $($dependency),+ )),
759                    make_canonical = $crate::reorder_closure!(( $($dependency),+ )),
760                    make_uncanonical = $crate::unreorder_closure!(( $($dependency),+ )),
761
762                    index_id_fn = {
763                        // This static must be initialized with a compile-time constant expression.
764                        // We use `usize::MAX` as a sentinel to mean "uninitialized". This
765                        // static variable is shared among all instances of this concrete item type.
766                        static INDEX_ID: std::sync::atomic::AtomicUsize =
767                            std::sync::atomic::AtomicUsize::new(usize::MAX);
768
769                        // Fast path: already initialized.
770                        let index_id = INDEX_ID.load(std::sync::atomic::Ordering::Relaxed);
771                        if index_id != usize::MAX {
772                            return index_id;
773                        }
774
775                        // Slow path: initialize it.
776                        // Multi-properties report a single index ID for all equivalent multi-properties,
777                        // because they share a single `Index<E, P>` instance.
778                        let mut type_ids = [$( <$dependency as $crate::entity::property::Property<$entity>>::type_id() ),+];
779                        type_ids.sort_unstable();
780                        // Check if an index has already been assigned to this property set.
781                        match $crate::entity::multi_property::type_ids_to_multi_property_index(&type_ids) {
782                            Some(index) => {
783                                // An index exists. Reuse it for our own index.
784                                INDEX_ID.store(index, std::sync::atomic::Ordering::Relaxed);
785                                index
786                            },
787                            None => {
788                                // An index ID is not yet assigned. We will use our own index for this property.
789                                let index = <Self as $crate::entity::property::Property<$entity>>::id();
790                                INDEX_ID.store(index, std::sync::atomic::Ordering::Relaxed);
791                                // And register the new index with this property set.
792                                $crate::entity::multi_property::register_type_ids_to_multi_property_index(
793                                    &type_ids,
794                                    index
795                                );
796                                index
797                            }
798                        }
799                    },
800
801                    collect_deps_fn = | deps: &mut $crate::HashSet<usize> | {
802                        $(
803                            if $dependency::is_derived() {
804                                $dependency::collect_non_derived_dependencies(deps);
805                            } else {
806                                deps.insert($dependency::id());
807                            }
808                        )*
809                    },
810
811                    ctor_registration = {
812                        // Ensure `Self::index_id()` is initialized at startup.
813                        let _ = [<$($dependency)*>]::index_id();
814                        $crate::entity::property_store::add_to_property_registry::<$entity, [<$($dependency)*>]>();
815                    }
816                );
817
818            }
819        };
820    }
821
822#[cfg(test)]
823mod tests {
824    // We define unused properties to test macro implementation.
825    #![allow(dead_code)]
826
827    use serde::Serialize;
828
829    use crate::entity::{PropertyIndexType, Query};
830    use crate::prelude::*;
831
832    define_entity!(Person);
833    define_entity!(Group);
834
835    define_property!(struct Pu32(u32), Person, default_const = Pu32(0));
836    define_property!(struct POu32(Option<u32>), Person, default_const = POu32(None));
837    define_property!(struct Name(&'static str), Person, default_const = Name(""));
838    define_property!(struct Age(u8), Person, default_const = Age(0));
839    define_property!(struct Weight(f64), Person, default_const = Weight(0.0));
840
841    // A struct with named fields
842    define_property!(
843        struct Innocculation {
844            time: f64,
845            dose: u8,
846        },
847        Person,
848        default_const = Innocculation { time: 0.0, dose: 0 }
849    );
850
851    // An enum non-derived property
852    define_property!(
853        enum InfectionStatus {
854            Susceptible,
855            Infected,
856            Recovered,
857        },
858        Person,
859        default_const = InfectionStatus::Susceptible
860    );
861
862    // An enum derived property
863    define_derived_property!(
864        enum AgeGroup {
865            Child,
866            Adult,
867            Senior,
868        },
869        Person,
870        [Age], // Depends only on age
871        |age| {
872            let age: Age = age;
873            if age.0 < 18 {
874                AgeGroup::Child
875            } else if age.0 < 65 {
876                AgeGroup::Adult
877            } else {
878                AgeGroup::Senior
879            }
880        }
881    );
882
883    // Derived property - computed from other properties
884    define_derived_property!(struct DerivedProp(bool), Person, [Age],
885        |age| {
886            DerivedProp(age.0 % 2 == 0)
887        }
888    );
889
890    // A property type for two distinct entities.
891    #[derive(Debug, PartialEq, Clone, Copy, Serialize)]
892    pub enum InfectionKind {
893        Respiratory,
894        Genetic,
895        Superficial,
896    }
897    impl_property!(
898        InfectionKind,
899        Person,
900        default_const = InfectionKind::Respiratory
901    );
902    impl_property!(InfectionKind, Group, default_const = InfectionKind::Genetic);
903
904    define_multi_property!((Name, Age, Weight), Person);
905    define_multi_property!((Age, Weight, Name), Person);
906    define_multi_property!((Weight, Age, Name), Person);
907
908    // For convenience
909    type ProfileNAW = (Name, Age, Weight);
910    type ProfileAWN = (Age, Weight, Name);
911    type ProfileWAN = (Weight, Age, Name);
912
913    #[test]
914    fn test_multi_property_ordering() {
915        let a = (Name("Jane"), Age(22), Weight(180.5));
916        let b = (Age(22), Weight(180.5), Name("Jane"));
917        let c = (Weight(180.5), Age(22), Name("Jane"));
918
919        // Multi-properties share the same index
920        assert_eq!(ProfileNAW::index_id(), ProfileAWN::index_id());
921        assert_eq!(ProfileNAW::index_id(), ProfileWAN::index_id());
922
923        let a_canonical: <ProfileNAW as Property<_>>::CanonicalValue =
924            ProfileNAW::make_canonical(a);
925        let b_canonical: <ProfileAWN as Property<_>>::CanonicalValue =
926            ProfileAWN::make_canonical(b);
927        let c_canonical: <ProfileWAN as Property<_>>::CanonicalValue =
928            ProfileWAN::make_canonical(c);
929
930        assert_eq!(a_canonical, b_canonical);
931        assert_eq!(a_canonical, c_canonical);
932
933        // Actually, all of the `Profile***::hash_property_value` methods should be the same,
934        // so we could use any single one.
935        assert_eq!(
936            ProfileNAW::hash_property_value(&a_canonical),
937            ProfileAWN::hash_property_value(&b_canonical)
938        );
939        assert_eq!(
940            ProfileNAW::hash_property_value(&a_canonical),
941            ProfileWAN::hash_property_value(&c_canonical)
942        );
943
944        // Since the canonical values are the same, we could have used any single one, but this
945        // demonstrates that we can convert from one order to another.
946        assert_eq!(ProfileNAW::make_uncanonical(b_canonical), a);
947        assert_eq!(ProfileAWN::make_uncanonical(c_canonical), b);
948        assert_eq!(ProfileWAN::make_uncanonical(a_canonical), c);
949    }
950
951    #[test]
952    fn test_multi_property_vs_property_query() {
953        let mut context = Context::new();
954
955        context
956            .add_entity((Name("John"), Age(42), Weight(220.5)))
957            .unwrap();
958        context
959            .add_entity((Name("Jane"), Age(22), Weight(180.5)))
960            .unwrap();
961        context
962            .add_entity((Name("Bob"), Age(32), Weight(190.5)))
963            .unwrap();
964        context
965            .add_entity((Name("Alice"), Age(22), Weight(170.5)))
966            .unwrap();
967
968        context.index_property::<_, ProfileNAW>();
969
970        // Check that all equivalent multi-properties are indexed...
971        assert!(context.is_property_indexed::<Person, ProfileNAW>());
972        assert!(context.is_property_indexed::<Person, ProfileAWN>());
973        assert!(context.is_property_indexed::<Person, ProfileWAN>());
974        // ...but only one `Index<E, P>` instance was created.
975        let mut indexed_count = 0;
976        if context
977            .get_property_value_store::<Person, ProfileNAW>()
978            .index_type()
979            != PropertyIndexType::Unindexed
980        {
981            indexed_count += 1;
982        }
983        if context
984            .get_property_value_store::<Person, ProfileAWN>()
985            .index_type()
986            != PropertyIndexType::Unindexed
987        {
988            indexed_count += 1;
989        }
990        if context
991            .get_property_value_store::<Person, ProfileWAN>()
992            .index_type()
993            != PropertyIndexType::Unindexed
994        {
995            indexed_count += 1;
996        }
997        assert_eq!(indexed_count, 1);
998
999        {
1000            let example_query = (Name("Alice"), Age(22), Weight(170.5));
1001            let query_multi_property_id =
1002                <(Name, Age, Weight) as Query<Person>>::multi_property_id(&example_query);
1003            assert!(query_multi_property_id.is_some());
1004            assert_eq!(ProfileNAW::index_id(), query_multi_property_id.unwrap());
1005            assert_eq!(
1006                Query::multi_property_value_hash(&example_query),
1007                ProfileNAW::hash_property_value(
1008                    &(Name("Alice"), Age(22), Weight(170.5)).make_canonical()
1009                )
1010            );
1011        }
1012
1013        context.with_query_results(((Name("John"), Age(42), Weight(220.5)),), &mut |results| {
1014            assert_eq!(results.len(), 1);
1015        });
1016    }
1017
1018    #[test]
1019    fn test_derived_property() {
1020        let mut context = Context::new();
1021
1022        let senior = context.add_entity::<Person, _>((Age(92),)).unwrap();
1023        let child = context.add_entity::<Person, _>((Age(12),)).unwrap();
1024        let adult = context.add_entity::<Person, _>((Age(44),)).unwrap();
1025
1026        let senior_group: AgeGroup = context.get_property(senior);
1027        let child_group: AgeGroup = context.get_property(child);
1028        let adult_group: AgeGroup = context.get_property(adult);
1029
1030        assert_eq!(senior_group, AgeGroup::Senior);
1031        assert_eq!(child_group, AgeGroup::Child);
1032        assert_eq!(adult_group, AgeGroup::Adult);
1033
1034        // Age has no dependencies (only dependents)
1035        assert!(Age::non_derived_dependencies().is_empty());
1036        // AgeGroup depends only on Age
1037        assert_eq!(AgeGroup::non_derived_dependencies(), [Age::id()]);
1038
1039        // Age has several dependents. This assert may break if you add or remove the properties in this test module.
1040        let mut expected_dependents = [
1041            AgeGroup::id(),
1042            DerivedProp::id(),
1043            ProfileNAW::id(),
1044            ProfileAWN::id(),
1045            ProfileWAN::id(),
1046        ];
1047        expected_dependents.sort_unstable();
1048        assert_eq!(Age::dependents(), expected_dependents);
1049    }
1050
1051    #[test]
1052    fn test_get_display() {
1053        let mut context = Context::new();
1054        let person = context.add_entity((POu32(Some(42)), Pu32(22))).unwrap();
1055        assert_eq!(
1056            format!(
1057                "{:}",
1058                POu32::get_display(&context.get_property::<_, POu32>(person))
1059            ),
1060            "42"
1061        );
1062        assert_eq!(
1063            format!(
1064                "{:}",
1065                Pu32::get_display(&context.get_property::<_, Pu32>(person))
1066            ),
1067            "Pu32(22)"
1068        );
1069        let person2 = context.add_entity((POu32(None), Pu32(11))).unwrap();
1070        assert_eq!(
1071            format!(
1072                "{:}",
1073                POu32::get_display(&context.get_property::<_, POu32>(person2))
1074            ),
1075            "None"
1076        );
1077    }
1078
1079    #[test]
1080    fn test_debug_trait() {
1081        let property = Pu32(11);
1082        let debug_str = format!("{:?}", property);
1083        assert_eq!(debug_str, "Pu32(11)");
1084
1085        let property = POu32(Some(22));
1086        let debug_str = format!("{:?}", property);
1087        assert_eq!(debug_str, "POu32(Some(22))");
1088    }
1089}