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-cook. 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. Applies an entry's `validate: <fn>` clamp to a value of the
29// component's own type, or leaves it alone when the entry declares none. The
30// clamps live in `crate::components::validate`, the same ones the authored JSON
31// path runs, so a typed build and a cooked build land on the same component.
32#[macro_export]
33#[doc(hidden)]
34macro_rules! __cn_validate {
35    ($val:expr;) => { $val };
36    ($val:expr; validate: $f:ident $($r:tt)*) => {
37        $crate::components::validate::$f($val)
38    };
39    ($val:expr; $t:tt $($r:tt)*) => { $crate::__cn_validate!($val; $($r)*) };
40}
41
42// Internal helper. Resolves a resource entry's `resource: <Kind>` flag into the
43// handle space it is assigned into.
44#[macro_export]
45#[doc(hidden)]
46macro_rules! __cn_resource_kind {
47    (resource: $kind:ident $($r:tt)*) => { $crate::ecs::ResourceKind::$kind };
48    ($t:tt $($r:tt)*) => { $crate::__cn_resource_kind!($($r)*) };
49}
50
51// Internal helper. Emits the runtime `<Kind>Asset` value enum the ECS stores
52// and the `From<$ty>` conversions. The authoring metadata registry (`<Kind>Type`)
53// is emitted separately so the two can live in different crates.
54#[macro_export]
55#[doc(hidden)]
56macro_rules! __define_asset_kind {
57    (
58        asset_enum: $asset_enum:ident,
59        asset_kind: $kind_variant:ident,
60        $( $variant:ident => $ty:path, $disc:expr_2021 ),+ $(,)?
61    ) => {
62        // One variant per component type; each is named for the component it
63        // wraps, so the list itself is the documentation.
64        /// A loaded component of any registered type.
65        #[derive(Debug)]
66        #[expect(missing_docs, reason = "one variant per component type, each named for the component it wraps")]
67        pub enum $asset_enum {
68            $( $variant($ty) ),+
69        }
70
71        $( impl From<$ty> for $asset_enum { fn from(c: $ty) -> Self { $asset_enum::$variant(c) } } )+
72    };
73}
74
75/// Generate the runtime component registry from the engine's component list:
76/// the `ComponentTag` discriminants, the `ComponentAsset` value enum, and the
77/// `ComponentStorage` / `ComponentSlot` pair the ECS stores rows in.
78///
79/// Each list entry carries a `{ ... }` metadata block the authoring registry
80/// consumes; this macro captures and ignores it.
81#[macro_export]
82macro_rules! define_components {
83    // Only the `stored` group gets a tag, an enum variant, and a column; the
84    // `resource` group is named here solely to mark it, since a resource is
85    // reached by handle rather than stored in one. Each entry's `{ ... }`
86    // metadata block is authoring metadata for `cn_impl_components!` and the
87    // world-side registry; this macro captures and ignores it.
88    (
89        stored: { $( $variant:ident => $ty:path { $($meta:tt)* } ),+ $(,)? },
90        resource: { $( $rvariant:ident => $rty:path { $($rmeta:tt)* } ),+ $(,)? } $(,)?
91    ) => {
92        /// The component type tag: one fieldless variant per component, in list
93        /// order, so each variant's `#[repr(u8)]` discriminant is its list
94        /// position (0, 1, 2, ...). `ComponentTag::$variant as u8` is that tag,
95        /// used both as the on-disk blob discriminant and as the in-memory ECS
96        /// `ComponentId`. The tag is assigned by position, not hand-written, and
97        /// is not a stable on-disk contract: a build regenerates the blob, so the
98        /// blob and the engine that loads it always agree. The authoring
99        /// `RegisteredType` registry derives the same tag from this enum.
100        // One variant per component type, named for that component.
101        #[repr(u8)]
102        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
103        #[expect(missing_docs, reason = "one variant per component type, named for that component")]
104        pub enum ComponentTag {
105            $( $variant ),+
106        }
107
108        impl ComponentTag {
109            /// The registry name of this tag, as a world authors it.
110            pub fn as_str(self) -> &'static str {
111                match self {
112                    $( ComponentTag::$variant => stringify!($variant) ),+
113                }
114            }
115
116
117            /// The tag that still holds this component's entities once the
118            /// world has started, or `None` if nothing does.
119            ///
120            /// A load-time pass drains some columns during `World::start`, so
121            /// they match nothing from the first tick onward. Where such a pass
122            /// leaves a runtime marker behind, this returns that marker's tag:
123            /// `Prop` resolves to `PropInstance`, which decomposition puts on
124            /// every prop's entity. Anything not drained resolves to itself.
125            pub fn surviving_tag(self) -> Option<ComponentTag> {
126                match self {
127                    $( ComponentTag::$variant => $crate::__cn_surviving_tag!($variant; $($meta)*) ),+
128                }
129            }
130
131            /// The tag a component name denotes. Resolves the component names a
132            /// Behavior declares in its `scope` and `queries`.
133            pub fn parse(name: &str) -> Option<ComponentTag> {
134                $(
135                    if name == stringify!($variant) {
136                        return Some(ComponentTag::$variant);
137                    }
138                )+
139                None
140            }
141        }
142
143        $crate::__define_asset_kind! {
144            asset_enum: ComponentAsset,
145            asset_kind: Component,
146            $( $variant => $ty, ComponentTag::$variant as u8 ),+
147        }
148
149        impl ComponentAsset {
150            /// Reconstruct a component from a blob def: dispatch on the tag and
151            /// deserialize the runtime component via `Component::from_baked`
152            /// (every record is baked -- cook already ran the asset -> component
153            /// translation).
154            pub fn from_baked(def: &BlobAssetDef) -> Result<Self, CnResult> {
155                $(
156                    if def.discriminant == ComponentTag::$variant as u8 {
157                        let mut c = <$ty as Component>::from_baked(&def.args_bytes)?;
158                        if let Some(id) = def.name {
159                            <$ty as Component>::inject_name(&mut c, id);
160                        }
161                        return Ok(ComponentAsset::$variant(c));
162                    }
163                )+
164                Err(CnResult::AssetInvalidType)
165            }
166
167            /// Inject a payload locator into the component after construction.
168            /// Delegates to `Component::inject_locator`; a no-op for types
169            /// that don't override that method.
170            pub fn inject_locator(&mut self, locator: PayloadLocator) {
171                match self {
172                    $( ComponentAsset::$variant(c) => c.inject_locator(locator) ),+
173                }
174            }
175
176            /// Inject the asset's declared identity after construction.
177            /// Delegates to `Component::inject_name`; a no-op for types that
178            /// never look themselves up by id.
179            pub fn inject_name(&mut self, id: $crate::ecs::asset_id::AssetId) {
180                match self {
181                    $( ComponentAsset::$variant(c) => c.inject_name(id) ),+
182                }
183            }
184
185            /// Apply the component's registered validation clamps, the ones a
186            /// cooked world's args pass through on their way to the blob. A
187            /// type declaring no clamp is returned unchanged.
188            pub fn validated(self) -> Self {
189                match self {
190                    $(
191                        ComponentAsset::$variant(c) => ComponentAsset::$variant(
192                            $crate::__cn_validate!(c; $($meta)*)
193                        ),
194                    )+
195                }
196            }
197
198            /// The registry name of the component this value holds.
199            pub fn type_name(&self) -> &'static str {
200                match self {
201                    $( ComponentAsset::$variant(_) => stringify!($variant) ),+
202                }
203            }
204
205            /// The tag of the component this value holds.
206            pub fn tag(&self) -> ComponentTag {
207                match self {
208                    $( ComponentAsset::$variant(_) => ComponentTag::$variant ),+
209                }
210            }
211        }
212
213        impl $crate::ecs::ResourceKind {
214            /// The handle space an asset type name compiles into, or `None`
215            /// when the name is not a resource asset. The names are the
216            /// registry's own, so `parse` and the authoring registry agree on
217            /// what counts as a resource.
218            pub fn parse(name: &str) -> Option<$crate::ecs::ResourceKind> {
219                $(
220                    if name == stringify!($rvariant) {
221                        return Some($crate::__cn_resource_kind!($($rmeta)*));
222                    }
223                )+
224                None
225            }
226        }
227
228        // Per-type runtime storage. The `Column`-backed storage struct, the
229        // `ComponentSlot` access trait, and the generic storage operations
230        // (typed push, drain, mutable access, counts) are generated by
231        // `define_component_storage!` -- shared and engine-agnostic. The
232        // asset-enum dispatch (`push`, `all_defs`) is engine-specific and
233        // added in the impl below.
234        $crate::define_component_storage! {
235            storage: ComponentStorage,
236            slot: ComponentSlot,
237            $( $variant => $ty, ComponentTag::$variant as u8 ),+
238        }
239
240        impl ComponentStorage {
241            /// Dispatch a `ComponentAsset` variant into its typed column via the
242            /// generic typed push (which mints the Entity and stamps the tick).
243            /// Returns the minted Entity so loaders can index it by name.
244            pub fn push(&mut self, asset: ComponentAsset) -> $crate::ecs::Entity {
245                match asset {
246                    $( ComponentAsset::$variant(c) => self.push_typed(c), )+
247                }
248            }
249
250            /// Overwrite the component the asset's variant addresses on
251            /// `entity`, keeping the entity and its other components, and
252            /// stamping the change tick so the frame's readers see it.
253            /// `false` when the entity holds no component of that type.
254            pub fn replace(&mut self, entity: $crate::ecs::Entity, asset: ComponentAsset) -> bool {
255                match asset {
256                    $(
257                        ComponentAsset::$variant(c) => match self.get_mut::<$ty>(entity) {
258                            Some(slot) => {
259                                *slot = c;
260                                true
261                            }
262                            None => false,
263                        },
264                    )+
265                }
266            }
267
268            /// Every entity carrying the component with this tag, in column
269            /// order. Serves the declared-query resolution in BehaviorSystem,
270            /// which selects components by authored name rather than by type.
271            pub fn entities_with_tag(&self, tag: u8) -> &[$crate::ecs::Entity] {
272                $(
273                    if tag == ComponentTag::$variant as u8 {
274                        return self.$variant.entities();
275                    }
276                )+
277                &[]
278            }
279
280            /// How many components of each type are stored: one `(tag, count)`
281            /// entry per populated type, in tag order. The debug WS snapshot
282            /// reports these; nothing re-serializes stored components back to
283            /// defs. Counted rather than listed per instance, so the snapshot
284            /// is sized by the number of component types rather than by the
285            /// world.
286            pub fn component_census(&self) -> ::alloc::vec::Vec<(u8, u32)> {
287                let mut out = ::alloc::vec::Vec::new();
288                $(
289                    let count = self.$variant.len();
290                    if count > 0 {
291                        out.push((ComponentTag::$variant as u8, count as u32));
292                    }
293                )+
294                out
295            }
296        }
297
298        // Which group an entry is in decides whether a world can hold it.
299        $( impl $crate::ecs::RuntimeComponent for $ty {} )+
300        $( impl $crate::ecs::ResourceAsset for $rty {} )+
301    };
302}