#[macro_export]
macro_rules! define_property {
(
struct $name:ident ( $visibility:vis Option<$inner_ty:ty> ),
$entity:ident
$(, $($extra:tt)+),*
) => {
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)]
pub struct $name(pub Option<$inner_ty>);
$crate::impl_property!(
$name,
$entity
$(, $($extra)+)*
, display_impl = |value: &Self| {
match value.0 {
Some(v) => format!("{:?}", v),
None => "None".to_string(),
}
}
);
};
(
struct $name:ident ( $($visibility:vis $field_ty:ty),* $(,)? ),
$entity:ident
$(, $($extra:tt)+),*
) => {
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)]
pub struct $name($(pub $field_ty),*);
$crate::impl_property!($name, $entity $(, $($extra)+)*);
};
(
struct $name:ident { $($visibility:vis $field_name:ident : $field_ty:ty),* $(,)? },
$entity:ident
$(, $($extra:tt)+),*
) => {
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)]
pub struct $name { $(pub $field_name : $field_ty),* }
$crate::impl_property!($name, $entity $(, $($extra)+)*);
};
(
enum $name:ident {
$($variant:ident),* $(,)?
},
$entity:ident
$(, $($extra:tt)+),*
) => {
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)]
pub enum $name {
$($variant),*
}
$crate::impl_property!($name, $entity $(, $($extra)+)*);
};
}
#[macro_export]
macro_rules! impl_property {
(
$property:ident,
$entity:ident
$(, compute_derived_fn = $compute_derived_fn:expr)?
$(, default_const = $default_const:expr)?
$(, display_impl = $display_impl:expr)?
$(, canonical_value = $canonical_value:ty)?
$(, make_canonical = $make_canonical:expr)?
$(, make_uncanonical = $make_uncanonical:expr)?
$(, index_id_fn = $index_id_fn:expr)?
$(, collect_deps_fn = $collect_deps_fn:expr)?
$(, ctor_registration = $ctor_registration:expr)?
) => {
$crate::impl_property!(@assert_not_both $($compute_derived_fn)? ; $($default_const)?);
$crate::impl_property!(
@__impl_property_common
$property,
$entity,
$crate::impl_property!(@unwrap_or_ty $($canonical_value)?, $property),
$crate::impl_property!(@select_initialization_kind $($compute_derived_fn)? ; $($default_const)?),
$crate::impl_property!(
@unwrap_or
$($compute_derived_fn)?,
|_, _| panic!("property {} is not derived", stringify!($property))
),
$crate::impl_property!(
@unwrap_or
$($default_const)?,
panic!("property {} has no default value", stringify!($property))
),
$crate::impl_property!(@unwrap_or $($make_canonical)?, std::convert::identity),
$crate::impl_property!(@unwrap_or $($make_uncanonical)?, std::convert::identity),
$crate::impl_property!(@unwrap_or $($display_impl)?, |v| format!("{v:?}")),
$crate::impl_property!(@unwrap_or $($index_id_fn)?, {
<Self as $crate::entity::property::Property<$entity>>::id()
}),
$crate::impl_property!(
@unwrap_or
$($collect_deps_fn)?,
|_| {}
),
$crate::impl_property!(@unwrap_or $($ctor_registration)?, {
$crate::entity::property_store::add_to_property_registry::<$entity, $property>();
}),
);
};
(@assert_not_both $compute_derived_fn:expr ; $default_const:expr) => {
compile_error!(
"impl_property!: `compute_derived_fn = ...` (derived property) and `default_const = ...` \
(non-derived property default constant) are mutually exclusive. Remove one of them."
);
};
(@assert_not_both $compute_derived_fn:expr ; ) => {};
(@assert_not_both ; $default_const:expr) => {};
(@assert_not_both ; ) => {};
(@select_initialization_kind $compute_derived_fn:expr ; $default_const:expr) => {
compile_error!(
"impl_property!: cannot select initialization kind because both `compute_derived_fn` \
and `default_const` are present"
)
};
(@select_initialization_kind $compute_derived_fn:expr ; ) => {
$crate::entity::property::PropertyInitializationKind::Derived
};
(@select_initialization_kind ; $default_const:expr) => {
$crate::entity::property::PropertyInitializationKind::Constant
};
(@select_initialization_kind ; ) => {
$crate::entity::property::PropertyInitializationKind::Explicit
};
(@unwrap_or $value:expr, $_default:expr) => { $value };
(@unwrap_or, $default:expr) => { $default };
(@unwrap_or_ty $ty:ty, $_default:ty) => { $ty };
(@unwrap_or_ty, $default:ty) => { $default };
(
@__impl_property_common
$property:ident, // The name of the type we are implementing `Property` for
$entity:ident, // The entity type this property is associated with
$canonical_value:ty, // If the type stored in the index is different from Self, the name of that type
$initialization_kind:expr, // The kind of initialization this property has (implicit selection)
$compute_derived_fn:expr, $default_const:expr, $make_canonical:expr, $make_uncanonical:expr, $display_impl:expr, $index_id_fn:expr, $collect_deps_fn:expr, $ctor_registration:expr, ) => {
impl $crate::entity::property::Property<$entity> for $property {
type CanonicalValue = $canonical_value;
fn initialization_kind() -> $crate::entity::property::PropertyInitializationKind {
$initialization_kind
}
fn compute_derived(
_context: &$crate::Context,
_entity_id: $crate::entity::EntityId<$entity>,
) -> Self {
($compute_derived_fn)(_context, _entity_id)
}
fn default_const() -> Self {
$default_const
}
fn make_canonical(self) -> Self::CanonicalValue {
($make_canonical)(self)
}
fn make_uncanonical(value: Self::CanonicalValue) -> Self {
($make_uncanonical)(value)
}
fn get_display(&self) -> String {
($display_impl)(self)
}
fn id() -> usize {
static INDEX: std::sync::atomic::AtomicUsize =
std::sync::atomic::AtomicUsize::new(usize::MAX);
let index = INDEX.load(std::sync::atomic::Ordering::Relaxed);
if index != usize::MAX {
return index;
}
$crate::entity::property_store::initialize_property_id::<$entity>(&INDEX)
}
fn index_id() -> usize {
$index_id_fn
}
fn collect_non_derived_dependencies(result: &mut $crate::HashSet<usize>) {
($collect_deps_fn)(result)
}
}
$crate::paste::paste! {
$crate::ctor::declarative::ctor!{
#[ctor]
fn [<_register_property_ $entity:snake _ $property:snake>]() {
$ctor_registration
}
}
}
};
}
#[macro_export]
macro_rules! define_derived_property {
(
struct $name:ident ( $visibility:vis Option<$inner_ty:ty> ),
$entity:ident,
[$($dependency:ident),*]
$(, [$($global_dependency:ident),*])?,
|$($param:ident),+| $derive_fn:expr
$(, $($extra:tt)+),*
) => {
#[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize)]
pub struct $name(pub Option<$inner_ty>);
$crate::impl_derived_property!(
$name,
$entity,
[$($dependency),*],
[$($($global_dependency),*)?],
|$($param),+| $derive_fn,
display_impl = |value: &Option<$inner_ty>| {
match value {
Some(v) => format!("{:?}", v),
None => "None".to_string(),
}
}
$(, $($extra)+)*
);
};
(
struct $name:ident ( $($visibility:vis $field_ty:ty),* $(,)? ),
$entity:ident,
[$($dependency:ident),*]
$(, [$($global_dependency:ident),*])?,
|$($param:ident),+| $derive_fn:expr
$(, $($extra:tt)+),*
) => {
#[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize)]
pub struct $name( $(pub $field_ty),* );
$crate::impl_derived_property!(
$name,
$entity,
[$($dependency),*],
[$($($global_dependency),*)?],
|$($param),+| $derive_fn
$(, $($extra)+)*
);
};
(
struct $name:ident { $($visibility:vis $field_name:ident : $field_ty:ty),* $(,)? },
$entity:ident,
[$($dependency:ident),*]
$(, [$($global_dependency:ident),*])?,
|$($param:ident),+| $derive_fn:expr
$(, $($extra:tt)+),*
) => {
#[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize)]
pub struct $name { $($visibility $field_name : $field_ty),* }
$crate::impl_derived_property!(
$name,
$entity,
[$($dependency),*],
[$($($global_dependency),*)?],
|$($param),+| $derive_fn
$(, $($extra)+)*
);
};
(
enum $name:ident {
$($variant:ident),* $(,)?
},
$entity:ident,
[$($dependency:ident),*]
$(, [$($global_dependency:ident),*])?,
|$($param:ident),+| $derive_fn:expr
$(, $($extra:tt)+),*
) => {
#[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize)]
pub enum $name {
$($variant),*
}
$crate::impl_derived_property!(
$name,
$entity,
[$($dependency),*],
[$($($global_dependency),*)?],
|$($param),+| $derive_fn
$(, $($extra)+)*
);
};
(
@construct_compute_fn
$entity:ident,
[$($dependency:ident),*],
[$($global_dependency:ident),*],
|$($param:ident),+| $derive_fn:expr
) => {
|context: &$crate::Context, entity_id| {
#[allow(unused_imports)]
use $crate::global_properties::ContextGlobalPropertiesExt;
#[allow(unused_parens)]
let ($($param,)*) = (
$(context.get_property::<$entity, $dependency>(entity_id)),*,
$(
context.get_global_property_value($global_dependency)
.expect(&format!("Global property {} not initialized", stringify!($global_dependency)))
),*
);
$derive_fn
}
};
}
#[macro_export]
macro_rules! impl_derived_property {
(
$name:ident,
$entity:ident,
[$($dependency:ident),*]
$(, [$($global_dependency:ident),*])?,
|$($param:ident),+| $derive_fn:expr
$(, $($extra:tt)+)*
) => {
$crate::impl_property!(
$name,
$entity,
compute_derived_fn = $crate::impl_derived_property!(
@construct_compute_fn
$entity,
[$($dependency),*],
[$($($global_dependency),*)?],
|$($param),+| $derive_fn
),
collect_deps_fn = | deps: &mut $crate::HashSet<usize> | {
$(
if <$dependency as $crate::entity::property::Property<$entity>>::is_derived() {
<$dependency as $crate::entity::property::Property<$entity>>::collect_non_derived_dependencies(deps);
} else {
deps.insert(<$dependency as $crate::entity::property::Property<$entity>>::id());
}
)*
}
$(, $($extra)+)*
);
};
(
@construct_compute_fn
$entity:ident,
[$($dependency:ident),*],
[$($global_dependency:ident),*],
|$($param:ident),+| $derive_fn:expr
) => {
|context: &$crate::Context, entity_id| {
#[allow(unused_imports)]
use $crate::global_properties::ContextGlobalPropertiesExt;
#[allow(unused_parens)]
let ($($param,)*) = (
$(context.get_property::<$entity, $dependency>(entity_id)),*,
$(
context.get_global_property_value($global_dependency)
.expect(&format!("Global property {} not initialized", stringify!($global_dependency)))
),*
);
$derive_fn
}
};
}
#[macro_export]
macro_rules! define_multi_property {
(
( $($dependency:ident),+ ),
$entity:ident
) => {
$crate::paste::paste! {
type [<$($dependency)*>] = ( $($dependency),+ );
$crate::impl_property!(
[<$($dependency)*>],
$entity,
compute_derived_fn = |context: &$crate::Context, entity_id: $crate::entity::EntityId<$entity>| {
(
$(context.get_property::<$entity, $dependency>(entity_id)),+
)
},
display_impl = |val: &( $($dependency),+ )| {
let ( $( [<_ $dependency:lower>] ),+ ) = val;
let mut displayed = String::from("(");
$(
displayed.push_str(
&<$dependency as $crate::entity::property::Property<$entity>>::get_display([<_ $dependency:lower>])
);
displayed.push_str(", ");
)+
displayed.truncate(displayed.len() - 2);
displayed.push_str(")");
displayed
},
canonical_value = $crate::sorted_tag!(( $($dependency),+ )),
make_canonical = $crate::reorder_closure!(( $($dependency),+ )),
make_uncanonical = $crate::unreorder_closure!(( $($dependency),+ )),
index_id_fn = {
static INDEX_ID: std::sync::atomic::AtomicUsize =
std::sync::atomic::AtomicUsize::new(usize::MAX);
let index_id = INDEX_ID.load(std::sync::atomic::Ordering::Relaxed);
if index_id != usize::MAX {
return index_id;
}
let mut type_ids = [$( <$dependency as $crate::entity::property::Property<$entity>>::type_id() ),+];
type_ids.sort_unstable();
match $crate::entity::multi_property::type_ids_to_multi_property_index(&type_ids) {
Some(index) => {
INDEX_ID.store(index, std::sync::atomic::Ordering::Relaxed);
index
},
None => {
let index = <Self as $crate::entity::property::Property<$entity>>::id();
INDEX_ID.store(index, std::sync::atomic::Ordering::Relaxed);
$crate::entity::multi_property::register_type_ids_to_multi_property_index(
&type_ids,
index
);
index
}
}
},
collect_deps_fn = | deps: &mut $crate::HashSet<usize> | {
$(
if <$dependency as $crate::entity::property::Property<$entity>>::is_derived() {
<$dependency as $crate::entity::property::Property<$entity>>::collect_non_derived_dependencies(deps);
} else {
deps.insert(<$dependency as $crate::entity::property::Property<$entity>>::id());
}
)*
},
ctor_registration = {
let _ = < [<$($dependency)*>] as $crate::entity::property::Property::<$entity> >::index_id();
$crate::entity::property_store::add_to_property_registry::<$entity, [<$($dependency)*>]>();
}
);
}
};
}
#[cfg(test)]
mod tests {
#![allow(dead_code)]
use serde::Serialize;
use crate::entity::{PropertyIndexType, Query};
use crate::prelude::*;
define_entity!(Person);
define_entity!(Group);
define_property!(struct Pu32(u32), Person, default_const = Pu32(0));
define_property!(struct POu32(Option<u32>), Person, default_const = POu32(None));
define_property!(struct Name(&'static str), Person, default_const = Name(""));
define_property!(struct Age(u8), Person, default_const = Age(0));
define_property!(struct Weight(f64), Person, default_const = Weight(0.0));
define_property!(
struct Innocculation {
time: f64,
dose: u8,
},
Person,
default_const = Innocculation { time: 0.0, dose: 0 }
);
define_property!(
enum InfectionStatus {
Susceptible,
Infected,
Recovered,
},
Person,
default_const = InfectionStatus::Susceptible
);
define_derived_property!(
enum AgeGroup {
Child,
Adult,
Senior,
},
Person,
[Age], |age| {
let age: Age = age;
if age.0 < 18 {
AgeGroup::Child
} else if age.0 < 65 {
AgeGroup::Adult
} else {
AgeGroup::Senior
}
}
);
define_derived_property!(struct DerivedProp(bool), Person, [Age],
|age| {
DerivedProp(age.0 % 2 == 0)
}
);
#[derive(Debug, PartialEq, Clone, Copy, Serialize)]
pub enum InfectionKind {
Respiratory,
Genetic,
Superficial,
}
impl_property!(
InfectionKind,
Person,
default_const = InfectionKind::Respiratory
);
impl_property!(InfectionKind, Group, default_const = InfectionKind::Genetic);
define_multi_property!((Name, Age, Weight), Person);
define_multi_property!((Age, Weight, Name), Person);
define_multi_property!((Weight, Age, Name), Person);
type ProfileNAW = (Name, Age, Weight);
type ProfileAWN = (Age, Weight, Name);
type ProfileWAN = (Weight, Age, Name);
#[test]
fn test_multi_property_ordering() {
let a = (Name("Jane"), Age(22), Weight(180.5));
let b = (Age(22), Weight(180.5), Name("Jane"));
let c = (Weight(180.5), Age(22), Name("Jane"));
assert_eq!(ProfileNAW::index_id(), ProfileAWN::index_id());
assert_eq!(ProfileNAW::index_id(), ProfileWAN::index_id());
let a_canonical: <ProfileNAW as Property<_>>::CanonicalValue =
ProfileNAW::make_canonical(a);
let b_canonical: <ProfileAWN as Property<_>>::CanonicalValue =
ProfileAWN::make_canonical(b);
let c_canonical: <ProfileWAN as Property<_>>::CanonicalValue =
ProfileWAN::make_canonical(c);
assert_eq!(a_canonical, b_canonical);
assert_eq!(a_canonical, c_canonical);
assert_eq!(
ProfileNAW::hash_property_value(&a_canonical),
ProfileAWN::hash_property_value(&b_canonical)
);
assert_eq!(
ProfileNAW::hash_property_value(&a_canonical),
ProfileWAN::hash_property_value(&c_canonical)
);
assert_eq!(ProfileNAW::make_uncanonical(b_canonical), a);
assert_eq!(ProfileAWN::make_uncanonical(c_canonical), b);
assert_eq!(ProfileWAN::make_uncanonical(a_canonical), c);
}
#[test]
fn test_multi_property_vs_property_query() {
let mut context = Context::new();
context
.add_entity((Name("John"), Age(42), Weight(220.5)))
.unwrap();
context
.add_entity((Name("Jane"), Age(22), Weight(180.5)))
.unwrap();
context
.add_entity((Name("Bob"), Age(32), Weight(190.5)))
.unwrap();
context
.add_entity((Name("Alice"), Age(22), Weight(170.5)))
.unwrap();
context.index_property::<_, ProfileNAW>();
assert!(context.is_property_indexed::<Person, ProfileNAW>());
assert!(context.is_property_indexed::<Person, ProfileAWN>());
assert!(context.is_property_indexed::<Person, ProfileWAN>());
let mut indexed_count = 0;
if context
.get_property_value_store::<Person, ProfileNAW>()
.index_type()
!= PropertyIndexType::Unindexed
{
indexed_count += 1;
}
if context
.get_property_value_store::<Person, ProfileAWN>()
.index_type()
!= PropertyIndexType::Unindexed
{
indexed_count += 1;
}
if context
.get_property_value_store::<Person, ProfileWAN>()
.index_type()
!= PropertyIndexType::Unindexed
{
indexed_count += 1;
}
assert_eq!(indexed_count, 1);
{
let example_query = (Name("Alice"), Age(22), Weight(170.5));
let query_multi_property_id =
<(Name, Age, Weight) as Query<Person>>::multi_property_id(&example_query);
assert!(query_multi_property_id.is_some());
assert_eq!(ProfileNAW::index_id(), query_multi_property_id.unwrap());
assert_eq!(
Query::multi_property_value_hash(&example_query),
ProfileNAW::hash_property_value(
&(Name("Alice"), Age(22), Weight(170.5)).make_canonical()
)
);
}
context.with_query_results(((Name("John"), Age(42), Weight(220.5)),), &mut |results| {
assert_eq!(results.into_iter().count(), 1);
});
}
#[test]
fn test_derived_property() {
let mut context = Context::new();
let senior = context.add_entity::<Person, _>((Age(92),)).unwrap();
let child = context.add_entity::<Person, _>((Age(12),)).unwrap();
let adult = context.add_entity::<Person, _>((Age(44),)).unwrap();
let senior_group: AgeGroup = context.get_property(senior);
let child_group: AgeGroup = context.get_property(child);
let adult_group: AgeGroup = context.get_property(adult);
assert_eq!(senior_group, AgeGroup::Senior);
assert_eq!(child_group, AgeGroup::Child);
assert_eq!(adult_group, AgeGroup::Adult);
assert!(Age::non_derived_dependencies().is_empty());
assert_eq!(AgeGroup::non_derived_dependencies(), [Age::id()]);
let mut expected_dependents = [
AgeGroup::id(),
DerivedProp::id(),
ProfileNAW::id(),
ProfileAWN::id(),
ProfileWAN::id(),
];
expected_dependents.sort_unstable();
assert_eq!(Age::dependents(), expected_dependents);
}
#[test]
fn test_get_display() {
let mut context = Context::new();
let person = context.add_entity((POu32(Some(42)), Pu32(22))).unwrap();
assert_eq!(
format!(
"{:}",
POu32::get_display(&context.get_property::<_, POu32>(person))
),
"42"
);
assert_eq!(
format!(
"{:}",
Pu32::get_display(&context.get_property::<_, Pu32>(person))
),
"Pu32(22)"
);
let person2 = context.add_entity((POu32(None), Pu32(11))).unwrap();
assert_eq!(
format!(
"{:}",
POu32::get_display(&context.get_property::<_, POu32>(person2))
),
"None"
);
}
#[test]
fn test_debug_trait() {
let property = Pu32(11);
let debug_str = format!("{:?}", property);
assert_eq!(debug_str, "Pu32(11)");
let property = POu32(Some(22));
let debug_str = format!("{:?}", property);
assert_eq!(debug_str, "POu32(Some(22))");
}
}