#[macro_export]
macro_rules! define_property {
(
struct $name:ident ( $visibility:vis Option<$inner_ty:ty> ),
$entity:ident,
impl_eq_hash = $impl_eq_hash:ident
$(, $($extra:tt)*)?
) => {
$crate::define_property!(
@apply_property_decoration $impl_eq_hash,
pub struct $name(pub Option<$inner_ty>);,
$name
);
$crate::impl_property!(@with_option_display_default $name, $entity $(, $($extra)*)?);
};
(
struct $name:ident ( $visibility:vis Option<$inner_ty:ty> ),
$entity:ident
$(, $($extra:tt)*)?
) => {
$crate::define_property!(
@apply_property_decoration ,
pub struct $name(pub Option<$inner_ty>);,
$name
);
$crate::impl_property!(@with_option_display_default $name, $entity $(, $($extra)*)?);
};
(
struct $name:ident ( $($visibility:vis $field_ty:ty),* $(,)? ),
$entity:ident,
impl_eq_hash = $impl_eq_hash:ident
$(, $($extra:tt)*)?
) => {
$crate::define_property!(
@apply_property_decoration $impl_eq_hash,
pub struct $name($(pub $field_ty),*);,
$name
);
$crate::impl_property!($name, $entity $(, $($extra)*)?);
};
(
struct $name:ident ( $($visibility:vis $field_ty:ty),* $(,)? ),
$entity:ident
$(, $($extra:tt)*)?
) => {
$crate::define_property!(
@apply_property_decoration ,
pub struct $name($(pub $field_ty),*);,
$name
);
$crate::impl_property!($name, $entity $(, $($extra)*)?);
};
(
struct $name:ident { $($visibility:vis $field_name:ident : $field_ty:ty),* $(,)? },
$entity:ident,
impl_eq_hash = $impl_eq_hash:ident
$(, $($extra:tt)*)?
) => {
$crate::define_property!(
@apply_property_decoration $impl_eq_hash,
pub struct $name { $(pub $field_name : $field_ty),* },
$name
);
$crate::impl_property!($name, $entity $(, $($extra)*)?);
};
(
struct $name:ident { $($visibility:vis $field_name:ident : $field_ty:ty),* $(,)? },
$entity:ident
$(, $($extra:tt)*)?
) => {
$crate::define_property!(
@apply_property_decoration ,
pub struct $name { $(pub $field_name : $field_ty),* },
$name
);
$crate::impl_property!($name, $entity $(, $($extra)*)?);
};
(
enum $name:ident {
$($variant:ident),* $(,)?
},
$entity:ident,
impl_eq_hash = $impl_eq_hash:ident
$(, $($extra:tt)*)?
) => {
$crate::define_property!(
@apply_property_decoration $impl_eq_hash,
pub enum $name { $($variant),* },
$name
);
$crate::impl_property!($name, $entity $(, $($extra)*)?);
};
(
enum $name:ident {
$($variant:ident),* $(,)?
},
$entity:ident
$(, $($extra:tt)*)?
) => {
$crate::define_property!(
@apply_property_decoration ,
pub enum $name { $($variant),* },
$name
);
$crate::impl_property!($name, $entity $(, $($extra)*)?);
};
(@apply_property_decoration , $item:item, $name:ident) => {
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, serde::Serialize)]
$item
};
(@apply_property_decoration Eq, $item:item, $name:ident) => {
#[derive(Debug, Clone, Copy, serde::Serialize, $crate::rkyv::Archive, $crate::rkyv::Serialize)]
#[rkyv(crate = $crate::rkyv)]
$item
$crate::define_property!(@apply_property_decoration_eq_impl $name);
};
(@apply_property_decoration Hash, $item:item, $name:ident) => {
#[derive(Debug, Clone, Copy, serde::Serialize, $crate::rkyv::Archive, $crate::rkyv::Serialize)]
#[rkyv(crate = $crate::rkyv)]
$item
$crate::define_property!(@apply_property_decoration_hash_impl $name);
};
(@apply_property_decoration both, $item:item, $name:ident) => {
#[derive(Debug, Clone, Copy, serde::Serialize, $crate::rkyv::Archive, $crate::rkyv::Serialize)]
#[rkyv(crate = $crate::rkyv)]
$item
$crate::define_property!(@apply_property_decoration_eq_impl $name);
$crate::define_property!(@apply_property_decoration_hash_impl $name);
};
(@apply_property_decoration neither, $item:item, $name:ident) => {
#[derive(Debug, Clone, Copy, serde::Serialize)]
$item
};
(@apply_property_decoration $mode:ident, $item:item, $name:ident) => {
compile_error!("`impl_eq_hash` must be one of `Eq`, `Hash`, `both`, or `neither`");
};
(@apply_property_decoration_eq_impl $name:ident) => {
impl core::cmp::PartialEq for $name {
fn eq(&self, other: &Self) -> bool {
const N: usize = core::mem::size_of::<<$name as $crate::rkyv::Archive>::Archived>();
let left = $crate::rkyv::api::high::to_bytes_in::<_, $crate::rkyv::rancor::Error>(
self,
$crate::hashing::EqualityBufferWriter::<N>::new(),
)
.expect("serializing left value for equality comparison failed");
let right = $crate::rkyv::api::high::to_bytes_in::<_, $crate::rkyv::rancor::Error>(
other,
$crate::hashing::EqualityBufferWriter::<N>::new(),
)
.expect("serializing right value for equality comparison failed");
left.as_written() == right.as_written()
}
}
impl core::cmp::Eq for $name {}
};
(@apply_property_decoration_hash_impl $name:ident) => {
impl core::hash::Hash for $name {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
$crate::rkyv::api::high::to_bytes_in::<_, $crate::rkyv::rancor::Error>(
self,
$crate::hashing::HasherWriter::new(state),
)
.expect("serialization failed while hashing");
}
}
};
}
#[macro_export]
macro_rules! impl_property {
(
$property:ident,
$entity:ident
$(, compute_derived_fn = $compute_derived_fn:expr)?
$(, default_const = $default_const: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)?
$(, display_impl = $display_impl: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),
[&'a dyn std::any::Any; 1],
{
|parts: &[&dyn std::any::Any]| -> Option<<$property as $crate::entity::property::Property<$entity>>::CanonicalValue> {
let [part] = parts else {
return None;
};
part.downcast_ref::<$property>()
.copied()
.map(<$property as $crate::entity::property::Property<$entity>>::make_canonical)
}
},
{
fn default_query_parts_for_value<'a>(value: &'a $property) -> [&'a dyn std::any::Any; 1] {
[value as &'a dyn std::any::Any]
}
default_query_parts_for_value
},
$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>();
}),
);
};
(
@with_option_display_default
$property:ident,
$entity:ident
$(, compute_derived_fn = $compute_derived_fn:expr)?
$(, default_const = $default_const: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)?
$(, display_impl = $display_impl:expr)?
$(, ctor_registration = $ctor_registration:expr)?
) => {
$crate::impl_property!(
$property,
$entity
$(, compute_derived_fn = $compute_derived_fn)?
$(, default_const = $default_const)?
$(, canonical_value = $canonical_value)?
$(, make_canonical = $make_canonical)?
$(, make_uncanonical = $make_uncanonical)?
$(, index_id_fn = $index_id_fn)?
$(, collect_deps_fn = $collect_deps_fn)?
, display_impl = $crate::impl_property!(@unwrap_or $($display_impl)?, |value: &Self| {
match value.0 {
Some(v) => format!("{:?}", v),
None => "None".to_string(),
}
})
$(, ctor_registration = $ctor_registration)?
);
};
(
@multi_property
$property:ident,
$entity:ident,
( $($dependency:ident),+ )
$(, compute_derived_fn = $compute_derived_fn:expr)?
$(, default_const = $default_const: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)?
$(, display_impl = $display_impl: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),
[&'a dyn std::any::Any; $crate::impl_property!(@count_tts $($dependency),+)],
$crate::canonical_from_sorted_query_parts_closure!(( $($dependency),+ )),
{
$crate::paste::paste! {
fn multi_property_query_parts_for_value<'a>(
value: &'a $property,
) -> [&'a dyn std::any::Any; $crate::impl_property!(@count_tts $($dependency),+)] {
let keys = [
$(
<$dependency as $crate::entity::property::Property<$entity>>::name(),
)+
];
let ( $( [<_ $dependency:lower>] ),+ ) = value;
let mut parts = [
$(
[<_ $dependency:lower>] as &'a dyn std::any::Any,
)+
];
$crate::entity::multi_property::static_reorder_by_keys(&keys, &mut parts);
parts
}
multi_property_query_parts_for_value
}
},
$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 };
(@replace_with_unit $_tt:tt) => { () };
(@count_tts $($tt:tt),* $(,)?) => {
<[()]>::len(&[$($crate::impl_property!(@replace_with_unit $tt)),*])
};
(
@__impl_property_common
$property:ident, $entity:ident, $canonical_value:ty, $initialization_kind:expr, $compute_derived_fn:expr, $default_const:expr, $make_canonical:expr, $make_uncanonical:expr, $query_parts_type:ty,
$canonical_from_sorted_query_parts_fn:expr,
$query_parts_for_value_fn: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;
type QueryParts<'a> = $query_parts_type where Self: 'a;
const NAME: &'static str = stringify!($property);
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 canonical_from_sorted_query_parts(
parts: &[&dyn std::any::Any],
) -> Option<Self::CanonicalValue> {
($canonical_from_sorted_query_parts_fn)(parts)
}
fn query_parts_for_value(value: &Self) -> Self::QueryParts<'_> {
($query_parts_for_value_fn)(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,
impl_eq_hash = $impl_eq_hash:ident
$(, $($extra:tt)+)*
) => {
$crate::define_property!(
@apply_property_decoration
$impl_eq_hash,
pub struct $name(pub Option<$inner_ty>);,
$name
);
$crate::impl_derived_property!(
@with_option_display_default
$name,
$entity,
[$($dependency),*],
[$($($global_dependency),*)?],
|$($param),+| $derive_fn
$(, $($extra)+)*
);
};
(
struct $name:ident ( $visibility:vis Option<$inner_ty:ty> ),
$entity:ident,
[$($dependency:ident),*]
$(, [$($global_dependency:ident),*])?,
|$($param:ident),+| $derive_fn:expr
$(, $($extra:tt)+)*
) => {
$crate::define_property!(
@apply_property_decoration
,
pub struct $name(pub Option<$inner_ty>);,
$name
);
$crate::impl_derived_property!(
@with_option_display_default
$name,
$entity,
[$($dependency),*],
[$($($global_dependency),*)?],
|$($param),+| $derive_fn
$(, $($extra)+)*
);
};
(
struct $name:ident ( $($visibility:vis $field_ty:ty),* $(,)? ),
$entity:ident,
[$($dependency:ident),*]
$(, [$($global_dependency:ident),*])?,
|$($param:ident),+| $derive_fn:expr,
impl_eq_hash = $impl_eq_hash:ident
$(, $($extra:tt)+)*
) => {
$crate::define_property!(
@apply_property_decoration
$impl_eq_hash,
pub struct $name( $(pub $field_ty),* );,
$name
);
$crate::impl_derived_property!(
$name,
$entity,
[$($dependency),*],
[$($($global_dependency),*)?],
|$($param),+| $derive_fn
$(, $($extra)+)*
);
};
(
struct $name:ident ( $($visibility:vis $field_ty:ty),* $(,)? ),
$entity:ident,
[$($dependency:ident),*]
$(, [$($global_dependency:ident),*])?,
|$($param:ident),+| $derive_fn:expr
$(, $($extra:tt)+)*
) => {
$crate::define_property!(
@apply_property_decoration
,
pub struct $name( $(pub $field_ty),* );,
$name
);
$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,
impl_eq_hash = $impl_eq_hash:ident
$(, $($extra:tt)+)*
) => {
$crate::define_property!(
@apply_property_decoration
$impl_eq_hash,
pub struct $name { $($visibility $field_name : $field_ty),* },
$name
);
$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)+)*
) => {
$crate::define_property!(
@apply_property_decoration
,
pub struct $name { $($visibility $field_name : $field_ty),* },
$name
);
$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,
impl_eq_hash = $impl_eq_hash:ident
$(, $($extra:tt)+)*
) => {
$crate::define_property!(
@apply_property_decoration
$impl_eq_hash,
pub enum $name {
$($variant),*
},
$name
);
$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)+)*
) => {
$crate::define_property!(
@apply_property_decoration
,
pub enum $name {
$($variant),*
},
$name
);
$crate::impl_derived_property!(
$name,
$entity,
[$($dependency),*],
[$($($global_dependency),*)?],
|$($param),+| $derive_fn
$(, $($extra)+)*
);
};
}
#[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
}
};
(@unwrap_or $value:expr, $_default:expr) => { $value };
(@unwrap_or, $default:expr) => { $default };
(
@with_option_display_default
$name:ident,
$entity:ident,
[$($dependency:ident),*],
[$($global_dependency:ident),*],
|$($param:ident),+| $derive_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_derived_property!(
$name,
$entity,
[$($dependency),*],
[$($global_dependency),*],
|$($param),+| $derive_fn
$(, default_const = $default_const)?
, display_impl = $crate::impl_derived_property!(@unwrap_or $($display_impl)?, |value: &$name| {
match value.0 {
Some(v) => format!("{:?}", v),
None => "None".to_string(),
}
})
$(, canonical_value = $canonical_value)?
$(, make_canonical = $make_canonical)?
$(, make_uncanonical = $make_uncanonical)?
$(, index_id_fn = $index_id_fn)?
$(, collect_deps_fn = $collect_deps_fn)?
$(, ctor_registration = $ctor_registration)?
);
};
}
#[macro_export]
macro_rules! define_multi_property {
(
( $($dependency:ident),+ ),
$entity:ident
) => {
$crate::paste::paste! {
type [<$($dependency)*>] = ( $($dependency),+ );
$(
const _: () = assert!(
$crate::entity::property::const_str_eq(
stringify!($dependency),
<$dependency as $crate::entity::property::Property<$entity>>::NAME,
),
concat!(
"define_multi_property!: `",
stringify!($dependency),
"` is a type alias; use the underlying property type (see issue #843)."
),
);
)+
$crate::impl_property!(
@multi_property
[<$($dependency)*>],
$entity,
( $($dependency),+ ),
compute_derived_fn = |context: &$crate::Context, entity_id: $crate::entity::EntityId<$entity>| {
(
$(context.get_property::<$entity, $dependency>(entity_id)),+
)
},
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());
}
)*
},
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
},
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 crate::entity::{PropertyIndexType, QueryInternal};
use crate::prelude::*;
use crate::with;
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 POFloat(Option<f64>),
Person,
impl_eq_hash = both,
default_const = POFloat(None)
);
define_property!(
struct POu32Custom(Option<u32>),
Person,
default_const = POu32Custom(None),
display_impl = |value: &POu32Custom| match value.0 {
Some(v) => format!("custom:{v}"),
None => "custom:none".to_string(),
}
);
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, impl_eq_hash = both, default_const = Weight(0.0));
define_property!(
struct Innocculation {
time: f64,
dose: u8,
},
Person,
impl_eq_hash = both,
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)
}
);
define_derived_property!(
struct DerivedMaybeAge(Option<u8>),
Person,
[Age],
|age| DerivedMaybeAge((age.0 != 0).then_some(age.0))
);
define_derived_property!(
struct DerivedMaybeWeight(Option<f64>),
Person,
[Age],
|age| DerivedMaybeWeight((age.0 != 0).then_some(age.0 as f64)),
impl_eq_hash = both
);
define_derived_property!(
struct DerivedMaybeAgeCustom(Option<u8>),
Person,
[Age],
|age| DerivedMaybeAgeCustom((age.0 != 0).then_some(age.0)),
display_impl = |value: &DerivedMaybeAgeCustom| match value.0 {
Some(v) => format!("derived:{v}"),
None => "derived:none".to_string(),
}
);
define_derived_property!(
struct DerivedWeight(f64),
Person,
[Age],
|age| DerivedWeight(age.0 as f64),
impl_eq_hash = both
);
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
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!(
crate::hashing::one_shot_128(&a_canonical),
crate::hashing::one_shot_128(&b_canonical)
);
assert_eq!(
crate::hashing::one_shot_128(&a_canonical),
crate::hashing::one_shot_128(&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(with!(Person, Name("John"), Age(42), Weight(220.5)))
.unwrap();
context
.add_entity(with!(Person, Name("Jane"), Age(22), Weight(180.5)))
.unwrap();
context
.add_entity(with!(Person, Name("Bob"), Age(32), Weight(190.5)))
.unwrap();
context
.add_entity(with!(Person, 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 QueryInternal<Person>>::multi_property_id(&example_query);
assert!(query_multi_property_id.is_some());
assert_eq!(ProfileNAW::index_id(), query_multi_property_id.unwrap());
let query_parts = QueryInternal::query_parts(&example_query);
assert_eq!(
ProfileNAW::canonical_from_sorted_query_parts(query_parts.as_ref()),
Some((Name("Alice"), Age(22), Weight(170.5)).make_canonical())
);
}
context.with_query_results(
with!(Person, (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, _>(with!(Person, Age(92)))
.unwrap();
let child = context
.add_entity::<Person, _>(with!(Person, Age(12)))
.unwrap();
let adult = context
.add_entity::<Person, _>(with!(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(),
DerivedMaybeAge::id(),
DerivedMaybeWeight::id(),
DerivedMaybeAgeCustom::id(),
DerivedWeight::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(with!(Person, 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(with!(Person, POu32(None), Pu32(11)))
.unwrap();
assert_eq!(
format!(
"{:}",
POu32::get_display(&context.get_property::<_, POu32>(person2))
),
"None"
);
}
#[test]
fn test_option_property_display_patterns() {
let mut context = Context::new();
let some_person = context
.add_entity(with!(
Person,
POu32(Some(42)),
POFloat(Some(3.5)),
POu32Custom(Some(7)),
Pu32(1),
))
.unwrap();
let none_person = context
.add_entity(with!(
Person,
POu32(None),
POFloat(None),
POu32Custom(None),
Pu32(2)
))
.unwrap();
assert_eq!(
POu32::get_display(&context.get_property::<_, POu32>(some_person)),
"42"
);
assert_eq!(
POu32::get_display(&context.get_property::<_, POu32>(none_person)),
"None"
);
assert_eq!(
POFloat::get_display(&context.get_property::<_, POFloat>(some_person)),
"3.5"
);
assert_eq!(
POFloat::get_display(&context.get_property::<_, POFloat>(none_person)),
"None"
);
assert_eq!(
POu32Custom::get_display(&context.get_property::<_, POu32Custom>(some_person)),
"custom:7"
);
assert_eq!(
POu32Custom::get_display(&context.get_property::<_, POu32Custom>(none_person)),
"custom:none"
);
}
#[test]
fn test_option_derived_property_display_patterns() {
let mut context = Context::new();
let some_person = context
.add_entity::<Person, _>(with!(Person, Age(42)))
.unwrap();
let none_person = context
.add_entity::<Person, _>(with!(Person, Age(0)))
.unwrap();
assert_eq!(
DerivedMaybeAge::get_display(&context.get_property::<_, DerivedMaybeAge>(some_person)),
"42"
);
assert_eq!(
DerivedMaybeAge::get_display(&context.get_property::<_, DerivedMaybeAge>(none_person)),
"None"
);
assert_eq!(
DerivedMaybeWeight::get_display(
&context.get_property::<_, DerivedMaybeWeight>(some_person)
),
"42.0"
);
assert_eq!(
DerivedMaybeWeight::get_display(
&context.get_property::<_, DerivedMaybeWeight>(none_person)
),
"None"
);
assert_eq!(
DerivedMaybeAgeCustom::get_display(
&context.get_property::<_, DerivedMaybeAgeCustom>(some_person)
),
"derived:42"
);
assert_eq!(
DerivedMaybeAgeCustom::get_display(
&context.get_property::<_, DerivedMaybeAgeCustom>(none_person)
),
"derived: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))");
}
#[test]
fn test_define_derived_property_impl_eq_hash() {
let mut values = crate::HashSet::default();
values.insert(DerivedWeight(3.0));
assert!(values.contains(&DerivedWeight(3.0)));
}
}