khora_data/ecs/
page.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::{
16    any::{Any, TypeId},
17    collections::HashMap,
18};
19
20use crate::ecs::EntityId;
21
22/// An internal helper trait to perform vector operations on a type-erased `Box<dyn Any>`.
23///
24/// This allows us to call methods like `swap_remove` on component columns without
25/// needing to know their concrete `Vec<T>` type at compile time.
26pub trait AnyVec {
27    /// Casts the trait object to `&dyn Any`.
28    fn as_any(&self) -> &dyn Any;
29
30    /// Casts the trait object to `&mut dyn Any`.
31    fn as_any_mut(&mut self) -> &mut dyn Any;
32
33    /// Performs a `swap_remove` on the underlying `Vec`, removing the element at `index`.
34    fn swap_remove_any(&mut self, index: usize);
35}
36
37// We implement this trait for any `Vec<T>` where T is `'static`.
38impl<T: 'static> AnyVec for Vec<T> {
39    fn as_any(&self) -> &dyn Any {
40        self
41    }
42
43    fn as_any_mut(&mut self) -> &mut dyn Any {
44        self
45    }
46
47    fn swap_remove_any(&mut self, index: usize) {
48        self.swap_remove(index);
49    }
50}
51
52/// A logical address pointing to an entity's component data within a specific `ComponentPage`.
53///
54/// This struct is the core of the relational aspect of our ECS. It decouples an entity's
55/// identity from the physical storage of its data by acting as a coordinate.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub struct PageIndex {
58    /// The unique identifier of the `ComponentPage` that stores the component data.
59    pub page_id: u32,
60    /// The index of the row within the page where this entity's components are stored.
61    pub row_index: u32,
62}
63
64/// A page of memory that stores the component data for multiple entities
65/// in a Structure of Arrays (SoA) layout.
66///
67/// A `ComponentPage` is specialized for a single semantic domain (e.g., physics).
68/// It contains multiple columns, where each column is a `Vec<T>` for a specific
69/// component type `T`. This SoA layout is the key to our high iteration performance,
70/// as it guarantees contiguous data access for native queries.
71pub struct ComponentPage {
72    /// A map from a component's `TypeId` to its actual storage column.
73    /// The `Box<dyn AnyVec>` is a type-erased `Vec<T>` that knows how to
74    /// perform basic vector operations like `swap_remove`.
75    pub(crate) columns: HashMap<TypeId, Box<dyn AnyVec>>,
76
77    /// A list of the `EntityId`s that own the data in each row of this page.
78    /// The entity at `entities[i]` corresponds to the components at `columns[...][i]`.
79    /// This is crucial for reverse lookups, especially during entity despawning.
80    pub(crate) entities: Vec<EntityId>,
81
82    /// The sorted list of `TypeId`s for the components stored in this page.
83    /// This acts as the page's "signature" for matching with bundles. It is
84    /// kept sorted to ensure that the signature is canonical.
85    pub(crate) type_ids: Vec<TypeId>,
86}
87
88impl ComponentPage {
89    /// Adds an entity to this page's entity list.
90    ///
91    /// This method is called by `World::spawn` and is a crucial part of maintaining
92    /// the invariant that the number of rows in the component columns is always
93    /// equal to the number of entities tracked by the page.
94    pub(crate) fn add_entity(&mut self, entity_id: EntityId) {
95        self.entities.push(entity_id);
96    }
97
98    /// Performs a `swap_remove` on a specific row across all component columns
99    /// and the entity list.
100    ///
101    /// This is the core of an O(1) despawn operation. It removes the data for the entity
102    /// at `row_index` by swapping it with the last element in each column and in the
103    /// entity list.
104    ///
105    /// It's the caller's (`World::despawn`) responsibility to update the metadata of
106    /// the entity that was moved from the last row.
107    pub(crate) fn swap_remove_row(&mut self, row_index: u32) {
108        // 1. Remove the corresponding entity ID from the list. `swap_remove` on a Vec
109        // returns the element that was at that index, but we don't need it here.
110        self.entities.swap_remove(row_index as usize);
111
112        // 2. Iterate through all component columns and perform the same swap_remove
113        // on each one, using our `AnyVec` trait.
114        for column in self.columns.values_mut() {
115            column.swap_remove_any(row_index as usize);
116        }
117    }
118
119    /// Returns the number of rows of data (and entities) this page currently stores.
120    #[allow(dead_code)]
121    pub(crate) fn row_count(&self) -> usize {
122        self.entities.len()
123    }
124}