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}