1#[macro_export]
205macro_rules! define_property {
206 (
225 struct $name:ident ( $visibility:vis Option<$inner_ty:ty> ),
226 $entity:ident,
227 impl_eq_hash = $impl_eq_hash:ident
228 $(, $($extra:tt)*)?
229 ) => {
230 $crate::define_property!(
231 @apply_property_decoration $impl_eq_hash,
232 pub struct $name(pub Option<$inner_ty>);,
233 $name
234 );
235 $crate::impl_property!(@with_option_display_default $name, $entity $(, $($extra)*)?);
236 };
237 (
238 struct $name:ident ( $visibility:vis Option<$inner_ty:ty> ),
239 $entity:ident
240 $(, $($extra:tt)*)?
241 ) => {
242 $crate::define_property!(
243 @apply_property_decoration ,
244 pub struct $name(pub Option<$inner_ty>);,
245 $name
246 );
247 $crate::impl_property!(@with_option_display_default $name, $entity $(, $($extra)*)?);
248 };
249
250 (
251 struct $name:ident ( $($visibility:vis $field_ty:ty),* $(,)? ),
252 $entity:ident,
253 impl_eq_hash = $impl_eq_hash:ident
254 $(, $($extra:tt)*)?
255 ) => {
256 $crate::define_property!(
257 @apply_property_decoration $impl_eq_hash,
258 pub struct $name($(pub $field_ty),*);,
259 $name
260 );
261 $crate::impl_property!($name, $entity $(, $($extra)*)?);
262 };
263 (
264 struct $name:ident ( $($visibility:vis $field_ty:ty),* $(,)? ),
265 $entity:ident
266 $(, $($extra:tt)*)?
267 ) => {
268 $crate::define_property!(
269 @apply_property_decoration ,
270 pub struct $name($(pub $field_ty),*);,
271 $name
272 );
273 $crate::impl_property!($name, $entity $(, $($extra)*)?);
274 };
275
276 (
277 struct $name:ident { $($visibility:vis $field_name:ident : $field_ty:ty),* $(,)? },
278 $entity:ident,
279 impl_eq_hash = $impl_eq_hash:ident
280 $(, $($extra:tt)*)?
281 ) => {
282 $crate::define_property!(
283 @apply_property_decoration $impl_eq_hash,
284 pub struct $name { $(pub $field_name : $field_ty),* },
285 $name
286 );
287 $crate::impl_property!($name, $entity $(, $($extra)*)?);
288 };
289 (
290 struct $name:ident { $($visibility:vis $field_name:ident : $field_ty:ty),* $(,)? },
291 $entity:ident
292 $(, $($extra:tt)*)?
293 ) => {
294 $crate::define_property!(
295 @apply_property_decoration ,
296 pub struct $name { $(pub $field_name : $field_ty),* },
297 $name
298 );
299 $crate::impl_property!($name, $entity $(, $($extra)*)?);
300 };
301
302 (
303 enum $name:ident {
304 $($variant:ident),* $(,)?
305 },
306 $entity:ident,
307 impl_eq_hash = $impl_eq_hash:ident
308 $(, $($extra:tt)*)?
309 ) => {
310 $crate::define_property!(
311 @apply_property_decoration $impl_eq_hash,
312 pub enum $name { $($variant),* },
313 $name
314 );
315 $crate::impl_property!($name, $entity $(, $($extra)*)?);
316 };
317 (
318 enum $name:ident {
319 $($variant:ident),* $(,)?
320 },
321 $entity:ident
322 $(, $($extra:tt)*)?
323 ) => {
324 $crate::define_property!(
325 @apply_property_decoration ,
326 pub enum $name { $($variant),* },
327 $name
328 );
329 $crate::impl_property!($name, $entity $(, $($extra)*)?);
330 };
331
332 (@apply_property_decoration , $item:item, $name:ident) => {
335 #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, serde::Serialize, serde::Deserialize)]
336 $item
337 };
338 (@apply_property_decoration Eq, $item:item, $name:ident) => {
339 #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, $crate::rkyv::Archive, $crate::rkyv::Serialize)]
340 #[rkyv(crate = $crate::rkyv)]
341 $item
342 $crate::impl_property_eq!($name);
343 };
344 (@apply_property_decoration Hash, $item:item, $name:ident) => {
345 #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, $crate::rkyv::Archive, $crate::rkyv::Serialize)]
346 #[rkyv(crate = $crate::rkyv)]
347 $item
348 $crate::impl_property_hash!($name);
349 };
350 (@apply_property_decoration both, $item:item, $name:ident) => {
351 #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, $crate::rkyv::Archive, $crate::rkyv::Serialize)]
352 #[rkyv(crate = $crate::rkyv)]
353 $item
354 $crate::impl_property_eq_hash!($name);
355 };
356 (@apply_property_decoration neither, $item:item, $name:ident) => {
357 #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
358 $item
359 };
360 (@apply_property_decoration $mode:ident, $item:item, $name:ident) => {
361 compile_error!("`impl_eq_hash` must be one of `Eq`, `Hash`, `both`, or `neither`");
362 };
363
364}
365
366#[macro_export]
377macro_rules! impl_property_eq {
378 ($name:ident) => {
379 impl core::cmp::PartialEq for $name {
380 fn eq(&self, other: &Self) -> bool {
381 const N: usize = core::mem::size_of::<<$name as $crate::rkyv::Archive>::Archived>();
382
383 let left = $crate::rkyv::api::high::to_bytes_in::<_, $crate::rkyv::rancor::Error>(
384 self,
385 $crate::hashing::EqualityBufferWriter::<N>::new(),
386 )
387 .expect("serializing left value for equality comparison failed");
388
389 let right = $crate::rkyv::api::high::to_bytes_in::<_, $crate::rkyv::rancor::Error>(
390 other,
391 $crate::hashing::EqualityBufferWriter::<N>::new(),
392 )
393 .expect("serializing right value for equality comparison failed");
394
395 left.as_written() == right.as_written()
396 }
397 }
398
399 impl core::cmp::Eq for $name {}
400 };
401}
402
403#[macro_export]
414macro_rules! impl_property_hash {
415 ($name:ident) => {
416 impl core::hash::Hash for $name {
417 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
418 $crate::rkyv::api::high::to_bytes_in::<_, $crate::rkyv::rancor::Error>(
419 self,
420 $crate::hashing::HasherWriter::new(state),
421 )
422 .expect("serialization failed while hashing");
423 }
424 }
425 };
426}
427
428#[macro_export]
437macro_rules! impl_property_eq_hash {
438 ($name:ident) => {
439 $crate::impl_property_eq!($name);
440 $crate::impl_property_hash!($name);
441 };
442}
443
444#[macro_export]
499macro_rules! impl_property {
500 (
501 $property:ident,
502 $entity:ident
503 $(, compute_derived_fn = $compute_derived_fn:expr)?
504 $(, default_const = $default_const:expr)?
505 $(, collect_deps_fn = $collect_deps_fn:expr)?
506 $(, display_impl = $display_impl:expr)?
507 $(, ctor_registration = $ctor_registration:expr)?
508 ) => {
509 $crate::impl_property!(@assert_not_both $($compute_derived_fn)? ; $($default_const)?);
511
512 $crate::impl_property!(
513 @__impl_property_common
514 $property,
515 $entity,
516
517 $crate::impl_property!(@select_initialization_kind $($compute_derived_fn)? ; $($default_const)?),
519
520 $crate::impl_property!(
522 @unwrap_or
523 $($compute_derived_fn)?,
524 |_, _| panic!("property {} is not derived", stringify!($property))
525 ),
526
527 $crate::impl_property!(
529 @unwrap_or
530 $($default_const)?,
531 panic!("property {} has no default value", stringify!($property))
532 ),
533
534 [&'a dyn std::any::Any; 1],
536
537 {
539 |parts: &[&dyn std::any::Any]| -> Option<$property> {
540 let [part] = parts else {
541 return None;
542 };
543 part.downcast_ref::<$property>().copied()
544 }
545 },
546
547 {
549 fn default_query_parts_for_value<'a>(value: &'a $property) -> [&'a dyn std::any::Any; 1] {
550 [value as &'a dyn std::any::Any]
551 }
552
553 default_query_parts_for_value
554 },
555
556 {
558 std::any::TypeId::of::<Self>()
559 },
560
561 $crate::impl_property!(@unwrap_or $($display_impl)?, |v| format!("{v:?}")),
563
564 $crate::impl_property!(
566 @unwrap_or
567 $($collect_deps_fn)?,
568 |_| {}
569 ),
570
571 $crate::impl_property!(@unwrap_or $($ctor_registration)?, {
573 $crate::entity::property_store::add_to_property_registry::<$entity, $property>();
574 }),
575 );
576 };
577
578 (
579 @with_option_display_default
580 $property:ident,
581 $entity:ident
582 $(, compute_derived_fn = $compute_derived_fn:expr)?
583 $(, default_const = $default_const:expr)?
584 $(, collect_deps_fn = $collect_deps_fn:expr)?
585 $(, display_impl = $display_impl:expr)?
586 $(, ctor_registration = $ctor_registration:expr)?
587 ) => {
588 $crate::impl_property!(
589 $property,
590 $entity
591 $(, compute_derived_fn = $compute_derived_fn)?
592 $(, default_const = $default_const)?
593 $(, collect_deps_fn = $collect_deps_fn)?
594 , display_impl = $crate::impl_property!(@unwrap_or $($display_impl)?, |value: &Self| {
595 match value.0 {
596 Some(v) => format!("{:?}", v),
597 None => "None".to_string(),
598 }
599 })
600 $(, ctor_registration = $ctor_registration)?
601 );
602 };
603
604 (
605 @multi_property
606 $property:ident,
607 $entity:ident,
608 ( $($dependency:ident),+ )
609 $(, compute_derived_fn = $compute_derived_fn:expr)?
610 $(, default_const = $default_const:expr)?
611 $(, collect_deps_fn = $collect_deps_fn:expr)?
612 $(, display_impl = $display_impl:expr)?
613 $(, ctor_registration = $ctor_registration:expr)?
614 ) => {
615 $crate::impl_property!(@assert_not_both $($compute_derived_fn)? ; $($default_const)?);
616
617 $crate::impl_property!(
618 @__impl_property_common
619 $property,
620 $entity,
621 $crate::impl_property!(@select_initialization_kind $($compute_derived_fn)? ; $($default_const)?),
622 $crate::impl_property!(
623 @unwrap_or
624 $($compute_derived_fn)?,
625 |_, _| panic!("property {} is not derived", stringify!($property))
626 ),
627 $crate::impl_property!(
628 @unwrap_or
629 $($default_const)?,
630 panic!("property {} has no default value", stringify!($property))
631 ),
632 [&'a dyn std::any::Any; $crate::impl_property!(@count_tts $($dependency),+)],
633 {
634 $crate::paste::paste! {
635 #[allow(unused_assignments)]
636 |parts: &[&dyn std::any::Any]| -> Option<$property> {
637 let [$( [<p_ $dependency:lower>] ),+,] = parts else {
638 return None;
639 };
640 let sorted_parts = [$( [<p_ $dependency:lower>] ),+];
641 let keys = [
642 $(
643 <$dependency as $crate::entity::property::Property<$entity>>::type_id(),
644 )+
645 ];
646 let indices = $crate::entity::multi_property::static_sorted_indices(&keys);
647 let inverse = $crate::entity::multi_property::static_inverse_indices(&indices);
648 let mut declared_index = 0usize;
649 Some((
650 $(
651 {
652 let value = *sorted_parts[inverse[declared_index]].downcast_ref::<$dependency>()?;
653 declared_index += 1;
654 value
655 },
656 )+
657 ))
658 }
659 }
660 },
661 {
662 $crate::paste::paste! {
663 fn multi_property_query_parts_for_value<'a>(
664 value: &'a $property,
665 ) -> [&'a dyn std::any::Any; $crate::impl_property!(@count_tts $($dependency),+)] {
666 let keys = [
667 $(
668 <$dependency as $crate::entity::property::Property<$entity>>::type_id(),
669 )+
670 ];
671 let ( $( [<_ $dependency:lower>] ),+ ) = value;
672 let mut parts = [
673 $(
674 [<_ $dependency:lower>] as &'a dyn std::any::Any,
675 )+
676 ];
677 $crate::entity::multi_property::static_reorder_by_keys(&keys, &mut parts);
678 parts
679 }
680
681 multi_property_query_parts_for_value
682 }
683 },
684 {
685 std::any::TypeId::of::<Self>()
686 },
687 $crate::impl_property!(@unwrap_or $($display_impl)?, |v| format!("{v:?}")),
688 $crate::impl_property!(@unwrap_or $($collect_deps_fn)?, |_| {}),
689 $crate::impl_property!(@unwrap_or $($ctor_registration)?, {
690 $crate::entity::property_store::add_to_property_registry::<$entity, $property>();
691 }),
692 );
693 };
694
695 (@assert_not_both $compute_derived_fn:expr ; $default_const:expr) => {
697 compile_error!(
698 "impl_property!: `compute_derived_fn = ...` (derived property) and `default_const = ...` \
699 (non-derived property default constant) are mutually exclusive. Remove one of them."
700 );
701 };
702 (@assert_not_both $compute_derived_fn:expr ; ) => {};
703 (@assert_not_both ; $default_const:expr) => {};
704 (@assert_not_both ; ) => {};
705
706 (@select_initialization_kind $compute_derived_fn:expr ; $default_const:expr) => {
708 compile_error!(
711 "impl_property!: cannot select initialization kind because both `compute_derived_fn` \
712 and `default_const` are present"
713 )
714 };
715 (@select_initialization_kind $compute_derived_fn:expr ; ) => {
716 $crate::entity::property::PropertyInitializationKind::Derived
717 };
718 (@select_initialization_kind ; $default_const:expr) => {
719 $crate::entity::property::PropertyInitializationKind::Constant
720 };
721 (@select_initialization_kind ; ) => {
722 $crate::entity::property::PropertyInitializationKind::Explicit
723 };
724
725 (@unwrap_or $value:expr, $_default:expr) => { $value };
727 (@unwrap_or, $default:expr) => { $default };
728
729 (@replace_with_unit $_tt:tt) => { () };
730 (@count_tts $($tt:tt),* $(,)?) => {
731 <[()]>::len(&[$($crate::impl_property!(@replace_with_unit $tt)),*])
732 };
733
734 (
736 @__impl_property_common
737 $property:ident, $entity:ident, $initialization_kind:expr, $compute_derived_fn:expr, $default_const:expr, $query_parts_type:ty,
743 $value_from_query_parts_fn:expr,
744 $query_parts_for_value_fn:expr,
745 $type_id_fn:expr, $display_impl:expr, $collect_deps_fn:expr, $ctor_registration:expr, ) => {
750 impl $crate::entity::property::Property<$entity> for $property {
751 type QueryParts<'a> = $query_parts_type where Self: 'a;
752
753 const NAME: &'static str = stringify!($property);
754
755 fn initialization_kind() -> $crate::entity::property::PropertyInitializationKind {
756 $initialization_kind
757 }
758
759 fn compute_derived(
760 _context: &$crate::Context,
761 _entity_id: $crate::entity::EntityId<$entity>,
762 ) -> Self {
763 ($compute_derived_fn)(_context, _entity_id)
764 }
765
766 fn default_const() -> Self {
767 $default_const
768 }
769
770 fn value_from_query_parts(
771 parts: &[&dyn std::any::Any],
772 ) -> Option<Self> {
773 ($value_from_query_parts_fn)(parts)
774 }
775
776 fn query_parts_for_value(value: &Self) -> Self::QueryParts<'_> {
777 ($query_parts_for_value_fn)(value)
778 }
779
780 fn type_id() -> std::any::TypeId {
781 $type_id_fn
782 }
783
784 fn get_display(&self) -> String {
785 ($display_impl)(self)
786 }
787
788 fn id() -> usize {
789 static INDEX: std::sync::atomic::AtomicUsize =
793 std::sync::atomic::AtomicUsize::new(usize::MAX);
794
795 let index = INDEX.load(std::sync::atomic::Ordering::Relaxed);
797 if index != usize::MAX {
798 return index;
799 }
800
801 $crate::entity::property_store::initialize_property_id::<$entity>(&INDEX)
803 }
804
805 fn query_identity_id() -> usize {
806 static IDENTITY: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
807
808 *IDENTITY.get_or_init(|| {
809 $crate::entity::multi_property::intern_query_identity_raw::<$entity>(&[
810 <Self as $crate::entity::property::Property<$entity>>::type_id(),
811 ])
812 })
813 }
814
815 fn collect_non_derived_dependencies(result: &mut $crate::HashSet<usize>) {
816 ($collect_deps_fn)(result)
817 }
818 }
819
820 $crate::paste::paste! {
821 $crate::ctor::declarative::ctor!{
822 #[ctor(unsafe)]
823 fn [<_register_property_ $entity:snake _ $property:snake>]() {
824 $ctor_registration
825 }
826 }
827 }
828 };
829}
830
831#[macro_export]
845macro_rules! define_derived_property {
846 (
855 struct $name:ident ( $visibility:vis Option<$inner_ty:ty> ),
856 $entity:ident,
857 [$($dependency:ident),*]
858 $(, [$($global_dependency:ident),*])?,
859 |$($param:ident),+| $derive_fn:expr,
860 impl_eq_hash = $impl_eq_hash:ident
861 $(, $($extra:tt)+)*
862 ) => {
863 $crate::define_property!(
864 @apply_property_decoration
865 $impl_eq_hash,
866 pub struct $name(pub Option<$inner_ty>);,
867 $name
868 );
869
870 $crate::impl_derived_property!(
871 @with_option_display_default
872 $name,
873 $entity,
874 [$($dependency),*],
875 [$($($global_dependency),*)?],
876 |$($param),+| $derive_fn
877 $(, $($extra)+)*
878 );
879 };
880 (
881 struct $name:ident ( $visibility:vis Option<$inner_ty:ty> ),
882 $entity:ident,
883 [$($dependency:ident),*]
884 $(, [$($global_dependency:ident),*])?,
885 |$($param:ident),+| $derive_fn:expr
886 $(, $($extra:tt)+)*
887 ) => {
888 $crate::define_property!(
889 @apply_property_decoration
890 ,
891 pub struct $name(pub Option<$inner_ty>);,
892 $name
893 );
894
895 $crate::impl_derived_property!(
896 @with_option_display_default
897 $name,
898 $entity,
899 [$($dependency),*],
900 [$($($global_dependency),*)?],
901 |$($param),+| $derive_fn
902 $(, $($extra)+)*
903 );
904 };
905
906 (
908 struct $name:ident ( $($visibility:vis $field_ty:ty),* $(,)? ),
909 $entity:ident,
910 [$($dependency:ident),*]
911 $(, [$($global_dependency:ident),*])?,
912 |$($param:ident),+| $derive_fn:expr,
913 impl_eq_hash = $impl_eq_hash:ident
914 $(, $($extra:tt)+)*
915 ) => {
916 $crate::define_property!(
917 @apply_property_decoration
918 $impl_eq_hash,
919 pub struct $name( $(pub $field_ty),* );,
920 $name
921 );
922
923 $crate::impl_derived_property!(
924 $name,
925 $entity,
926 [$($dependency),*],
927 [$($($global_dependency),*)?],
928 |$($param),+| $derive_fn
929 $(, $($extra)+)*
930 );
931 };
932 (
933 struct $name:ident ( $($visibility:vis $field_ty:ty),* $(,)? ),
934 $entity:ident,
935 [$($dependency:ident),*]
936 $(, [$($global_dependency:ident),*])?,
937 |$($param:ident),+| $derive_fn:expr
938 $(, $($extra:tt)+)*
939 ) => {
940 $crate::define_property!(
941 @apply_property_decoration
942 ,
943 pub struct $name( $(pub $field_ty),* );,
944 $name
945 );
946
947 $crate::impl_derived_property!(
948 $name,
949 $entity,
950 [$($dependency),*],
951 [$($($global_dependency),*)?],
952 |$($param),+| $derive_fn
953 $(, $($extra)+)*
954 );
955 };
956
957 (
959 struct $name:ident { $($visibility:vis $field_name:ident : $field_ty:ty),* $(,)? },
960 $entity:ident,
961 [$($dependency:ident),*]
962 $(, [$($global_dependency:ident),*])?,
963 |$($param:ident),+| $derive_fn:expr,
964 impl_eq_hash = $impl_eq_hash:ident
965 $(, $($extra:tt)+)*
966 ) => {
967 $crate::define_property!(
968 @apply_property_decoration
969 $impl_eq_hash,
970 pub struct $name { $($visibility $field_name : $field_ty),* },
971 $name
972 );
973
974 $crate::impl_derived_property!(
975 $name,
976 $entity,
977 [$($dependency),*],
978 [$($($global_dependency),*)?],
979 |$($param),+| $derive_fn
980 $(, $($extra)+)*
981 );
982 };
983 (
984 struct $name:ident { $($visibility:vis $field_name:ident : $field_ty:ty),* $(,)? },
985 $entity:ident,
986 [$($dependency:ident),*]
987 $(, [$($global_dependency:ident),*])?,
988 |$($param:ident),+| $derive_fn:expr
989 $(, $($extra:tt)+)*
990 ) => {
991 $crate::define_property!(
992 @apply_property_decoration
993 ,
994 pub struct $name { $($visibility $field_name : $field_ty),* },
995 $name
996 );
997
998 $crate::impl_derived_property!(
999 $name,
1000 $entity,
1001 [$($dependency),*],
1002 [$($($global_dependency),*)?],
1003 |$($param),+| $derive_fn
1004 $(, $($extra)+)*
1005 );
1006 };
1007
1008 (
1010 enum $name:ident {
1011 $($variant:ident),* $(,)?
1012 },
1013 $entity:ident,
1014 [$($dependency:ident),*]
1015 $(, [$($global_dependency:ident),*])?,
1016 |$($param:ident),+| $derive_fn:expr,
1017 impl_eq_hash = $impl_eq_hash:ident
1018 $(, $($extra:tt)+)*
1019 ) => {
1020 $crate::define_property!(
1021 @apply_property_decoration
1022 $impl_eq_hash,
1023 pub enum $name {
1024 $($variant),*
1025 },
1026 $name
1027 );
1028
1029 $crate::impl_derived_property!(
1030 $name,
1031 $entity,
1032 [$($dependency),*],
1033 [$($($global_dependency),*)?],
1034 |$($param),+| $derive_fn
1035 $(, $($extra)+)*
1036 );
1037 };
1038 (
1039 enum $name:ident {
1040 $($variant:ident),* $(,)?
1041 },
1042 $entity:ident,
1043 [$($dependency:ident),*]
1044 $(, [$($global_dependency:ident),*])?,
1045 |$($param:ident),+| $derive_fn:expr
1046 $(, $($extra:tt)+)*
1047 ) => {
1048 $crate::define_property!(
1049 @apply_property_decoration
1050 ,
1051 pub enum $name {
1052 $($variant),*
1053 },
1054 $name
1055 );
1056
1057 $crate::impl_derived_property!(
1058 $name,
1059 $entity,
1060 [$($dependency),*],
1061 [$($($global_dependency),*)?],
1062 |$($param),+| $derive_fn
1063 $(, $($extra)+)*
1064 );
1065 };
1066}
1067
1068#[macro_export]
1074macro_rules! impl_derived_property {
1075 (
1076 $name:ident,
1077 $entity:ident,
1078 [$($dependency:ident),*]
1079 $(, [$($global_dependency:ident),*])?,
1080 |$($param:ident),+| $derive_fn:expr
1081 $(, $($extra:tt)+)*
1082 ) => {
1083 $crate::impl_property!(
1084 $name,
1085 $entity,
1086 compute_derived_fn = $crate::impl_derived_property!(
1087 @construct_compute_fn
1088 $entity,
1089 [$($dependency),*],
1090 [$($($global_dependency),*)?],
1091 |$($param),+| $derive_fn
1092 ),
1093 collect_deps_fn = | deps: &mut $crate::HashSet<usize> | {
1094 $(
1095 if <$dependency as $crate::entity::property::Property<$entity>>::is_derived() {
1096 <$dependency as $crate::entity::property::Property<$entity>>::collect_non_derived_dependencies(deps);
1097 } else {
1098 deps.insert(<$dependency as $crate::entity::property::Property<$entity>>::id());
1099 }
1100 )*
1101 }
1102 $(, $($extra)+)*
1103 );
1104 };
1105
1106 (
1108 @construct_compute_fn
1109 $entity:ident,
1110 [$($dependency:ident),*],
1111 [$($global_dependency:ident),*],
1112 |$($param:ident),+| $derive_fn:expr
1113 ) => {
1114 |context: &$crate::Context, entity_id| {
1115 #[allow(unused_imports)]
1116 use $crate::global_properties::ContextGlobalPropertiesExt;
1117 #[allow(unused_parens)]
1118 let ($($param,)*) = (
1119 $(context.get_property::<$entity, $dependency>(entity_id)),*,
1120 $(
1121 context.get_global_property_value($global_dependency)
1122 .expect(&format!("Global property {} not initialized", stringify!($global_dependency)))
1123 ),*
1124 );
1125 $derive_fn
1126 }
1127 };
1128
1129 (@unwrap_or $value:expr, $_default:expr) => { $value };
1130 (@unwrap_or, $default:expr) => { $default };
1131
1132 (
1133 @with_option_display_default
1134 $name:ident,
1135 $entity:ident,
1136 [$($dependency:ident),*],
1137 [$($global_dependency:ident),*],
1138 |$($param:ident),+| $derive_fn:expr
1139 $(, default_const = $default_const:expr)?
1140 $(, display_impl = $display_impl:expr)?
1141 $(, collect_deps_fn = $collect_deps_fn:expr)?
1142 $(, ctor_registration = $ctor_registration:expr)?
1143 ) => {
1144 $crate::impl_derived_property!(
1145 $name,
1146 $entity,
1147 [$($dependency),*],
1148 [$($global_dependency),*],
1149 |$($param),+| $derive_fn
1150 $(, default_const = $default_const)?
1151 , display_impl = $crate::impl_derived_property!(@unwrap_or $($display_impl)?, |value: &$name| {
1152 match value.0 {
1153 Some(v) => format!("{:?}", v),
1154 None => "None".to_string(),
1155 }
1156 })
1157 $(, collect_deps_fn = $collect_deps_fn)?
1158 $(, ctor_registration = $ctor_registration)?
1159 );
1160 };
1161
1162}
1163
1164#[macro_export]
1222macro_rules! define_multi_property {
1223 (@impl
1224 $entity:ident,
1225 $($dependency:ident),+
1226 ) => {
1227 $crate::paste::paste! {
1228 type [<$($dependency)*>] = ( $($dependency),+ );
1229
1230 $(
1232 const _: () = assert!(
1233 $crate::entity::property::const_str_eq(
1234 stringify!($dependency),
1235 <$dependency as $crate::entity::property::Property<$entity>>::NAME,
1236 ),
1237 concat!(
1238 "define_multi_property!: `",
1239 stringify!($dependency),
1240 "` is a type alias; use the underlying property type (see issue #843)."
1241 ),
1242 );
1243 )+
1244
1245 $crate::impl_property!(
1246 @multi_property
1247 [<$($dependency)*>],
1248 $entity,
1249 ( $($dependency),+ ),
1250 compute_derived_fn = |context: &$crate::Context, entity_id: $crate::entity::EntityId<$entity>| {
1251 (
1252 $(<$crate::Context as $crate::entity::ContextEntitiesExt>::get_property::<$entity, $dependency>(
1253 context,
1254 entity_id,
1255 )),+
1256 )
1257 },
1258
1259 collect_deps_fn = | deps: &mut $crate::HashSet<usize> | {
1260 $(
1261 if <$dependency as $crate::entity::property::Property<$entity>>::is_derived() {
1262 <$dependency as $crate::entity::property::Property<$entity>>::collect_non_derived_dependencies(deps);
1263 } else {
1264 deps.insert(<$dependency as $crate::entity::property::Property<$entity>>::id());
1265 }
1266 )*
1267 },
1268
1269 display_impl = |val: &( $($dependency),+ )| {
1270 let ( $( [<_ $dependency:lower>] ),+ ) = val;
1271 let mut displayed = String::from("(");
1272 $(
1273 displayed.push_str(
1274 &<$dependency as $crate::entity::property::Property<$entity>>::get_display([<_ $dependency:lower>])
1275 );
1276 displayed.push_str(", ");
1277 )+
1278 displayed.truncate(displayed.len() - 2);
1279 displayed.push_str(")");
1280 displayed
1281 },
1282
1283 ctor_registration = {
1284 let mut type_ids = [$( <$dependency as $crate::entity::property::Property<$entity>>::type_id() ),+];
1285 type_ids.sort_unstable();
1286 if let Some((_, existing_name)) =
1287 $crate::entity::multi_property::register_type_ids_to_multi_property_id(
1288 <$entity as $crate::entity::Entity>::id(),
1289 &type_ids,
1290 <[<$($dependency)*>] as $crate::entity::property::Property<$entity>>::type_id(),
1291 <[<$($dependency)*>] as $crate::entity::property::Property<$entity>>::id(),
1292 <[<$($dependency)*>] as $crate::entity::property::Property<$entity>>::name(),
1293 )
1294 {
1295 $crate::entity::multi_property::record_pre_main_warning(format!(
1296 "multi-property {} is equivalent to already registered multi-property {existing_name}; queries will resolve to {existing_name}, and attempting to index {} will panic",
1297 <[<$($dependency)*>] as $crate::entity::property::Property<$entity>>::name(),
1298 <[<$($dependency)*>] as $crate::entity::property::Property<$entity>>::name(),
1299 ));
1300 }
1301 $crate::entity::property_store::add_to_property_registry::<$entity, [<$($dependency)*>]>();
1302 }
1303 );
1304
1305 }
1306 };
1307
1308 (
1309 $entity:ident,
1310 ( $first:ident, $second:ident $(, $dependency:ident)* $(,)? )
1311 $(,)?
1312 ) => {
1313 $crate::define_multi_property!(@impl $entity, $first, $second $(, $dependency)*);
1314 };
1315
1316 () => {
1317 compile_error!(
1318 "define_multi_property!: expected `define_multi_property!(Entity, (PropertyA, PropertyB, ...))`"
1319 );
1320 };
1321
1322 ($entity:ident $(,)?) => {
1323 compile_error!(
1324 "define_multi_property!: multi-properties require at least two component properties"
1325 );
1326 };
1327
1328 (( $($dependency:ident),+ $(,)? ), $entity:ident $(,)?) => {
1329 compile_error!(
1330 "define_multi_property!: tuple-first syntax is no longer supported; write `define_multi_property!(Entity, (PropertyA, PropertyB, ...))`"
1331 );
1332 };
1333
1334 ($entity:ident, ( $only:ident $(,)? ) $(,)?) => {
1335 compile_error!(
1336 "define_multi_property!: multi-properties require at least two component properties"
1337 );
1338 };
1339
1340 (
1341 $entity:ident,
1342 $first:ident,
1343 $second:ident
1344 $(, $dependency:ident)*
1345 $(,)?
1346 ) => {
1347 compile_error!(
1348 "define_multi_property!: flat component syntax is no longer supported; write `define_multi_property!(Entity, (PropertyA, PropertyB, ...))`"
1349 );
1350 };
1351
1352 ($entity:ident, $only:ident $(,)?) => {
1353 compile_error!(
1354 "define_multi_property!: multi-properties require at least two component properties"
1355 );
1356 };
1357
1358 ($($tokens:tt)*) => {
1359 compile_error!(
1360 "define_multi_property!: expected `define_multi_property!(Entity, (PropertyA, PropertyB, ...))`"
1361 );
1362 };
1363 }
1364#[cfg(test)]
1365mod tests {
1366 #![allow(dead_code)]
1368
1369 use crate::entity::QueryInternal;
1370 use crate::prelude::*;
1371 use crate::with;
1372
1373 define_entity!(Person);
1374 define_entity!(Group);
1375
1376 define_property!(struct Pu32(u32), Person, default_const = Pu32(0));
1377 define_property!(struct POu32(Option<u32>), Person, default_const = POu32(None));
1378 define_property!(
1379 struct POFloat(Option<f64>),
1380 Person,
1381 impl_eq_hash = both,
1382 default_const = POFloat(None)
1383 );
1384 define_property!(
1385 struct POu32Custom(Option<u32>),
1386 Person,
1387 default_const = POu32Custom(None),
1388 display_impl = |value: &POu32Custom| match value.0 {
1389 Some(v) => format!("custom:{v}"),
1390 None => "custom:none".to_string(),
1391 }
1392 );
1393 define_property!(struct Name(&'static str), Person, default_const = Name(""));
1394 define_property!(struct Age(u8), Person, default_const = Age(0));
1395 define_property!(struct Weight(f64), Person, impl_eq_hash = both, default_const = Weight(0.0));
1396
1397 define_property!(
1399 struct Innocculation {
1400 time: f64,
1401 dose: u8,
1402 },
1403 Person,
1404 impl_eq_hash = both,
1405 default_const = Innocculation { time: 0.0, dose: 0 }
1406 );
1407
1408 define_property!(
1410 enum InfectionStatus {
1411 Susceptible,
1412 Infected,
1413 Recovered,
1414 },
1415 Person,
1416 default_const = InfectionStatus::Susceptible
1417 );
1418
1419 define_derived_property!(
1421 enum AgeGroup {
1422 Child,
1423 Adult,
1424 Senior,
1425 },
1426 Person,
1427 [Age], |age| {
1429 let age: Age = age;
1430 if age.0 < 18 {
1431 AgeGroup::Child
1432 } else if age.0 < 65 {
1433 AgeGroup::Adult
1434 } else {
1435 AgeGroup::Senior
1436 }
1437 }
1438 );
1439
1440 define_derived_property!(struct DerivedProp(bool), Person, [Age],
1442 |age| {
1443 DerivedProp(age.0 % 2 == 0)
1444 }
1445 );
1446
1447 define_derived_property!(
1448 struct DerivedMaybeAge(Option<u8>),
1449 Person,
1450 [Age],
1451 |age| DerivedMaybeAge((age.0 != 0).then_some(age.0))
1452 );
1453
1454 define_derived_property!(
1455 struct DerivedMaybeWeight(Option<f64>),
1456 Person,
1457 [Age],
1458 |age| DerivedMaybeWeight((age.0 != 0).then_some(age.0 as f64)),
1459 impl_eq_hash = both
1460 );
1461
1462 define_derived_property!(
1463 struct DerivedMaybeAgeCustom(Option<u8>),
1464 Person,
1465 [Age],
1466 |age| DerivedMaybeAgeCustom((age.0 != 0).then_some(age.0)),
1467 display_impl = |value: &DerivedMaybeAgeCustom| match value.0 {
1468 Some(v) => format!("derived:{v}"),
1469 None => "derived:none".to_string(),
1470 }
1471 );
1472
1473 define_derived_property!(
1474 struct DerivedWeight(f64),
1475 Person,
1476 [Age],
1477 |age| DerivedWeight(age.0 as f64),
1478 impl_eq_hash = both
1479 );
1480
1481 #[derive(Debug, PartialEq, Clone, Copy)]
1482 struct NonIndexableFloat(f64);
1483 impl_property!(
1484 NonIndexableFloat,
1485 Person,
1486 default_const = NonIndexableFloat(0.0)
1487 );
1488
1489 #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
1491 pub enum InfectionKind {
1492 Respiratory,
1493 Genetic,
1494 Superficial,
1495 }
1496 impl_property!(
1497 InfectionKind,
1498 Person,
1499 default_const = InfectionKind::Respiratory
1500 );
1501 impl_property!(InfectionKind, Group, default_const = InfectionKind::Genetic);
1502
1503 define_multi_property!(Person, (Name, Age, Weight));
1504 define_multi_property!(Person, (Age, Weight, Name));
1505 define_multi_property!(Person, (Weight, Age, Name));
1506 define_multi_property!(Person, (Name, Weight,),);
1507
1508 type ProfileNAW = (Name, Age, Weight);
1510 type ProfileAWN = (Age, Weight, Name);
1511 type ProfileWAN = (Weight, Age, Name);
1512 type ProfileNW = (Name, Weight);
1513
1514 define_entity!(SingleProfilePerson);
1515 define_property!(
1516 struct SingleName(&'static str),
1517 SingleProfilePerson,
1518 default_const = SingleName("")
1519 );
1520 define_property!(
1521 struct SingleAge(u8),
1522 SingleProfilePerson,
1523 default_const = SingleAge(0)
1524 );
1525 define_property!(
1526 struct SingleWeight(u8),
1527 SingleProfilePerson,
1528 default_const = SingleWeight(0)
1529 );
1530 define_multi_property!(SingleProfilePerson, (SingleName, SingleAge, SingleWeight));
1531 type SingleProfile = (SingleName, SingleAge, SingleWeight);
1532
1533 #[test]
1534 fn test_multi_property_ordering() {
1535 let a = (Name("Jane"), Age(22), Weight(180.5));
1536 let b = (Age(22), Weight(180.5), Name("Jane"));
1537 let c = (Weight(180.5), Age(22), Name("Jane"));
1538
1539 assert_ne!(ProfileNAW::id(), ProfileAWN::id());
1542 assert_ne!(ProfileNAW::id(), ProfileWAN::id());
1543 assert_ne!(ProfileNAW::type_id(), ProfileAWN::type_id());
1544 assert_ne!(ProfileNAW::type_id(), ProfileWAN::type_id());
1545 let _ = ProfileNW::id();
1546
1547 let query_parts = ProfileNAW::query_parts_for_value(&a);
1548 assert_eq!(
1549 ProfileAWN::value_from_query_parts(query_parts.as_ref()),
1550 Some(b)
1551 );
1552 assert_eq!(
1553 ProfileWAN::value_from_query_parts(query_parts.as_ref()),
1554 Some(c)
1555 );
1556 }
1557
1558 #[test]
1559 fn test_non_indexable_property_unindexed_behavior() {
1560 let mut context = Context::new();
1561
1562 let first = context
1563 .add_entity(with!(Person, NonIndexableFloat(1.5)))
1564 .unwrap();
1565 let _second = context
1566 .add_entity(with!(Person, NonIndexableFloat(2.5)))
1567 .unwrap();
1568
1569 assert_eq!(
1570 context.get_property::<Person, NonIndexableFloat>(first),
1571 NonIndexableFloat(1.5)
1572 );
1573
1574 context.set_property(first, NonIndexableFloat(3.5));
1575 assert_eq!(
1576 context.get_property::<Person, NonIndexableFloat>(first),
1577 NonIndexableFloat(3.5)
1578 );
1579
1580 let mut results = Vec::new();
1581 context.with_query_results(with!(Person, NonIndexableFloat(3.5)), &mut |entity_ids| {
1582 results = entity_ids.into_iter().collect::<Vec<_>>();
1583 });
1584 assert_eq!(results, vec![first]);
1585 assert_eq!(
1586 context.query_entity_count(with!(Person, NonIndexableFloat(3.5))),
1587 1
1588 );
1589 }
1590
1591 #[test]
1592 fn test_single_multi_property_vs_property_query() {
1593 let mut context = Context::new();
1594
1595 context
1596 .add_entity(with!(
1597 SingleProfilePerson,
1598 SingleName("John"),
1599 SingleAge(42),
1600 SingleWeight(220)
1601 ))
1602 .unwrap();
1603 context
1604 .add_entity(with!(
1605 SingleProfilePerson,
1606 SingleName("Jane"),
1607 SingleAge(22),
1608 SingleWeight(180)
1609 ))
1610 .unwrap();
1611 context
1612 .add_entity(with!(
1613 SingleProfilePerson,
1614 SingleName("Bob"),
1615 SingleAge(32),
1616 SingleWeight(190)
1617 ))
1618 .unwrap();
1619 context
1620 .add_entity(with!(
1621 SingleProfilePerson,
1622 SingleName("Alice"),
1623 SingleAge(22),
1624 SingleWeight(170)
1625 ))
1626 .unwrap();
1627
1628 context.index_property::<SingleProfilePerson, SingleProfile>();
1629
1630 let example_query = (SingleName("Alice"), SingleAge(22), SingleWeight(170));
1631 assert_eq!(
1632 <SingleProfile as QueryInternal<SingleProfilePerson>>::multi_property_id(
1633 &example_query
1634 ),
1635 Some(SingleProfile::id())
1636 );
1637 let query_parts = QueryInternal::query_parts(&example_query);
1638 assert_eq!(
1639 SingleProfile::value_from_query_parts(query_parts.as_ref()),
1640 Some((SingleName("Alice"), SingleAge(22), SingleWeight(170)))
1641 );
1642
1643 context.with_query_results(
1644 with!(
1645 SingleProfilePerson,
1646 (SingleName("John"), SingleAge(42), SingleWeight(220))
1647 ),
1648 &mut |results| {
1649 assert_eq!(results.into_iter().count(), 1);
1650 },
1651 );
1652 }
1653
1654 #[test]
1655 fn test_equivalent_multi_property_query_routing() {
1656 let example_query = (Name("Alice"), Age(22), Weight(170.5));
1657 let query_multi_property_id =
1658 <(Name, Age, Weight) as QueryInternal<Person>>::multi_property_id(&example_query);
1659
1660 assert!(matches!(
1661 query_multi_property_id,
1662 Some(id) if id == ProfileNAW::id() || id == ProfileAWN::id() || id == ProfileWAN::id()
1663 ));
1664
1665 let query_parts = QueryInternal::query_parts(&example_query);
1666 assert_eq!(
1667 ProfileNAW::value_from_query_parts(query_parts.as_ref()),
1668 Some((Name("Alice"), Age(22), Weight(170.5)))
1669 );
1670 assert_eq!(
1671 ProfileAWN::value_from_query_parts(query_parts.as_ref()),
1672 Some((Age(22), Weight(170.5), Name("Alice")))
1673 );
1674 assert_eq!(
1675 ProfileWAN::value_from_query_parts(query_parts.as_ref()),
1676 Some((Weight(170.5), Age(22), Name("Alice")))
1677 );
1678 }
1679
1680 #[test]
1681 fn test_derived_property() {
1682 let mut context = Context::new();
1683
1684 let senior = context
1685 .add_entity::<Person, _>(with!(Person, Age(92)))
1686 .unwrap();
1687 let child = context
1688 .add_entity::<Person, _>(with!(Person, Age(12)))
1689 .unwrap();
1690 let adult = context
1691 .add_entity::<Person, _>(with!(Person, Age(44)))
1692 .unwrap();
1693
1694 let senior_group: AgeGroup = context.get_property(senior);
1695 let child_group: AgeGroup = context.get_property(child);
1696 let adult_group: AgeGroup = context.get_property(adult);
1697
1698 assert_eq!(senior_group, AgeGroup::Senior);
1699 assert_eq!(child_group, AgeGroup::Child);
1700 assert_eq!(adult_group, AgeGroup::Adult);
1701
1702 assert!(Age::non_derived_dependencies().is_empty());
1704 assert_eq!(AgeGroup::non_derived_dependencies(), [Age::id()]);
1706
1707 let mut expected_dependents = [
1709 AgeGroup::id(),
1710 DerivedProp::id(),
1711 DerivedMaybeAge::id(),
1712 DerivedMaybeWeight::id(),
1713 DerivedMaybeAgeCustom::id(),
1714 DerivedWeight::id(),
1715 ProfileNAW::id(),
1716 ProfileAWN::id(),
1717 ProfileWAN::id(),
1718 ];
1719 expected_dependents.sort_unstable();
1720 assert_eq!(Age::dependents(), expected_dependents);
1721 }
1722
1723 #[test]
1724 fn test_get_display() {
1725 let mut context = Context::new();
1726 let person = context
1727 .add_entity(with!(Person, POu32(Some(42)), Pu32(22)))
1728 .unwrap();
1729 assert_eq!(
1730 POu32::get_display(&context.get_property::<_, POu32>(person)).to_string(),
1731 "42"
1732 );
1733 assert_eq!(
1734 Pu32::get_display(&context.get_property::<_, Pu32>(person)).to_string(),
1735 "Pu32(22)"
1736 );
1737 let person2 = context
1738 .add_entity(with!(Person, POu32(None), Pu32(11)))
1739 .unwrap();
1740 assert_eq!(
1741 POu32::get_display(&context.get_property::<_, POu32>(person2)).to_string(),
1742 "None"
1743 );
1744 }
1745
1746 #[test]
1747 fn test_option_property_display_patterns() {
1748 let mut context = Context::new();
1749
1750 let some_person = context
1751 .add_entity(with!(
1752 Person,
1753 POu32(Some(42)),
1754 POFloat(Some(3.5)),
1755 POu32Custom(Some(7)),
1756 Pu32(1),
1757 ))
1758 .unwrap();
1759 let none_person = context
1760 .add_entity(with!(
1761 Person,
1762 POu32(None),
1763 POFloat(None),
1764 POu32Custom(None),
1765 Pu32(2)
1766 ))
1767 .unwrap();
1768
1769 assert_eq!(
1770 POu32::get_display(&context.get_property::<_, POu32>(some_person)),
1771 "42"
1772 );
1773 assert_eq!(
1774 POu32::get_display(&context.get_property::<_, POu32>(none_person)),
1775 "None"
1776 );
1777
1778 assert_eq!(
1779 POFloat::get_display(&context.get_property::<_, POFloat>(some_person)),
1780 "3.5"
1781 );
1782 assert_eq!(
1783 POFloat::get_display(&context.get_property::<_, POFloat>(none_person)),
1784 "None"
1785 );
1786
1787 assert_eq!(
1788 POu32Custom::get_display(&context.get_property::<_, POu32Custom>(some_person)),
1789 "custom:7"
1790 );
1791 assert_eq!(
1792 POu32Custom::get_display(&context.get_property::<_, POu32Custom>(none_person)),
1793 "custom:none"
1794 );
1795 }
1796
1797 #[test]
1798 fn test_option_derived_property_display_patterns() {
1799 let mut context = Context::new();
1800
1801 let some_person = context
1802 .add_entity::<Person, _>(with!(Person, Age(42)))
1803 .unwrap();
1804 let none_person = context
1805 .add_entity::<Person, _>(with!(Person, Age(0)))
1806 .unwrap();
1807
1808 assert_eq!(
1809 DerivedMaybeAge::get_display(&context.get_property::<_, DerivedMaybeAge>(some_person)),
1810 "42"
1811 );
1812 assert_eq!(
1813 DerivedMaybeAge::get_display(&context.get_property::<_, DerivedMaybeAge>(none_person)),
1814 "None"
1815 );
1816
1817 assert_eq!(
1818 DerivedMaybeWeight::get_display(
1819 &context.get_property::<_, DerivedMaybeWeight>(some_person)
1820 ),
1821 "42.0"
1822 );
1823 assert_eq!(
1824 DerivedMaybeWeight::get_display(
1825 &context.get_property::<_, DerivedMaybeWeight>(none_person)
1826 ),
1827 "None"
1828 );
1829
1830 assert_eq!(
1831 DerivedMaybeAgeCustom::get_display(
1832 &context.get_property::<_, DerivedMaybeAgeCustom>(some_person)
1833 ),
1834 "derived:42"
1835 );
1836 assert_eq!(
1837 DerivedMaybeAgeCustom::get_display(
1838 &context.get_property::<_, DerivedMaybeAgeCustom>(none_person)
1839 ),
1840 "derived:none"
1841 );
1842 }
1843
1844 #[test]
1845 fn test_debug_trait() {
1846 let property = Pu32(11);
1847 let debug_str = format!("{:?}", property);
1848 assert_eq!(debug_str, "Pu32(11)");
1849
1850 let property = POu32(Some(22));
1851 let debug_str = format!("{:?}", property);
1852 assert_eq!(debug_str, "POu32(Some(22))");
1853 }
1854
1855 #[test]
1856 fn test_define_derived_property_impl_eq_hash() {
1857 let mut values = crate::HashSet::default();
1858 values.insert(DerivedWeight(3.0));
1859 assert!(values.contains(&DerivedWeight(3.0)));
1860 }
1861}