1use alloc::collections::TryReserveError;
7use alloc::vec::Vec;
8use core::fmt;
9
10use crate::entity::EntityId;
11
12use super::World;
13
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16#[non_exhaustive]
17pub enum EntityScratchError {
18 WrongWorld,
20 StaleEntity { entity: EntityId },
22 EntityNotLive { entity: EntityId },
24}
25
26impl fmt::Display for EntityScratchError {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 match self {
29 Self::WrongWorld => f.write_str("entity scratch used with the wrong world"),
30 Self::StaleEntity { entity } => write!(f, "stale entity {entity:?}"),
31 Self::EntityNotLive { entity } => write!(f, "entity {entity:?} is not live"),
32 }
33 }
34}
35
36#[cfg(feature = "std")]
37impl std::error::Error for EntityScratchError {}
38
39struct DenseSlot<V> {
40 generation: u32,
41 value: Option<V>,
42 active_index: usize,
43}
44
45impl<V> DenseSlot<V> {
46 const INACTIVE: usize = usize::MAX;
47
48 const fn vacant() -> Self {
49 Self {
50 generation: 0,
51 value: None,
52 active_index: Self::INACTIVE,
53 }
54 }
55}
56
57pub struct DenseEntityScratch<V> {
63 owner: u32,
64 slots: Vec<DenseSlot<V>>,
65 active: Vec<u32>,
66}
67
68impl<V> DenseEntityScratch<V> {
69 pub fn new(world: &World) -> Self {
71 Self {
72 owner: world.owner.token(),
73 slots: Vec::new(),
74 active: Vec::new(),
75 }
76 }
77
78 pub fn with_capacity(world: &World, capacity: usize) -> Self {
80 Self {
81 owner: world.owner.token(),
82 slots: Vec::with_capacity(capacity),
83 active: Vec::with_capacity(capacity),
84 }
85 }
86
87 pub fn reserve(&mut self, additional: usize) {
89 self.slots.reserve(additional);
90 self.active.reserve(additional);
91 }
92
93 pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> {
95 self.slots.try_reserve(additional)?;
96 self.active.try_reserve(additional)
97 }
98
99 pub fn capacity(&self) -> usize {
101 self.slots.capacity().min(self.active.capacity())
102 }
103
104 pub fn insert(
106 &mut self,
107 world: &World,
108 entity: EntityId,
109 value: V,
110 ) -> Result<Option<V>, EntityScratchError> {
111 self.validate_entity(world, entity)?;
112 let slot_index = entity.slot() as usize;
113 self.ensure_slot(slot_index);
114 let slot = &mut self.slots[slot_index];
115
116 if slot.value.is_none() {
117 slot.generation = entity.generation();
118 slot.value = Some(value);
119 slot.active_index = self.active.len();
120 self.active.push(entity.slot());
121 return Ok(None);
122 }
123
124 if slot.generation == entity.generation() {
125 return Ok(slot.value.replace(value));
126 }
127
128 let stale = slot.value.replace(value);
133 slot.generation = entity.generation();
134 drop(stale);
135 Ok(None)
136 }
137
138 pub fn get<'a>(
140 &'a self,
141 world: &World,
142 entity: EntityId,
143 ) -> Result<Option<&'a V>, EntityScratchError> {
144 self.validate_entity(world, entity)?;
145 Ok(self.slot_value(entity))
146 }
147
148 pub fn get_mut<'a>(
150 &'a mut self,
151 world: &World,
152 entity: EntityId,
153 ) -> Result<Option<&'a mut V>, EntityScratchError> {
154 self.validate_entity(world, entity)?;
155 Ok(self.slot_value_mut(entity))
156 }
157
158 pub fn get_or_insert_with<'a>(
160 &'a mut self,
161 world: &World,
162 entity: EntityId,
163 insert: impl FnOnce() -> V,
164 ) -> Result<&'a mut V, EntityScratchError> {
165 self.validate_entity(world, entity)?;
166 let slot_index = entity.slot() as usize;
167 self.ensure_slot(slot_index);
168 let slot = &mut self.slots[slot_index];
169
170 if slot.value.is_none() {
171 slot.generation = entity.generation();
172 slot.value = Some(insert());
173 slot.active_index = self.active.len();
174 self.active.push(entity.slot());
175 } else if slot.generation != entity.generation() {
176 let value = insert();
177 let stale = slot.value.replace(value);
178 slot.generation = entity.generation();
179 drop(stale);
180 }
181
182 Ok(slot
183 .value
184 .as_mut()
185 .expect("dense scratch slot was populated above"))
186 }
187
188 pub fn remove(
190 &mut self,
191 world: &World,
192 entity: EntityId,
193 ) -> Result<Option<V>, EntityScratchError> {
194 self.validate_entity(world, entity)?;
195 let Some(slot) = self.slots.get_mut(entity.slot() as usize) else {
196 return Ok(None);
197 };
198 if slot.generation != entity.generation() {
199 return Ok(None);
200 }
201 let value = slot.value.take();
202 if value.is_some() {
203 self.remove_active(entity.slot());
204 }
205 Ok(value)
206 }
207
208 pub fn retain_live(&mut self, world: &World) -> Result<usize, EntityScratchError> {
210 self.validate_world(world)?;
211 let before = self.active.len();
212 let mut index = 0;
213 while index < self.active.len() {
214 let entity_slot = self.active[index];
215 let slot = &mut self.slots[entity_slot as usize];
216 let entity = EntityId::from_owned_parts(self.owner, entity_slot, slot.generation);
217 if world.allocator.is_alive(entity) {
218 index += 1;
219 } else {
220 let value = slot.value.take();
221 slot.active_index = DenseSlot::<V>::INACTIVE;
222 self.active.swap_remove(index);
223 if index < self.active.len() {
224 let moved_slot = self.active[index] as usize;
225 self.slots[moved_slot].active_index = index;
226 }
227 drop(value);
228 }
229 }
230 Ok(before - self.active.len())
231 }
232
233 pub fn clear(&mut self) {
235 while let Some(entity_slot) = self.active.pop() {
236 let slot = &mut self.slots[entity_slot as usize];
237 let value = slot.value.take();
238 slot.active_index = DenseSlot::<V>::INACTIVE;
239 drop(value);
240 }
241 }
242
243 pub fn len(&self) -> usize {
245 self.active.len()
246 }
247
248 pub fn is_empty(&self) -> bool {
250 self.active.is_empty()
251 }
252
253 fn ensure_slot(&mut self, slot_index: usize) {
254 self.slots.resize_with(slot_index + 1, DenseSlot::vacant);
255 }
256
257 fn slot_value(&self, entity: EntityId) -> Option<&V> {
258 self.slots
259 .get(entity.slot() as usize)
260 .filter(|slot| slot.generation == entity.generation())
261 .and_then(|slot| slot.value.as_ref())
262 }
263
264 fn slot_value_mut(&mut self, entity: EntityId) -> Option<&mut V> {
265 self.slots
266 .get_mut(entity.slot() as usize)
267 .filter(|slot| slot.generation == entity.generation())
268 .and_then(|slot| slot.value.as_mut())
269 }
270
271 fn remove_active(&mut self, entity_slot: u32) {
272 let slot_index = entity_slot as usize;
273 let active_index = self.slots[slot_index].active_index;
274 debug_assert_ne!(active_index, DenseSlot::<V>::INACTIVE);
275 debug_assert_eq!(self.active[active_index], entity_slot);
276 self.active.swap_remove(active_index);
277 self.slots[slot_index].active_index = DenseSlot::<V>::INACTIVE;
278 if active_index < self.active.len() {
279 let moved_slot = self.active[active_index] as usize;
280 self.slots[moved_slot].active_index = active_index;
281 }
282 }
283
284 fn validate_entity(&self, world: &World, entity: EntityId) -> Result<(), EntityScratchError> {
285 self.validate_world(world)?;
286 if world.allocator.is_alive(entity) {
287 Ok(())
288 } else if world.allocator.is_reserved(entity) {
289 Err(EntityScratchError::EntityNotLive { entity })
290 } else {
291 Err(EntityScratchError::StaleEntity { entity })
292 }
293 }
294
295 fn validate_world(&self, world: &World) -> Result<(), EntityScratchError> {
296 if world.owner.token() == self.owner {
297 Ok(())
298 } else {
299 Err(EntityScratchError::WrongWorld)
300 }
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307 use crate::world::WorldBuilder;
308 use alloc::rc::Rc;
309 use alloc::string::ToString;
310 use core::cell::Cell;
311
312 #[test]
313 fn capacity_and_get_or_insert_cover_dense_storage() {
314 let mut world = WorldBuilder::new().build().expect("world");
315 let entity = world.spawn().expect("spawn");
316 let calls = Cell::new(0);
317 let mut scratch = DenseEntityScratch::with_capacity(&world, 4);
318
319 assert!(scratch.capacity() >= 4);
320 assert_eq!(
321 *scratch
322 .get_or_insert_with(&world, entity, || {
323 calls.set(calls.get() + 1);
324 7
325 })
326 .expect("insert"),
327 7
328 );
329 assert_eq!(
330 *scratch
331 .get_or_insert_with(&world, entity, i32::default)
332 .expect("get"),
333 7
334 );
335 assert_eq!(calls.get(), 1);
336 scratch.reserve(8);
337 scratch.try_reserve(8).expect("try reserve");
338 assert_eq!(
339 EntityScratchError::WrongWorld.to_string(),
340 "entity scratch used with the wrong world"
341 );
342 assert!(!EntityScratchError::StaleEntity { entity }
343 .to_string()
344 .is_empty());
345 assert!(!EntityScratchError::EntityNotLive { entity }
346 .to_string()
347 .is_empty());
348 }
349
350 #[test]
351 fn stale_generation_replacement_drops_once_and_reuses_active_slot() {
352 struct Tracked(Rc<Cell<usize>>);
353
354 impl Drop for Tracked {
355 fn drop(&mut self) {
356 self.0.set(self.0.get() + 1);
357 }
358 }
359
360 let mut world = WorldBuilder::new().build().expect("world");
361 let stale = world.spawn().expect("spawn");
362 let drops = Rc::new(Cell::new(0));
363 let mut scratch = DenseEntityScratch::new(&world);
364 scratch
365 .insert(&world, stale, Tracked(Rc::clone(&drops)))
366 .expect("insert");
367 world.despawn(stale).expect("despawn");
368 let replacement = world.spawn().expect("reuse slot");
369
370 scratch
371 .insert(&world, replacement, Tracked(Rc::clone(&drops)))
372 .expect("replace stale");
373 assert_eq!(scratch.len(), 1);
374 assert_eq!(drops.get(), 1);
375 scratch.clear();
376 assert_eq!(drops.get(), 2);
377 assert!(scratch.is_empty());
378 }
379
380 #[test]
381 fn remove_updates_the_swapped_active_slot_index() {
382 let mut world = WorldBuilder::new().build().expect("world");
383 let first = world.spawn().expect("first");
384 let middle = world.spawn().expect("middle");
385 let last = world.spawn().expect("last");
386 let mut scratch = DenseEntityScratch::new(&world);
387 scratch.insert(&world, first, 1).expect("insert first");
388 scratch.insert(&world, middle, 2).expect("insert middle");
389 scratch.insert(&world, last, 3).expect("insert last");
390
391 assert_eq!(
392 scratch.remove(&world, first).expect("remove first"),
393 Some(1)
394 );
395 assert_eq!(scratch.remove(&world, last).expect("remove moved"), Some(3));
396 assert_eq!(
397 scratch.remove(&world, middle).expect("remove middle"),
398 Some(2)
399 );
400 assert!(scratch.is_empty());
401 }
402
403 #[test]
404 fn get_or_insert_replaces_stale_generation_and_remove_handles_absence() {
405 let mut world = WorldBuilder::new().build().expect("world");
406 let stale = world.spawn().expect("stale");
407 let mut scratch = DenseEntityScratch::new(&world);
408 scratch.insert(&world, stale, 1).expect("insert stale");
409 world.despawn(stale).expect("despawn");
410 let live = world.spawn().expect("reuse");
411 assert_eq!(
412 *scratch
413 .get_or_insert_with(&world, live, || 2)
414 .expect("replace"),
415 2
416 );
417
418 let untouched = world.spawn().expect("untouched");
419 assert_eq!(
420 scratch.remove(&world, untouched).expect("missing slot"),
421 None
422 );
423 scratch.ensure_slot(untouched.slot() as usize);
424 scratch.slots[untouched.slot() as usize].generation =
425 untouched.generation().wrapping_add(1);
426 assert_eq!(
427 scratch
428 .remove(&world, untouched)
429 .expect("generation mismatch"),
430 None
431 );
432 scratch.slots[untouched.slot() as usize].generation = untouched.generation();
433 assert_eq!(scratch.remove(&world, untouched).expect("empty slot"), None);
434 }
435
436 #[test]
437 fn retain_live_repairs_the_active_index_after_swap_remove() {
438 let mut world = WorldBuilder::new().build().expect("world");
439 let stale = world.spawn().expect("stale");
440 let middle = world.spawn().expect("middle");
441 let last = world.spawn().expect("last");
442 let mut scratch = DenseEntityScratch::new(&world);
443 scratch.insert(&world, stale, 1).expect("stale value");
444 scratch.insert(&world, middle, 2).expect("middle value");
445 scratch.insert(&world, last, 3).expect("last value");
446 world.despawn(stale).expect("despawn");
447
448 assert_eq!(scratch.retain_live(&world).expect("retain"), 1);
449 assert_eq!(scratch.remove(&world, last).expect("last"), Some(3));
450 assert_eq!(scratch.remove(&world, middle).expect("middle"), Some(2));
451
452 let final_stale = world.spawn().expect("final stale");
453 let mut final_scratch = DenseEntityScratch::new(&world);
454 final_scratch
455 .insert(&world, final_stale, 4)
456 .expect("final value");
457 world.despawn(final_stale).expect("final despawn");
458 assert_eq!(final_scratch.retain_live(&world).expect("final retain"), 1);
459 }
460}