khora_data/ecs/bundle.rs
1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::any::TypeId;
16use std::collections::HashMap;
17
18use crate::ecs::component::Component;
19use crate::ecs::entity::EntityMetadata;
20use crate::ecs::page::{AnyVec, ComponentPage, PageIndex};
21use crate::ecs::registry::ComponentRegistry;
22
23/// A trait for any collection of components that can be spawned together as a single unit.
24///
25/// This is a key part of the ECS's public API. It is typically implemented on tuples
26/// of components, like `(Position, Velocity)`. It provides the logic for identifying
27/// its component types and safely writing its data into a `ComponentPage`.
28pub trait ComponentBundle {
29 /// Returns the sorted list of `TypeId`s for the components in this bundle.
30 ///
31 /// This provides a canonical "signature" for the bundle, which is used to find
32 /// a matching `ComponentPage` in the `World`. Sorting is crucial to ensure that
33 /// tuples with the same components but in a different order (e.g., (A, B) vs (B, A))
34 /// are treated as identical.
35 fn type_ids() -> Vec<TypeId>;
36
37 /// Creates the set of empty, type-erased `Vec<T>` columns required to store
38 /// this bundle's components.
39 ///
40 /// This is called by the `World` when a new `ComponentPage` needs to be
41 /// created for a specific bundle layout.
42 fn create_columns() -> HashMap<TypeId, Box<dyn AnyVec>>;
43
44 /// Updates the appropriate fields in an `EntityMetadata` struct to point
45 /// to the location of this bundle's data.
46 ///
47 /// This method is called by `World::spawn` to link an entity to its newly
48 /// created component data.
49 fn update_metadata(
50 metadata: &mut EntityMetadata,
51 location: PageIndex,
52 registry: &ComponentRegistry,
53 );
54
55 /// Adds the components from this bundle into the specified `ComponentPage`.
56 ///
57 /// # Safety
58 /// This function is unsafe because it relies on the caller to guarantee that
59 /// the `ComponentPage` is the correct one for this bundle's exact component layout.
60 /// It performs unsafe downcasting to write to the type-erased `Vec<T>`s.
61 unsafe fn add_to_page(self, page: &mut ComponentPage);
62}
63
64// Implementation for a single component.
65impl<C1: Component> ComponentBundle for C1 {
66 fn type_ids() -> Vec<TypeId> {
67 // The signature is just the TypeId of the single component.
68 vec![TypeId::of::<C1>()]
69 }
70
71 fn create_columns() -> HashMap<TypeId, Box<dyn AnyVec>> {
72 let mut columns: HashMap<TypeId, Box<dyn AnyVec>> = HashMap::new();
73 columns.insert(
74 TypeId::of::<C1>(),
75 Box::new(Vec::<C1>::new()) as Box<dyn AnyVec>,
76 );
77 columns
78 }
79
80 fn update_metadata(
81 metadata: &mut EntityMetadata,
82 location: PageIndex,
83 registry: &ComponentRegistry,
84 ) {
85 // Find the domain for this component type in the registry.
86 if let Some(domain) = registry.domain_of::<Self>() {
87 // Insert or update the location for that domain.
88 metadata.locations.insert(domain, location);
89 }
90 // Note: We might want to log a warning here if a component is not registered.
91 }
92
93 unsafe fn add_to_page(self, page: &mut ComponentPage) {
94 // Get the column and push the single component.
95 page.columns
96 .get_mut(&TypeId::of::<C1>())
97 .unwrap()
98 .as_any_mut()
99 .downcast_mut::<Vec<C1>>()
100 .unwrap()
101 .push(self);
102 }
103}
104
105// Implementation for a 2-component tuple
106impl<C1: Component, C2: Component> ComponentBundle for (C1, C2) {
107 fn type_ids() -> Vec<TypeId> {
108 let mut ids = vec![TypeId::of::<C1>(), TypeId::of::<C2>()];
109 // It's crucial to sort the IDs to have a canonical signature.
110 ids.sort();
111 ids
112 }
113
114 fn create_columns() -> HashMap<TypeId, Box<dyn AnyVec>> {
115 // Create a HashMap to hold the type-erased component vectors.
116 let mut columns: HashMap<TypeId, Box<dyn AnyVec>> = HashMap::new();
117
118 // For each component in the bundle, create an empty `Vec<T>`,
119 // box it, and insert it into the map with its `TypeId` as the key.
120 columns.insert(
121 TypeId::of::<C1>(),
122 Box::new(Vec::<C1>::new()) as Box<dyn AnyVec>,
123 );
124 columns.insert(
125 TypeId::of::<C2>(),
126 Box::new(Vec::<C2>::new()) as Box<dyn AnyVec>,
127 );
128
129 columns
130 }
131
132 fn update_metadata(
133 metadata: &mut EntityMetadata,
134 location: PageIndex,
135 registry: &ComponentRegistry,
136 ) {
137 // Assumption: all components in a bundle belong to the same semantic domain.
138 // We look up the domain of the *first* component type.
139 if let Some(domain) = registry.domain_of::<C1>() {
140 metadata.locations.insert(domain, location);
141 }
142 }
143
144 unsafe fn add_to_page(self, page: &mut ComponentPage) {
145 // --- THIS IS A CRITICAL SAFETY REGION ---
146
147 // 1. Store the TypeIds in local variables to create references with a valid lifetime.
148 let type_id1 = TypeId::of::<C1>();
149 let type_id2 = TypeId::of::<C2>();
150
151 // 2. Assert that we are not trying to mutably alias the same component type.
152 // This is a critical runtime safety check for our logic.
153 assert_ne!(
154 type_id1, type_id2,
155 "Bundles cannot contain duplicate component types."
156 );
157
158 // 3. Get mutable references to both columns simultaneously using the correct, stable API.
159 let [c1_anyvec, c2_anyvec] = page.columns.get_disjoint_mut([&type_id1, &type_id2]);
160
161 // 4. Safely unwrap and downcast each reference.
162 // We can safely unwrap here because the `unsafe` contract of this function
163 // guarantees that the columns exist.
164 let c1_vec = c1_anyvec
165 .unwrap()
166 .as_any_mut()
167 .downcast_mut::<Vec<C1>>()
168 .unwrap();
169 let c2_vec = c2_anyvec
170 .unwrap()
171 .as_any_mut()
172 .downcast_mut::<Vec<C2>>()
173 .unwrap();
174
175 // 5. Push the component data.
176 c1_vec.push(self.0);
177 c2_vec.push(self.1);
178 }
179}
180
181// Implementation for a 3-component tuple.
182impl<C1: Component, C2: Component, C3: Component> ComponentBundle for (C1, C2, C3) {
183 fn type_ids() -> Vec<TypeId> {
184 let mut ids = vec![TypeId::of::<C1>(), TypeId::of::<C2>(), TypeId::of::<C3>()];
185 ids.sort();
186 ids.dedup();
187 ids
188 }
189
190 fn create_columns() -> HashMap<TypeId, Box<dyn AnyVec>> {
191 let mut columns: HashMap<TypeId, Box<dyn AnyVec>> = HashMap::new();
192 columns.insert(
193 TypeId::of::<C1>(),
194 Box::new(Vec::<C1>::new()) as Box<dyn AnyVec>,
195 );
196 columns.insert(
197 TypeId::of::<C2>(),
198 Box::new(Vec::<C2>::new()) as Box<dyn AnyVec>,
199 );
200 columns.insert(
201 TypeId::of::<C3>(),
202 Box::new(Vec::<C3>::new()) as Box<dyn AnyVec>,
203 );
204 columns
205 }
206
207 fn update_metadata(
208 metadata: &mut EntityMetadata,
209 location: PageIndex,
210 registry: &ComponentRegistry,
211 ) {
212 // Assumption: all components in a bundle belong to the same semantic domain.
213 // We look up the domain of the *first* component type.
214 if let Some(domain) = registry.domain_of::<C1>() {
215 metadata.locations.insert(domain, location);
216 }
217 }
218
219 unsafe fn add_to_page(self, page: &mut ComponentPage) {
220 let type_id1 = TypeId::of::<C1>();
221 let type_id2 = TypeId::of::<C2>();
222 let type_id3 = TypeId::of::<C3>();
223 assert_ne!(
224 type_id1, type_id2,
225 "Bundles cannot contain duplicate component types."
226 );
227 assert_ne!(
228 type_id1, type_id3,
229 "Bundles cannot contain duplicate component types."
230 );
231 assert_ne!(
232 type_id2, type_id3,
233 "Bundles cannot contain duplicate component types."
234 );
235
236 let [c1_anyvec, c2_anyvec, c3_anyvec] = page
237 .columns
238 .get_disjoint_mut([&type_id1, &type_id2, &type_id3]);
239
240 c1_anyvec
241 .unwrap()
242 .as_any_mut()
243 .downcast_mut::<Vec<C1>>()
244 .unwrap()
245 .push(self.0);
246 c2_anyvec
247 .unwrap()
248 .as_any_mut()
249 .downcast_mut::<Vec<C2>>()
250 .unwrap()
251 .push(self.1);
252 c3_anyvec
253 .unwrap()
254 .as_any_mut()
255 .downcast_mut::<Vec<C3>>()
256 .unwrap()
257 .push(self.2);
258 }
259}