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