Skip to main content

concinnity_core/ecs/
define_components.rs

1//! The macros that build the runtime half of the component registry.
2//!
3//! `define_components!` is invoked in [`crate::ecs::registry`] over the shared
4//! `for_each_component!` list: it emits the `ComponentTag` discriminants, the
5//! `ComponentAsset` value enum (via the `__define_asset_kind!` helper), the blob
6//! loader, and the `ComponentStorage` / `ComponentSlot` pair, whose storage half
7//! comes from [`define_component_storage!`](crate::define_component_storage).
8//!
9//! The authoring `RegisteredType` registry is built from the same list in
10//! concinnity-world. Systems are registered separately, client-side, by the
11//! `define_systems!` table.
12
13// Internal helper. Resolves an entry's `consumed` flag into the tag that still
14// holds its entities once the world has started: the entry's own tag when no
15// load-time pass drains it, the `consumed: <Type>` substitute when one does but
16// leaves a runtime marker behind, and `None` when nothing survives.
17#[macro_export]
18#[doc(hidden)]
19macro_rules! __cn_surviving_tag {
20    ($variant:ident;) => { Some($crate::ecs::ComponentTag::$variant) };
21    ($variant:ident; consumed: $surviving:ident $($rest:tt)*) => {
22        Some($crate::ecs::ComponentTag::$surviving)
23    };
24    ($variant:ident; consumed $($rest:tt)*) => { None };
25    ($variant:ident; $skip:tt $($rest:tt)*) => { $crate::__cn_surviving_tag!($variant; $($rest)*) };
26}
27
28// Internal helper. Emits the runtime `<Kind>Asset` value enum the ECS stores
29// and the `From<$ty>` conversions. The authoring metadata registry (`<Kind>Type`)
30// is emitted separately so the two can live in different crates.
31#[macro_export]
32#[doc(hidden)]
33macro_rules! __define_asset_kind {
34    (
35        asset_enum: $asset_enum:ident,
36        asset_kind: $kind_variant:ident,
37        $( $variant:ident => $ty:path, $disc:expr_2021 ),+ $(,)?
38    ) => {
39        // One variant per component type; each is named for the component it
40        // wraps, so the list itself is the documentation.
41        /// A loaded component of any registered type.
42        #[derive(Debug)]
43        #[expect(missing_docs, reason = "one variant per component type, each named for the component it wraps")]
44        pub enum $asset_enum {
45            $( $variant($ty) ),+
46        }
47
48        $( impl From<$ty> for $asset_enum { fn from(c: $ty) -> Self { $asset_enum::$variant(c) } } )+
49    };
50}
51
52/// Generate the runtime component registry from the engine's component list:
53/// the `ComponentTag` discriminants, the `ComponentAsset` value enum, and the
54/// `ComponentStorage` / `ComponentSlot` pair the ECS stores rows in.
55///
56/// Each list entry carries a `{ ... }` metadata block the authoring registry
57/// consumes; this macro captures and ignores it.
58#[macro_export]
59macro_rules! define_components {
60    // Only the `stored` group gets a tag, an enum variant, and a column; the
61    // `resource` group is named here solely to mark it, since a resource is
62    // reached by handle rather than stored in one. Each entry's `{ ... }`
63    // metadata block is authoring metadata for `cn_impl_components!` and the
64    // world-side registry; this macro captures and ignores it.
65    (
66        stored: { $( $variant:ident => $ty:path { $($meta:tt)* } ),+ $(,)? },
67        resource: { $( $rvariant:ident => $rty:path { $($rmeta:tt)* } ),+ $(,)? } $(,)?
68    ) => {
69        /// The component type tag: one fieldless variant per component, in list
70        /// order, so each variant's `#[repr(u8)]` discriminant is its list
71        /// position (0, 1, 2, ...). `ComponentTag::$variant as u8` is that tag,
72        /// used both as the on-disk blob discriminant and as the in-memory ECS
73        /// `ComponentId`. The tag is assigned by position, not hand-written, and
74        /// is not a stable on-disk contract: a build regenerates the blob, so the
75        /// blob and the engine that loads it always agree. The authoring
76        /// `RegisteredType` registry derives the same tag from this enum.
77        // One variant per component type, named for that component.
78        #[repr(u8)]
79        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
80        #[expect(missing_docs, reason = "one variant per component type, named for that component")]
81        pub enum ComponentTag {
82            $( $variant ),+
83        }
84
85        impl ComponentTag {
86            /// The registry name of this tag, as a world authors it.
87            pub fn as_str(self) -> &'static str {
88                match self {
89                    $( ComponentTag::$variant => stringify!($variant) ),+
90                }
91            }
92
93
94            /// The tag that still holds this component's entities once the
95            /// world has started, or `None` if nothing does.
96            ///
97            /// A load-time pass drains some columns during `World::start`, so
98            /// they match nothing from the first tick onward. Where such a pass
99            /// leaves a runtime marker behind, this returns that marker's tag:
100            /// `Prop` resolves to `PropInstance`, which decomposition puts on
101            /// every prop's entity. Anything not drained resolves to itself.
102            pub fn surviving_tag(self) -> Option<ComponentTag> {
103                match self {
104                    $( ComponentTag::$variant => $crate::__cn_surviving_tag!($variant; $($meta)*) ),+
105                }
106            }
107
108            /// The tag a component name denotes. Resolves the component names a
109            /// Behavior declares in its `scope` and `queries`.
110            pub fn parse(name: &str) -> Option<ComponentTag> {
111                $(
112                    if name == stringify!($variant) {
113                        return Some(ComponentTag::$variant);
114                    }
115                )+
116                None
117            }
118        }
119
120        $crate::__define_asset_kind! {
121            asset_enum: ComponentAsset,
122            asset_kind: Component,
123            $( $variant => $ty, ComponentTag::$variant as u8 ),+
124        }
125
126        impl ComponentAsset {
127            /// Reconstruct a component from a blob def: dispatch on the tag and
128            /// deserialize the runtime component via `Component::from_baked`
129            /// (every record is baked -- cook already ran the asset -> component
130            /// translation).
131            pub fn from_baked(def: &BlobAssetDef) -> Result<Self, CnResult> {
132                $(
133                    if def.discriminant == ComponentTag::$variant as u8 {
134                        let mut c = <$ty as Component>::from_baked(&def.args_bytes)?;
135                        if let Some(id) = def.name {
136                            <$ty as Component>::inject_name(&mut c, id);
137                        }
138                        return Ok(ComponentAsset::$variant(c));
139                    }
140                )+
141                Err(CnResult::AssetInvalidType)
142            }
143
144            /// Inject a payload locator into the component after construction.
145            /// Delegates to `Component::inject_locator`; a no-op for types
146            /// that don't override that method.
147            pub fn inject_locator(&mut self, locator: PayloadLocator) {
148                match self {
149                    $( ComponentAsset::$variant(c) => c.inject_locator(locator) ),+
150                }
151            }
152        }
153
154        // Per-type runtime storage. The `Column`-backed storage struct, the
155        // `ComponentSlot` access trait, and the generic storage operations
156        // (typed push, drain, mutable access, counts) are generated by
157        // `define_component_storage!` -- shared and engine-agnostic. The
158        // asset-enum dispatch (`push`, `all_defs`) is engine-specific and
159        // added in the impl below.
160        $crate::define_component_storage! {
161            storage: ComponentStorage,
162            slot: ComponentSlot,
163            $( $variant => $ty, ComponentTag::$variant as u8 ),+
164        }
165
166        impl ComponentStorage {
167            /// Dispatch a `ComponentAsset` variant into its typed column via the
168            /// generic typed push (which mints the Entity and stamps the tick).
169            /// Returns the minted Entity so loaders can index it by name.
170            pub fn push(&mut self, asset: ComponentAsset) -> $crate::ecs::Entity {
171                match asset {
172                    $( ComponentAsset::$variant(c) => self.push_typed(c), )+
173                }
174            }
175
176            /// Overwrite the component the asset's variant addresses on
177            /// `entity`, keeping the entity and its other components, and
178            /// stamping the change tick so the frame's readers see it.
179            /// `false` when the entity holds no component of that type.
180            pub fn replace(&mut self, entity: $crate::ecs::Entity, asset: ComponentAsset) -> bool {
181                match asset {
182                    $(
183                        ComponentAsset::$variant(c) => match self.get_mut::<$ty>(entity) {
184                            Some(slot) => {
185                                *slot = c;
186                                true
187                            }
188                            None => false,
189                        },
190                    )+
191                }
192            }
193
194            /// Every entity carrying the component with this tag, in column
195            /// order. Serves the declared-query resolution in BehaviorSystem,
196            /// which selects components by authored name rather than by type.
197            pub fn entities_with_tag(&self, tag: u8) -> &[$crate::ecs::Entity] {
198                $(
199                    if tag == ComponentTag::$variant as u8 {
200                        return self.$variant.entities();
201                    }
202                )+
203                &[]
204            }
205
206            /// How many components of each type are stored: one `(tag, count)`
207            /// entry per populated type, in tag order. The debug WS snapshot
208            /// reports these; nothing re-serializes stored components back to
209            /// defs. Counted rather than listed per instance, so the snapshot
210            /// is sized by the number of component types rather than by the
211            /// world.
212            pub fn component_census(&self) -> ::alloc::vec::Vec<(u8, u32)> {
213                let mut out = ::alloc::vec::Vec::new();
214                $(
215                    let count = self.$variant.len();
216                    if count > 0 {
217                        out.push((ComponentTag::$variant as u8, count as u32));
218                    }
219                )+
220                out
221            }
222        }
223
224        // Which group an entry is in decides whether a world can hold it.
225        $( impl $crate::ecs::RuntimeComponent for $ty {} )+
226        $( impl $crate::ecs::ResourceAsset for $rty {} )+
227    };
228}