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 bincode::{Decode, Encode};
21use khora_core::ecs::entity::EntityId;
22
23/// An internal helper trait to perform vector operations on a type-erased `Box<dyn Any>`.
24///
25/// This allows us to call methods like `swap_remove` on component columns without
26/// needing to know their concrete `Vec<T>` type at compile time.
27pub trait AnyVec: Any + Send + Sync {
28    /// Casts the trait object to `&dyn Any`.
29    fn as_any(&self) -> &dyn Any;
30
31    /// Casts the trait object to `&mut dyn Any`.
32    fn as_any_mut(&mut self) -> &mut dyn Any;
33
34    /// Performs a `swap_remove` on the underlying `Vec`, removing the element at `index`.
35    fn swap_remove_any(&mut self, index: usize);
36
37    /// # Safety
38    /// Returns the raw byte slice of the underlying `Vec<T>`.
39    /// The caller must ensure that this byte representation is handled correctly.
40    unsafe fn as_bytes(&self) -> &[u8];
41
42    /// # Safety
43    /// Replaces the contents of the `Vec<T>` with the given raw bytes.
44    /// The caller must guarantee that the bytes represent a valid sequence of `T`
45    /// with the correct size and alignment.
46    unsafe fn set_from_bytes(&mut self, bytes: &[u8]);
47}
48
49// We implement this trait for any `Vec<T>` where T is `'static`.
50impl<T: 'static + Send + Sync> AnyVec for Vec<T> {
51    fn as_any(&self) -> &dyn Any {
52        self
53    }
54
55    fn as_any_mut(&mut self) -> &mut dyn Any {
56        self
57    }
58
59    fn swap_remove_any(&mut self, index: usize) {
60        self.swap_remove(index);
61    }
62
63    unsafe fn as_bytes(&self) -> &[u8] {
64        std::slice::from_raw_parts(
65            self.as_ptr() as *const u8,
66            self.len() * std::mem::size_of::<T>(),
67        )
68    }
69
70    unsafe fn set_from_bytes(&mut self, bytes: &[u8]) {
71        let elem_size = std::mem::size_of::<T>();
72        if elem_size == 0 {
73            return; // Correctly handle Zero-Sized Types.
74        }
75        assert_eq!(
76            bytes.len() % elem_size,
77            0,
78            "Byte slice length is not a multiple of element size"
79        );
80
81        // Calculate the new length and resize the Vec accordingly.
82        let new_len = bytes.len() / elem_size;
83        self.clear();
84        self.reserve(new_len);
85
86        // Perform the copy directly into the Vec's allocated memory.
87        let ptr = self.as_mut_ptr() as *mut u8;
88        std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, bytes.len());
89        self.set_len(new_len);
90    }
91}
92
93/// A logical address pointing to an entity's component data within a specific `ComponentPage`.
94///
95/// This struct is the core of the relational aspect of our ECS. It decouples an entity's
96/// identity from the physical storage of its data by acting as a coordinate.
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
98pub struct PageIndex {
99    /// The unique identifier of the `ComponentPage` that stores the component data.
100    pub page_id: u32,
101    /// The index of the row within the page where this entity's components are stored.
102    pub row_index: u32,
103}
104
105/// A serializable representation of a single `ComponentPage`.
106#[derive(Encode, Decode)]
107pub(crate) struct SerializedPage {
108    /// The unique identifiers of this page.
109    pub(crate) type_names: Vec<String>,
110    /// The list of entities whose component data is stored in this page.
111    pub(crate) entities: Vec<EntityId>,
112    /// The actual serialized component data columns. Each column is a byte vector
113    /// representing the serialized `Vec<T>` for a specific component
114    pub(crate) columns: HashMap<String, Vec<u8>>,
115}
116
117/// A page of memory that stores the component data for multiple entities
118/// in a Structure of Arrays (SoA) layout.
119///
120/// A `ComponentPage` is specialized for a single semantic domain (e.g., physics).
121/// It contains multiple columns, where each column is a `Vec<T>` for a specific
122/// component type `T`. This SoA layout is the key to our high iteration performance,
123/// as it guarantees contiguous data access for native queries.
124pub struct ComponentPage {
125    /// A map from a component's `TypeId` to its actual storage column.
126    /// The `Box<dyn AnyVec>` is a type-erased `Vec<T>` that knows how to
127    /// perform basic vector operations like `swap_remove`.
128    pub(crate) columns: HashMap<TypeId, Box<dyn AnyVec>>,
129
130    /// A list of the `EntityId`s that own the data in each row of this page.
131    /// The entity at `entities[i]` corresponds to the components at `columns[...][i]`.
132    /// This is crucial for reverse lookups, especially during entity despawning.
133    pub(crate) entities: Vec<EntityId>,
134
135    /// The sorted list of `TypeId`s for the components stored in this page.
136    /// This acts as the page's "signature" for matching with bundles. It is
137    /// kept sorted to ensure that the signature is canonical.
138    pub(crate) type_ids: Vec<TypeId>,
139}
140
141impl ComponentPage {
142    /// Adds an entity to this page's entity list.
143    ///
144    /// This method is called by `World::spawn` and is a crucial part of maintaining
145    /// the invariant that the number of rows in the component columns is always
146    /// equal to the number of entities tracked by the page.
147    pub(crate) fn add_entity(&mut self, entity_id: EntityId) {
148        self.entities.push(entity_id);
149    }
150
151    /// Performs a `swap_remove` on a specific row across all component columns
152    /// and the entity list.
153    ///
154    /// This is the core of an O(1) despawn operation. It removes the data for the entity
155    /// at `row_index` by swapping it with the last element in each column and in the
156    /// entity list.
157    ///
158    /// It's the caller's (`World::despawn`) responsibility to update the metadata of
159    /// the entity that was moved from the last row.
160    pub(crate) fn swap_remove_row(&mut self, row_index: u32) {
161        // 1. Remove the corresponding entity ID from the list. `swap_remove` on a Vec
162        // returns the element that was at that index, but we don't need it here.
163        self.entities.swap_remove(row_index as usize);
164
165        // 2. Iterate through all component columns and perform the same swap_remove
166        // on each one, using our `AnyVec` trait.
167        for column in self.columns.values_mut() {
168            column.swap_remove_any(row_index as usize);
169        }
170    }
171
172    /// Returns the number of rows of data (and entities) this page currently stores.
173    #[allow(dead_code)]
174    pub(crate) fn row_count(&self) -> usize {
175        self.entities.len()
176    }
177}