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}