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}