khora_data/ecs/query.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 crate::ecs::{
16 page::{AnyVec, ComponentPage},
17 Component, EntityId, World,
18};
19use std::{any::TypeId, marker::PhantomData};
20
21// ------------------------- //
22// ---- WorldQuery Part ---- //
23// ------------------------- //
24
25/// A trait implemented by types that can be used to query data from the `World`.
26///
27/// This "sealed" trait provides the necessary information for the query engine to
28/// find the correct pages and safely access component data. It is implemented for
29/// component references (`&T`, `&mut T`), `EntityId`, filters like `Without<T>`,
30/// and tuples of other `WorldQuery` types.
31pub trait WorldQuery {
32 /// The type of item that the query iterator will yield (e.g., `(&'a Position, &'a mut Velocity)`).
33 type Item<'a>;
34
35 /// Returns the sorted list of `TypeId`s for the components to be INCLUDED in the query.
36 /// This signature is used to find `ComponentPage`s that contain all these components.
37 fn type_ids() -> Vec<TypeId>;
38
39 /// Returns the sorted list of `TypeId`s for components to be EXCLUDED from the query.
40 /// Used to filter out pages that contain these components.
41 fn without_type_ids() -> Vec<TypeId> {
42 Vec::new()
43 }
44
45 /// Fetches the query's item from a specific row in a `ComponentPage`.
46 ///
47 /// # Safety
48 ///
49 /// This function is unsafe because the caller (the `Query` iterator) must guarantee
50 /// several invariants:
51 /// 1. The page pointed to by `page_ptr` is valid and matches the query's signature.
52 /// 2. `row_index` is a valid index within the page's columns.
53 /// 3. Aliasing rules are not violated (e.g., no two `&mut T` to the same data).
54 unsafe fn fetch<'a>(page_ptr: *const ComponentPage, row_index: usize) -> Self::Item<'a>;
55}
56
57// Implementation for a query of a single, immutable component reference.
58impl<T: Component> WorldQuery for &T {
59 type Item<'a> = &'a T;
60
61 fn type_ids() -> Vec<TypeId> {
62 vec![TypeId::of::<T>()]
63 }
64
65 /// Fetches a reference to the component `T` from the specified row.
66 ///
67 /// # Safety
68 /// The caller MUST guarantee that the page contains a column for component `T`
69 /// and that `row_index` is in bounds.
70 unsafe fn fetch<'a>(page_ptr: *const ComponentPage, row_index: usize) -> Self::Item<'a> {
71 // 1. Get a reference to the `ComponentPage`.
72 let page = &*page_ptr;
73
74 // 2. Get the type-erased column for the component `T`.
75 // We can unwrap because the caller guarantees the column exists.
76 let column: &dyn AnyVec = &**page.columns.get(&TypeId::of::<T>()).unwrap();
77
78 // 3. Downcast the column to its concrete `Vec<T>` type.
79 // First, cast to `&dyn Any`, then `downcast_ref`.
80 let vec: &Vec<T> = column.as_any().downcast_ref::<Vec<T>>().unwrap();
81
82 // 4. Get the component from the vector at the specified row.
83 // We use `get_unchecked` for performance, as the caller guarantees the
84 // index is in bounds. This avoids a bounds check.
85 vec.get_unchecked(row_index)
86 }
87}
88
89// Implementation for a query of a single, mutable component reference.
90impl<T: Component> WorldQuery for &mut T {
91 type Item<'a> = &'a mut T;
92
93 fn type_ids() -> Vec<TypeId> {
94 vec![TypeId::of::<T>()]
95 }
96
97 /// Fetches a mutable reference to the component `T` from the specified row.
98 ///
99 /// # Safety
100 /// The caller MUST guarantee that:
101 /// 1. The page contains a column for component `T`.
102 /// 2. `row_index` is in bounds.
103 /// 3. No other mutable reference to this specific component exists at the same time.
104 /// The query engine must enforce this.
105 unsafe fn fetch<'a>(page_ptr: *const ComponentPage, row_index: usize) -> Self::Item<'a> {
106 // UNSAFE: We cast the const pointer to a mutable one.
107 // This is safe ONLY if the query engine guarantees no other access.
108 let page = &mut *(page_ptr as *mut ComponentPage);
109 let column = page.columns.get_mut(&TypeId::of::<T>()).unwrap();
110 let vec = column.as_any_mut().downcast_mut::<Vec<T>>().unwrap();
111 vec.get_unchecked_mut(row_index)
112 }
113}
114
115// Implementation for a query of a 2-item tuple.
116// This is now generic over any two types that implement `WorldQuery`.
117impl<Q1: WorldQuery, Q2: WorldQuery> WorldQuery for (Q1, Q2) {
118 type Item<'a> = (Q1::Item<'a>, Q2::Item<'a>);
119
120 fn type_ids() -> Vec<TypeId> {
121 let mut ids = Q1::type_ids();
122 ids.extend(Q2::type_ids());
123 // Sorting is crucial to maintain a canonical signature.
124 ids.sort();
125 // We also remove duplicates, in case of queries like `(&T, &T)`.
126 ids.dedup();
127 ids
128 }
129
130 fn without_type_ids() -> Vec<TypeId> {
131 let mut ids = Q1::without_type_ids();
132 ids.extend(Q2::without_type_ids());
133 ids.sort();
134 ids.dedup();
135 ids
136 }
137
138 unsafe fn fetch<'a>(page_ptr: *const ComponentPage, row_index: usize) -> Self::Item<'a> {
139 // Fetch data for each part of the tuple individually.
140 let item1 = Q1::fetch(page_ptr, row_index);
141 let item2 = Q2::fetch(page_ptr, row_index);
142 (item1, item2)
143 }
144}
145
146// Implementation for a query of a 3-item tuple.
147impl<Q1: WorldQuery, Q2: WorldQuery, Q3: WorldQuery> WorldQuery for (Q1, Q2, Q3) {
148 type Item<'a> = (Q1::Item<'a>, Q2::Item<'a>, Q3::Item<'a>);
149
150 fn type_ids() -> Vec<TypeId> {
151 let mut ids = Q1::type_ids();
152 ids.extend(Q2::type_ids());
153 ids.extend(Q3::type_ids());
154 ids.sort();
155 ids.dedup();
156 ids
157 }
158
159 fn without_type_ids() -> Vec<TypeId> {
160 let mut ids = Q1::without_type_ids();
161 ids.extend(Q2::without_type_ids());
162 ids.extend(Q3::without_type_ids());
163 ids.sort();
164 ids.dedup();
165 ids
166 }
167
168 unsafe fn fetch<'a>(page_ptr: *const ComponentPage, row_index: usize) -> Self::Item<'a> {
169 // Fetch data for each part of the tuple individually.
170 let item1 = Q1::fetch(page_ptr, row_index);
171 let item2 = Q2::fetch(page_ptr, row_index);
172 let item3 = Q3::fetch(page_ptr, row_index);
173 (item1, item2, item3)
174 }
175}
176
177// Implementation for a query of a 4-item tuple.
178impl<Q1: WorldQuery, Q2: WorldQuery, Q3: WorldQuery, Q4: WorldQuery> WorldQuery
179 for (Q1, Q2, Q3, Q4)
180{
181 type Item<'a> = (Q1::Item<'a>, Q2::Item<'a>, Q3::Item<'a>, Q4::Item<'a>);
182
183 fn type_ids() -> Vec<TypeId> {
184 let mut ids = Q1::type_ids();
185 ids.extend(Q2::type_ids());
186 ids.extend(Q3::type_ids());
187 ids.extend(Q4::type_ids());
188 ids.sort();
189 ids.dedup();
190 ids
191 }
192
193 fn without_type_ids() -> Vec<TypeId> {
194 let mut ids = Q1::without_type_ids();
195 ids.extend(Q2::without_type_ids());
196 ids.extend(Q3::without_type_ids());
197 ids.extend(Q4::without_type_ids());
198 ids.sort();
199 ids.dedup();
200 ids
201 }
202
203 unsafe fn fetch<'a>(page_ptr: *const ComponentPage, row_index: usize) -> Self::Item<'a> {
204 (
205 Q1::fetch(page_ptr, row_index),
206 Q2::fetch(page_ptr, row_index),
207 Q3::fetch(page_ptr, row_index),
208 Q4::fetch(page_ptr, row_index),
209 )
210 }
211}
212
213// To fetch an entity's ID, we need to access the page's own entity list.
214// We also need to query for the entity ID itself.
215impl WorldQuery for EntityId {
216 type Item<'a> = EntityId;
217
218 // EntityId is not a component, it doesn't have a TypeId in the page signature.
219 fn type_ids() -> Vec<TypeId> {
220 Vec::new()
221 }
222
223 // It doesn't have a `without` filter either.
224 // The default implementation is sufficient.
225
226 unsafe fn fetch<'a>(page_ptr: *const ComponentPage, row_index: usize) -> Self::Item<'a> {
227 let page = &*page_ptr;
228 // The entity ID is fetched from the page's own list of entities.
229 *page.entities.get_unchecked(row_index)
230 }
231}
232
233// -------------------- //
234// ---- Query Part ---- //
235// -------------------- //
236
237/// An iterator that yields the results of a `WorldQuery`.
238///
239/// This struct is created by the [`World::query()`] method. It holds a reference
240/// to the world and iterates through all `ComponentPage`s that match the query's
241/// signature, fetching the requested data for each entity in those pages.
242pub struct Query<'a, Q: WorldQuery> {
243 /// A raw pointer to the world. Using a pointer avoids lifetime variance issues
244 /// and gives us the flexibility to provide either mutable or immutable access.
245 world_ptr: *const World,
246
247 /// The list of page indices that match this query.
248 matching_page_indices: Vec<u32>,
249
250 /// The index of the current page we are iterating through.
251 current_page_index: usize,
252
253 /// The index of the next row to fetch within the current page.
254 current_row_index: usize,
255
256 /// A marker to associate this iterator with the lifetime `'a` and the query type `Q`.
257 /// It tells Rust that our iterator behaves as if it's borrowing `&'a Q` from the world.
258 _phantom: PhantomData<(&'a (), Q)>,
259}
260
261impl<'a, Q: WorldQuery> Query<'a, Q> {
262 /// (Internal) Creates a new `Query` iterator.
263 ///
264 /// This is intended to be called only by `World::query()`.
265 /// It takes the world and the list of matching pages as arguments.
266 pub(crate) fn new(world: &'a World, matching_page_indices: Vec<u32>) -> Self {
267 Self {
268 world_ptr: world as *const _,
269 matching_page_indices,
270 current_page_index: 0,
271 current_row_index: 0,
272 _phantom: PhantomData,
273 }
274 }
275}
276
277impl<'a, Q: WorldQuery> Iterator for Query<'a, Q> {
278 /// The type of item yielded by this iterator, as defined by the `WorldQuery` trait.
279 type Item = Q::Item<'a>;
280
281 /// Advances the iterator and returns the next item.
282 fn next(&mut self) -> Option<Self::Item> {
283 loop {
284 // 1. Check if there are any pages left to iterate through.
285 if self.current_page_index >= self.matching_page_indices.len() {
286 return None; // No more pages, iteration is finished.
287 }
288
289 // Unsafe block because we are dereferencing a raw pointer.
290 // This is safe because the `Query` is only created from a valid `World` reference.
291 let world = unsafe { &*self.world_ptr };
292
293 // 2. Get the current page.
294 let page_id = self.matching_page_indices[self.current_page_index];
295 let page = &world.pages[page_id as usize];
296
297 // 3. Check if there are rows left in the current page.
298 if self.current_row_index < page.row_count() {
299 // There are rows left, so we can fetch the data.
300 let item = unsafe {
301 // This is safe because we've already matched the page signature
302 // and we are iterating within the page's bounds.
303 Q::fetch(page as *const _, self.current_row_index)
304 };
305
306 // Advance the row index for the next call.
307 self.current_row_index += 1;
308 return Some(item);
309 } else {
310 // 4. No more rows in this page. Move to the next page.
311 self.current_page_index += 1;
312 self.current_row_index = 0; // Reset the row index for the new page.
313 // The `loop` will then re-evaluate with the new page index.
314 }
315 }
316 }
317}
318
319/// A `WorldQuery` filter that matches entities that do NOT have component `T`.
320///
321/// This is used as a marker in a query tuple to exclude entities. For example,
322/// `Query<(&Position, Without<Velocity>)>` will iterate over all entities
323/// that have a `Position` but no `Velocity`.
324pub struct Without<T: Component>(PhantomData<T>);
325
326// `Without<T>` itself doesn't fetch any data, so its `WorldQuery` implementation
327// is mostly empty. It acts as a signal to the query engine.
328impl<T: Component> WorldQuery for Without<T> {
329 /// This query item is a zero-sized unit type, as it fetches no data.
330 type Item<'a> = ();
331
332 /// A `Without` filter does not add any component types to the query's main
333 /// signature for page matching. The filtering is handled separately.
334 fn type_ids() -> Vec<TypeId> {
335 Vec::new() // Returns an empty Vec.
336 }
337
338 /// This is the key part of the filter: it returns the TypeId of the component to filter out.
339 fn without_type_ids() -> Vec<TypeId> {
340 // This is the key change: it returns the TypeId of the component to filter out.
341 vec![TypeId::of::<T>()]
342 }
343
344 /// Fetches nothing. Returns a unit type `()`.
345 unsafe fn fetch<'a>(_page_ptr: *const ComponentPage, _row_index: usize) -> Self::Item<'a> {
346 // This function will be called but its result is ignored.
347 }
348}