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 the empty tuple, allowing spawning of empty entities.
65impl ComponentBundle for () {
66    fn type_ids() -> Vec<TypeId> {
67        Vec::new()
68    }
69
70    fn create_columns() -> HashMap<TypeId, Box<dyn AnyVec>> {
71        HashMap::new()
72    }
73
74    fn update_metadata(
75        _metadata: &mut EntityMetadata,
76        _location: PageIndex,
77        _registry: &ComponentRegistry,
78    ) {
79        // No components, so no location information to update.
80    }
81
82    unsafe fn add_to_page(self, _page: &mut ComponentPage) {
83        // No components to add, so this is a no-op.
84    }
85}
86
87// Implementation for a single component.
88impl<C1: Component> ComponentBundle for C1 {
89    fn type_ids() -> Vec<TypeId> {
90        // The signature is just the TypeId of the single component.
91        vec![TypeId::of::<C1>()]
92    }
93
94    fn create_columns() -> HashMap<TypeId, Box<dyn AnyVec>> {
95        let mut columns: HashMap<TypeId, Box<dyn AnyVec>> = HashMap::new();
96        columns.insert(
97            TypeId::of::<C1>(),
98            Box::new(Vec::<C1>::new()) as Box<dyn AnyVec>,
99        );
100        columns
101    }
102
103    fn update_metadata(
104        metadata: &mut EntityMetadata,
105        location: PageIndex,
106        registry: &ComponentRegistry,
107    ) {
108        // Find the domain for this component type in the registry.
109        if let Some(domain) = registry.domain_of::<Self>() {
110            // Insert or update the location for that domain.
111            metadata.locations.insert(domain, location);
112        }
113        // Note: We might want to log a warning here if a component is not registered.
114    }
115
116    unsafe fn add_to_page(self, page: &mut ComponentPage) {
117        // Get the column and push the single component.
118        page.columns
119            .get_mut(&TypeId::of::<C1>())
120            .unwrap()
121            .as_any_mut()
122            .downcast_mut::<Vec<C1>>()
123            .unwrap()
124            .push(self);
125    }
126}
127
128// Implementation for a 2-component tuple
129impl<C1: Component, C2: Component> ComponentBundle for (C1, C2) {
130    fn type_ids() -> Vec<TypeId> {
131        let mut ids = vec![TypeId::of::<C1>(), TypeId::of::<C2>()];
132        // It's crucial to sort the IDs to have a canonical signature.
133        ids.sort();
134        ids
135    }
136
137    fn create_columns() -> HashMap<TypeId, Box<dyn AnyVec>> {
138        // Create a HashMap to hold the type-erased component vectors.
139        let mut columns: HashMap<TypeId, Box<dyn AnyVec>> = HashMap::new();
140
141        // For each component in the bundle, create an empty `Vec<T>`,
142        // box it, and insert it into the map with its `TypeId` as the key.
143        columns.insert(
144            TypeId::of::<C1>(),
145            Box::new(Vec::<C1>::new()) as Box<dyn AnyVec>,
146        );
147        columns.insert(
148            TypeId::of::<C2>(),
149            Box::new(Vec::<C2>::new()) as Box<dyn AnyVec>,
150        );
151
152        columns
153    }
154
155    fn update_metadata(
156        metadata: &mut EntityMetadata,
157        location: PageIndex,
158        registry: &ComponentRegistry,
159    ) {
160        // Assumption: all components in a bundle belong to the same semantic domain.
161        // We look up the domain of the *first* component type.
162        if let Some(domain) = registry.domain_of::<C1>() {
163            metadata.locations.insert(domain, location);
164        }
165    }
166
167    unsafe fn add_to_page(self, page: &mut ComponentPage) {
168        // --- THIS IS A CRITICAL SAFETY REGION ---
169
170        // 1. Store the TypeIds in local variables to create references with a valid lifetime.
171        let type_id1 = TypeId::of::<C1>();
172        let type_id2 = TypeId::of::<C2>();
173
174        // 2. Assert that we are not trying to mutably alias the same component type.
175        // This is a critical runtime safety check for our logic.
176        assert_ne!(
177            type_id1, type_id2,
178            "Bundles cannot contain duplicate component types."
179        );
180
181        // 3. Get mutable references to both columns simultaneously using the correct, stable API.
182        let [c1_anyvec, c2_anyvec] = page.columns.get_disjoint_mut([&type_id1, &type_id2]);
183
184        // 4. Safely unwrap and downcast each reference.
185        // We can safely unwrap here because the `unsafe` contract of this function
186        // guarantees that the columns exist.
187        let c1_vec = c1_anyvec
188            .unwrap()
189            .as_any_mut()
190            .downcast_mut::<Vec<C1>>()
191            .unwrap();
192        let c2_vec = c2_anyvec
193            .unwrap()
194            .as_any_mut()
195            .downcast_mut::<Vec<C2>>()
196            .unwrap();
197
198        // 5. Push the component data.
199        c1_vec.push(self.0);
200        c2_vec.push(self.1);
201    }
202}
203
204// Implementation for a 3-component tuple.
205impl<C1: Component, C2: Component, C3: Component> ComponentBundle for (C1, C2, C3) {
206    fn type_ids() -> Vec<TypeId> {
207        let mut ids = vec![TypeId::of::<C1>(), TypeId::of::<C2>(), TypeId::of::<C3>()];
208        ids.sort();
209        ids.dedup();
210        ids
211    }
212
213    fn create_columns() -> HashMap<TypeId, Box<dyn AnyVec>> {
214        let mut columns: HashMap<TypeId, Box<dyn AnyVec>> = HashMap::new();
215        columns.insert(
216            TypeId::of::<C1>(),
217            Box::new(Vec::<C1>::new()) as Box<dyn AnyVec>,
218        );
219        columns.insert(
220            TypeId::of::<C2>(),
221            Box::new(Vec::<C2>::new()) as Box<dyn AnyVec>,
222        );
223        columns.insert(
224            TypeId::of::<C3>(),
225            Box::new(Vec::<C3>::new()) as Box<dyn AnyVec>,
226        );
227        columns
228    }
229
230    fn update_metadata(
231        metadata: &mut EntityMetadata,
232        location: PageIndex,
233        registry: &ComponentRegistry,
234    ) {
235        // Assumption: all components in a bundle belong to the same semantic domain.
236        // We look up the domain of the *first* component type.
237        if let Some(domain) = registry.domain_of::<C1>() {
238            metadata.locations.insert(domain, location);
239        }
240    }
241
242    unsafe fn add_to_page(self, page: &mut ComponentPage) {
243        let type_id1 = TypeId::of::<C1>();
244        let type_id2 = TypeId::of::<C2>();
245        let type_id3 = TypeId::of::<C3>();
246        assert_ne!(
247            type_id1, type_id2,
248            "Bundles cannot contain duplicate component types."
249        );
250        assert_ne!(
251            type_id1, type_id3,
252            "Bundles cannot contain duplicate component types."
253        );
254        assert_ne!(
255            type_id2, type_id3,
256            "Bundles cannot contain duplicate component types."
257        );
258
259        let [c1_anyvec, c2_anyvec, c3_anyvec] = page
260            .columns
261            .get_disjoint_mut([&type_id1, &type_id2, &type_id3]);
262
263        c1_anyvec
264            .unwrap()
265            .as_any_mut()
266            .downcast_mut::<Vec<C1>>()
267            .unwrap()
268            .push(self.0);
269        c2_anyvec
270            .unwrap()
271            .as_any_mut()
272            .downcast_mut::<Vec<C2>>()
273            .unwrap()
274            .push(self.1);
275        c3_anyvec
276            .unwrap()
277            .as_any_mut()
278            .downcast_mut::<Vec<C3>>()
279            .unwrap()
280            .push(self.2);
281    }
282}