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}