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}