khora_data/ecs/world.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;
16
17use crate::ecs::{
18 entity::EntityMetadata,
19 page::{ComponentPage, PageIndex},
20 query::{Query, WorldQuery},
21 registry::ComponentRegistry,
22 Children, Component, ComponentBundle, EntityId, GlobalTransform, Parent, SemanticDomain,
23 Transform,
24};
25
26/// The central container for the entire ECS, holding all entities, components, and metadata.
27///
28/// The `World` orchestrates the CRPECS architecture. It owns all ECS data and provides the main
29/// API for creating and destroying entities, and for querying their component data.
30pub struct World {
31 /// A dense list of metadata for every entity slot that has ever been created.
32 /// The index into this vector is used as the `index` part of an `EntityId`.
33 /// The `Option<EntityMetadata>` is `None` if the entity slot is currently free.
34 pub(crate) entities: Vec<(EntityId, Option<EntityMetadata>)>,
35
36 /// A list of all allocated `ComponentPage`s, where component data is stored.
37 /// A `page_id` in a `PageIndex` corresponds to an index in this vector.
38 pub(crate) pages: Vec<ComponentPage>,
39
40 /// A list of entity indices that have been freed by `despawn` and are available
41 /// for reuse by `spawn`. This recycling mechanism keeps the `entities` vector dense.
42 pub(crate) freed_entities: Vec<u32>,
43
44 /// The registry that maps component types to their storage domains.
45 registry: ComponentRegistry,
46 // We will add more fields here later, such as a resource manager
47 // or an entity ID allocator to manage recycled generations.
48}
49
50impl World {
51 /// (Internal) Allocates a new or recycled `EntityId` and reserves its metadata slot.
52 ///
53 /// This is the first step in the spawning process. It prioritizes recycling
54 /// freed entity indices to keep the entity list dense. If no indices are free,
55 /// it creates a new entry. It also handles incrementing the generation count
56 /// for recycled entities to prevent the ABA problem.
57 fn create_entity(&mut self) -> EntityId {
58 if let Some(index) = self.freed_entities.pop() {
59 // --- Recycle an existing slot ---
60 let index = index as usize;
61 let (id_slot, metadata_slot) = &mut self.entities[index];
62 id_slot.generation += 1;
63 *metadata_slot = Some(EntityMetadata::default());
64 *id_slot
65 } else {
66 // --- Allocate a new slot ---
67 let index = self.entities.len() as u32;
68 let new_id = EntityId {
69 index,
70 generation: 0,
71 };
72 self.entities
73 .push((new_id, Some(EntityMetadata::default())));
74 new_id
75 }
76 }
77
78 /// (Internal) Finds a page suitable for the given `ComponentBundle`, or creates one if none exists.
79 ///
80 /// A page is considered suitable if it stores the exact same set of component types
81 /// as the bundle. This method iterates through existing pages to find a match based
82 /// on their canonical type signatures. If no match is found, it allocates a new `ComponentPage`.
83 ///
84 /// Returns the `page_id` of the suitable page.
85 fn find_or_create_page_for_bundle<B: ComponentBundle>(&mut self) -> u32 {
86 // 1. Get the canonical signature for the bundle we want to insert.
87 let bundle_type_ids = B::type_ids();
88
89 // 2. --- Search for an existing page ---
90 // Iterate through all currently allocated pages.
91 for (page_id, page) in self.pages.iter().enumerate() {
92 if page.type_ids == bundle_type_ids {
93 return page_id as u32;
94 }
95 }
96
97 // 3. --- Create a new page if none was found ---
98 // At this point, the loop has finished and found no match.
99 // The ID for the new page will be the current number of pages.
100 let new_page_id = self.pages.len() as u32;
101 let new_page = ComponentPage {
102 type_ids: bundle_type_ids,
103 columns: B::create_columns(),
104 entities: Vec::new(),
105 };
106 self.pages.push(new_page);
107 new_page_id
108 }
109
110 /// (Internal) A helper function to handle the `swap_remove` logic for a single component group.
111 ///
112 /// It removes component data from the specified page location. If another entity's
113 /// data is moved during this process, its metadata is updated with its new location.
114 fn remove_from_page(
115 &mut self,
116 entity_to_despawn: EntityId,
117 location: PageIndex,
118 domain: SemanticDomain,
119 ) {
120 let page = &mut self.pages[location.page_id as usize];
121 if page.entities.is_empty() {
122 return;
123 }
124
125 let last_entity_in_page = *page.entities.last().unwrap();
126 page.swap_remove_row(location.row_index);
127
128 if last_entity_in_page != entity_to_despawn {
129 let (_id, metadata_opt) = &mut self.entities[last_entity_in_page.index as usize];
130 let metadata = metadata_opt.as_mut().unwrap();
131 metadata.locations.insert(domain, location);
132 }
133 }
134
135 /// Creates a new, empty `World` with pre-registered internal component types.
136 pub fn new() -> Self {
137 let mut world = Self {
138 entities: Vec::new(),
139 pages: Vec::new(),
140 freed_entities: Vec::new(),
141 registry: ComponentRegistry::default(),
142 };
143 world.register_component::<Transform>(SemanticDomain::Spatial);
144 world.register_component::<GlobalTransform>(SemanticDomain::Spatial);
145 world.register_component::<Parent>(SemanticDomain::Spatial);
146 world.register_component::<Children>(SemanticDomain::Spatial);
147 world
148 }
149
150 /// Spawns a new entity with the given bundle of components.
151 ///
152 /// This is the primary method for creating entities. It orchestrates the entire process:
153 /// 1. Allocates a new `EntityId`.
154 /// 2. Finds or creates a suitable `ComponentPage` for the component bundle.
155 /// 3. Pushes the component data into the page's columns.
156 /// 4. Updates the entity's metadata to point to the new data's location.
157 ///
158 /// Returns the `EntityId` of the newly created entity.
159 pub fn spawn<B: ComponentBundle>(&mut self, bundle: B) -> EntityId {
160 // Step 1: Allocate a new EntityId.
161 let entity_id = self.create_entity();
162
163 // Step 2: Find or create a page for this bundle.
164 let page_id = self.find_or_create_page_for_bundle::<B>();
165
166 // --- Step 3: Push component data into the page. ---
167 let row_index;
168 {
169 let page = &mut self.pages[page_id as usize];
170 row_index = page.entities.len() as u32;
171 unsafe {
172 bundle.add_to_page(page);
173 }
174 page.add_entity(entity_id);
175 }
176
177 // --- Step 4: Update the entity's metadata. ---
178 let location = PageIndex { page_id, row_index };
179 let (_id, metadata_opt) = &mut self.entities[entity_id.index as usize];
180 let metadata = metadata_opt.as_mut().unwrap();
181 B::update_metadata(metadata, location, &self.registry);
182 entity_id
183 }
184
185 /// Despawns an entity, removing all its components and freeing its ID for recycling.
186 ///
187 /// This method performs the following steps:
188 /// 1. Verifies that the `EntityId` is valid by checking its index and generation.
189 /// 2. Removes the entity's component data from all pages where it is stored.
190 /// 3. Marks the entity's metadata slot as vacant and adds its index to the free list.
191 ///
192 /// Returns `true` if the entity was valid and despawned, `false` otherwise.
193 pub fn despawn(&mut self, entity_id: EntityId) -> bool {
194 // Step 1: Validate the EntityId.
195 // First, check if the index is even valid for our entities Vec.
196 if entity_id.index as usize >= self.entities.len() {
197 return false;
198 }
199
200 // Get the data at the slot.
201 let (id_in_world, metadata_slot) = &self.entities[entity_id.index as usize];
202
203 // An ID is valid if its generation matches the one in the world,
204 // AND if the metadata slot is currently occupied (`is_some`).
205 if id_in_world.generation != entity_id.generation || metadata_slot.is_none() {
206 return false;
207 }
208
209 // --- At this point, the ID is valid. ---
210
211 // Step 2: Take the metadata out of the slot, leaving it `None`.
212 // This is what officially "kills" the entity.
213 let metadata = self.entities[entity_id.index as usize].1.take().unwrap();
214 self.freed_entities.push(entity_id.index);
215
216 // --- Step 3: Iterate over the entity's component locations and remove them ---
217 for (domain, location) in metadata.locations {
218 self.remove_from_page(entity_id, location, domain);
219 }
220 true
221 }
222
223 /// Creates an iterator that queries the world for entities matching a set of components and filters.
224 ///
225 /// This is the primary method for reading and writing data in the ECS. The query `Q`
226 /// is specified as a tuple via turbofish syntax. It can include component references
227 /// (e.g., `&Position`, `&mut Velocity`) and filters (e.g., `Without<Parent>`).
228 ///
229 /// This method is very cheap to call. It performs an efficient search to identify all
230 /// `ComponentPage`s that satisfy the query's criteria. The returned iterator then
231 /// efficiently iterates over the data in only those pages.
232 ///
233 /// # Examples
234 ///
235 /// ```rust,ignore
236 /// // Find all entities with a `Transform` and `GlobalTransform`.
237 /// for (transform, global) in world.query::<(&Transform, &GlobalTransform)>() {
238 /// // ...
239 /// }
240 ///
241 /// // Find all root entities (those with a `Transform` but without a `Parent`).
242 /// for (transform,) in world.query::<(&Transform, Without<Parent>)>() {
243 /// // ...
244 /// }
245 /// ```
246 pub fn query<'a, Q: WorldQuery>(&'a self) -> Query<'a, Q> {
247 // 1. Get the component and filter signatures from the query type.
248 let query_type_ids = Q::type_ids();
249 let without_type_ids = Q::without_type_ids();
250
251 // 2. Find all pages that match the query's criteria.
252 let mut matching_page_indices = Vec::new();
253 'page_loop: for (page_id, page) in self.pages.iter().enumerate() {
254 // --- Filtering Logic ---
255
256 // A) Check for required components.
257 // The page must contain ALL component types requested by the query.
258 for required_type in &query_type_ids {
259 // `binary_search` is fast on the sorted `page.type_ids` vector.
260 if page.type_ids.binary_search(required_type).is_err() {
261 continue 'page_loop; // This page is missing a required component, skip it.
262 }
263 }
264
265 // B) Check for excluded components.
266 // The page must NOT contain ANY component types from the `without` filter.
267 for excluded_type in &without_type_ids {
268 if page.type_ids.binary_search(excluded_type).is_ok() {
269 continue 'page_loop; // This page contains an excluded component, skip it.
270 }
271 }
272
273 // If we reach this point, the page is a match.
274 matching_page_indices.push(page_id as u32);
275 }
276
277 // 3. Construct and return the `Query` iterator.
278 Query::new(self, matching_page_indices)
279 }
280
281 /// Gets a mutable reference to a single component `T` for a given entity.
282 ///
283 /// This provides direct, "random" access to a component, which can be less
284 /// performant than querying but is useful for targeted modifications.
285 ///
286 /// # Returns
287 ///
288 /// `None` if the entity is not alive or does not have the requested component.
289 pub fn get_mut<T: Component>(&mut self, entity_id: EntityId) -> Option<&mut T> {
290 // 1. Validate the entity ID.
291 let (id_in_world, metadata_opt) = self.entities.get(entity_id.index as usize)?;
292 if id_in_world.generation != entity_id.generation || metadata_opt.is_none() {
293 return None;
294 }
295 let metadata = metadata_opt.as_ref().unwrap();
296
297 // 2. Use the registry to find the component's domain and its location.
298 let domain = self.registry.domain_of::<T>()?;
299 let location = metadata.locations.get(&domain)?;
300
301 // 3. Get the component data from the page.
302 let type_id = TypeId::of::<T>();
303 let page = self.pages.get_mut(location.page_id as usize)?;
304 let column = page.columns.get_mut(&type_id)?;
305 let vec = column.as_any_mut().downcast_mut::<Vec<T>>()?;
306
307 vec.get_mut(location.row_index as usize)
308 }
309
310 /// Gets an immutable reference to a single component `T` for a given entity.
311 ///
312 /// This provides direct, "random" access to a component.
313 ///
314 /// # Returns
315 ///
316 /// `None` if the entity is not alive or does not have the requested component.
317 pub fn get<T: Component>(&self, entity_id: EntityId) -> Option<&T> {
318 // 1. Validate the entity ID.
319 let (id_in_world, metadata_opt) = self.entities.get(entity_id.index as usize)?;
320 if id_in_world.generation != entity_id.generation || metadata_opt.is_none() {
321 return None;
322 }
323 let metadata = metadata_opt.as_ref().unwrap();
324
325 // 2. Use the registry to find the component's domain and its location.
326 let domain = self.registry.domain_of::<T>()?;
327 let location = metadata.locations.get(&domain)?;
328
329 // 3. Get the component data from the page.
330 let type_id = TypeId::of::<T>();
331 let page = self.pages.get(location.page_id as usize)?;
332
333 // 4. Return the immutable reference.
334 let vec = page
335 .columns
336 .get(&type_id)?
337 .as_any()
338 .downcast_ref::<Vec<T>>()?;
339 vec.get(location.row_index as usize)
340 }
341
342 /// Registers a component type with a specific semantic domain.
343 ///
344 /// This is a crucial setup step. Before a component of type `T` can be used
345 /// in a bundle, it must be registered with the world to define which semantic
346 /// page group its data will be stored in.
347 pub fn register_component<T: Component>(&mut self, domain: SemanticDomain) {
348 self.registry.register::<T>(domain);
349 }
350}
351
352impl Default for World {
353 /// Creates a new, empty `World` via `World::new()`.
354 fn default() -> Self {
355 Self::new()
356 }
357}