1use alloc::vec::Vec;
16
17use crate::ecs::entity::Entity;
18use crate::ecs::mask::{ComponentId, ComponentMask};
19
20const NO_ROW: u32 = u32::MAX;
22
23#[derive(Default, Debug)]
24pub struct JoinIndex {
27 occupants: Vec<Option<Entity>>,
30 masks: Vec<ComponentMask>,
32 rows: Vec<Vec<u32>>,
35}
36
37impl JoinIndex {
38 pub fn new() -> JoinIndex {
40 JoinIndex::default()
41 }
42
43 pub fn set(&mut self, entity: Entity, id: ComponentId, row: u32) {
47 let index = entity.index() as usize;
48 self.grow_to(index);
49 if self.occupants[index] != Some(entity) {
50 self.reset_index(index);
51 self.occupants[index] = Some(entity);
52 }
53 self.masks[index].insert(id);
54 let column = self.row_column(id.get());
55 if index >= column.len() {
56 column.resize(index + 1, NO_ROW);
57 }
58 column[index] = row;
59 }
60
61 pub fn clear(&mut self, entity: Entity, id: ComponentId) {
64 let index = entity.index() as usize;
65 if self.occupants.get(index).copied().flatten() != Some(entity) {
66 return;
67 }
68 self.masks[index].remove(id);
69 if let Some(slot) = self
70 .rows
71 .get_mut(id.get() as usize)
72 .and_then(|column| column.get_mut(index))
73 {
74 *slot = NO_ROW;
75 }
76 if self.masks[index].is_empty() {
77 self.occupants[index] = None;
78 }
79 }
80
81 pub fn clear_entity(&mut self, entity: Entity) {
83 let index = entity.index() as usize;
84 if self.occupants.get(index).copied().flatten() != Some(entity) {
85 return;
86 }
87 self.reset_index(index);
88 }
89
90 pub fn mask(&self, entity: Entity) -> ComponentMask {
92 let index = entity.index() as usize;
93 if self.occupants.get(index).copied().flatten() != Some(entity) {
94 return ComponentMask::EMPTY;
95 }
96 self.masks
97 .get(index)
98 .copied()
99 .unwrap_or(ComponentMask::EMPTY)
100 }
101
102 pub fn row(&self, entity: Entity, id: ComponentId) -> Option<u32> {
105 let index = entity.index() as usize;
106 if self.occupants.get(index).copied().flatten() != Some(entity) {
107 return None;
108 }
109 let row = self.rows.get(id.get() as usize)?.get(index).copied()?;
110 (row != NO_ROW).then_some(row)
111 }
112
113 pub fn matches(
115 &self,
116 entity: Entity,
117 required: ComponentMask,
118 excluded: ComponentMask,
119 ) -> bool {
120 let mask = self.mask(entity);
121 mask.contains_all(required) && mask.is_disjoint(excluded)
122 }
123
124 fn reset_index(&mut self, index: usize) {
126 if let Some(mask) = self.masks.get_mut(index) {
127 *mask = ComponentMask::EMPTY;
128 }
129 for column in &mut self.rows {
130 if let Some(slot) = column.get_mut(index) {
131 *slot = NO_ROW;
132 }
133 }
134 if index < self.occupants.len() {
135 self.occupants[index] = None;
136 }
137 }
138
139 fn grow_to(&mut self, index: usize) {
141 if index >= self.occupants.len() {
142 self.occupants.resize(index + 1, None);
143 }
144 if index >= self.masks.len() {
145 self.masks.resize(index + 1, ComponentMask::EMPTY);
146 }
147 }
148
149 fn row_column(&mut self, id: u8) -> &mut Vec<u32> {
150 let id = id as usize;
151 if id >= self.rows.len() {
152 self.rows.resize_with(id + 1, Vec::new);
153 }
154 &mut self.rows[id]
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::ecs::entity::Entities;
162
163 fn ids() -> (ComponentId, ComponentId, ComponentId) {
164 (
165 ComponentId::new(1),
166 ComponentId::new(2),
167 ComponentId::new(3),
168 )
169 }
170
171 #[test]
172 fn set_records_mask_and_row() {
173 let mut entities = Entities::new();
174 let e = entities.alloc();
175 let (transform, mesh, _) = ids();
176 let mut join = JoinIndex::new();
177 join.set(e, transform, 0);
178 join.set(e, mesh, 7);
179
180 assert!(join.mask(e).contains(transform));
181 assert!(join.mask(e).contains(mesh));
182 assert_eq!(join.row(e, transform), Some(0));
183 assert_eq!(join.row(e, mesh), Some(7));
184 assert_eq!(join.row(e, ComponentId::new(9)), None);
186 }
187
188 #[test]
189 fn clear_removes_one_component() {
190 let mut entities = Entities::new();
191 let e = entities.alloc();
192 let (transform, mesh, _) = ids();
193 let mut join = JoinIndex::new();
194 join.set(e, transform, 0);
195 join.set(e, mesh, 1);
196
197 join.clear(e, mesh);
198 assert!(join.mask(e).contains(transform));
199 assert!(!join.mask(e).contains(mesh));
200 assert_eq!(join.row(e, mesh), None);
201 assert_eq!(join.row(e, transform), Some(0));
202 }
203
204 #[test]
205 fn clear_last_component_frees_the_slot() {
206 let mut entities = Entities::new();
207 let e = entities.alloc();
208 let (transform, _, _) = ids();
209 let mut join = JoinIndex::new();
210 join.set(e, transform, 0);
211 join.clear(e, transform);
212 assert!(join.mask(e).is_empty());
214 assert_eq!(join.row(e, transform), None);
215 }
216
217 #[test]
218 fn clear_entity_removes_everything() {
219 let mut entities = Entities::new();
220 let e = entities.alloc();
221 let (transform, mesh, collider) = ids();
222 let mut join = JoinIndex::new();
223 join.set(e, transform, 0);
224 join.set(e, mesh, 1);
225 join.set(e, collider, 2);
226
227 join.clear_entity(e);
228 assert!(join.mask(e).is_empty());
229 assert_eq!(join.row(e, transform), None);
230 assert_eq!(join.row(e, mesh), None);
231 assert_eq!(join.row(e, collider), None);
232 }
233
234 #[test]
235 fn matches_required_and_excluded() {
236 let mut entities = Entities::new();
237 let e = entities.alloc();
238 let (transform, mesh, collider) = ids();
239 let mut join = JoinIndex::new();
240 join.set(e, transform, 0);
241 join.set(e, mesh, 1);
242
243 let required = ComponentMask::with(transform);
244 let want_mesh = {
245 let mut m = ComponentMask::with(transform);
246 m.insert(mesh);
247 m
248 };
249 assert!(join.matches(e, required, ComponentMask::with(collider)));
250 assert!(join.matches(e, want_mesh, ComponentMask::EMPTY));
251 assert!(!join.matches(e, required, ComponentMask::with(mesh)));
253 assert!(!join.matches(e, ComponentMask::with(collider), ComponentMask::EMPTY));
255 }
256
257 #[test]
258 fn distinct_entities_are_independent() {
259 let mut entities = Entities::new();
260 let a = entities.alloc();
261 let b = entities.alloc();
262 let (transform, mesh, _) = ids();
263 let mut join = JoinIndex::new();
264 join.set(a, transform, 0);
265 join.set(b, mesh, 0);
266
267 assert!(join.mask(a).contains(transform));
268 assert!(!join.mask(a).contains(mesh));
269 assert!(join.mask(b).contains(mesh));
270 assert!(!join.mask(b).contains(transform));
271 }
272
273 #[test]
274 fn stale_handle_resolves_to_empty_not_the_recycled_occupant() {
275 let mut entities = Entities::new();
276 let a = entities.alloc();
277 let (transform, mesh, _) = ids();
278 let mut join = JoinIndex::new();
279 join.set(a, transform, 0);
280 join.set(a, mesh, 5);
281
282 entities.despawn(a);
285 let b = entities.alloc();
286 assert_eq!(a.index(), b.index());
287 assert_ne!(a, b);
288
289 join.set(b, transform, 0);
291 assert!(join.mask(b).contains(transform));
293 assert!(!join.mask(b).contains(mesh));
294 assert_eq!(join.row(b, mesh), None);
295 assert!(join.mask(a).is_empty());
297 assert_eq!(join.row(a, transform), None);
298 }
299
300 #[test]
301 fn recycled_index_rejects_old_generation_before_overwrite() {
302 let mut entities = Entities::new();
303 let a = entities.alloc();
304 let (transform, _, _) = ids();
305 let mut join = JoinIndex::new();
306 join.set(a, transform, 0);
307 entities.despawn(a);
311 let b = entities.alloc();
312 assert_eq!(a.index(), b.index());
313 assert!(join.mask(b).is_empty());
314 assert_eq!(join.row(b, transform), None);
315 }
316}