ixa/macros/
entity_impl.rs

1//! Macros to correctly define and implement the `Entity` trait.
2
3/// Defines a zero-sized struct with the right derived traits and implements the `Entity` trait. If you already
4/// have a type defined (struct, enum, etc.), you can use the `impl_entity!` macro instead.
5#[macro_export]
6macro_rules! define_entity {
7    ($entity_name:ident) => {
8        #[allow(unused)]
9        #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
10        pub struct $entity_name;
11
12        impl $entity_name {
13            #[allow(unused)]
14            pub fn new() -> Self {
15                Self::default()
16            }
17        }
18
19        $crate::impl_entity!($entity_name);
20    };
21}
22
23/// Implements the `Entity` trait for the given existing type and defines a type alias
24/// of the form `MyEntityId = EntityId<MyEntity>`. For simple zero-sized types, use the
25/// `define_entity!` macro instead, which will define the struct and derive all the super traits.
26///
27/// This macro ensures the correct implementation of the `Entity` trait. The tricky bit is the implementation of
28/// `Entity::index`, which requires synchronization in multithreaded runtimes. This is an instance of
29/// _correctness via macro_.
30#[macro_export]
31macro_rules! impl_entity {
32    ($entity_name:ident) => {
33        // Alias of the form `MyEntityId = EntityId<MyEntity>`
34        $crate::paste::paste! {
35            #[allow(unused)]
36            pub type [<$entity_name Id>] = $crate::entity::EntityId<$entity_name>;
37        }
38
39        impl $crate::entity::Entity for $entity_name {
40            fn id() -> usize {
41                // This static must be initialized with a compile-time constant expression.
42                // We use `usize::MAX` as a sentinel to mean "uninitialized". This
43                // static variable is shared among all instances of this concrete item type.
44                static INDEX: std::sync::atomic::AtomicUsize =
45                    std::sync::atomic::AtomicUsize::new(usize::MAX);
46
47                // Fast path: already initialized.
48                let index = INDEX.load(std::sync::atomic::Ordering::Relaxed);
49                if index != usize::MAX {
50                    return index;
51                }
52
53                // Slow path: initialize it.
54                $crate::entity::entity_store::initialize_entity_index(&INDEX)
55            }
56
57            fn as_any(&self) -> &dyn std::any::Any {
58                self
59            }
60            fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
61                self
62            }
63        }
64
65        // Using `ctor` to initialize entities at program start-up means we know how many entities
66        // there are at the time any `EntityStore` is created, which means we never have
67        // to mutate `EntityStore` to initialize an `Entity` that hasn't yet been accessed.
68        // (The mutation happens inside of a `OnceCell`, which we can already have ready
69        // when we construct `EntityStore`.) In other words, we could do away with `ctor`
70        // if we were willing to have a mechanism for interior mutability for `EntityStore`.
71        $crate::paste::paste! {
72            $crate::ctor::declarative::ctor!{
73                #[ctor]
74                fn [<_register_entity_$entity_name:snake>]() {
75                    $crate::entity::entity_store::add_to_entity_registry::<$entity_name>();
76                }
77            }
78        }
79    };
80}