Skip to main content

moirai/world/
access.rs

1//! Typed [`World`] access helpers.
2//!
3//! [`DenseEntityScratch`] provides generational, entity-keyed transient storage for
4//! per-system scratch data that must not outlive live entity handles.
5
6use alloc::collections::TryReserveError;
7use alloc::vec::Vec;
8use core::fmt;
9
10use crate::entity::EntityId;
11
12use super::World;
13
14/// Failure while accessing [`DenseEntityScratch`] against a bound world.
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16#[non_exhaustive]
17pub enum EntityScratchError {
18    /// The supplied world is not the world that created the scratch storage.
19    WrongWorld,
20    /// The entity generation is no longer live in the bound world.
21    StaleEntity { entity: EntityId },
22    /// The entity belongs to the bound world but is not live yet.
23    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
57/// Dense transient, world-bound storage keyed by full generational entity handles.
58///
59/// Values are addressed directly by entity slot. An active-slot list keeps clearing
60/// and liveness retention proportional to the number of stored values rather than
61/// the highest entity slot that has been observed.
62pub struct DenseEntityScratch<V> {
63    owner: u32,
64    slots: Vec<DenseSlot<V>>,
65    active: Vec<u32>,
66}
67
68impl<V> DenseEntityScratch<V> {
69    /// Creates empty scratch storage bound to `world`.
70    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    /// Creates empty scratch storage with capacity for at least `capacity` values.
79    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    /// Reserves capacity for at least `additional` more dense slots and values.
88    pub fn reserve(&mut self, additional: usize) {
89        self.slots.reserve(additional);
90        self.active.reserve(additional);
91    }
92
93    /// Tries to reserve capacity for at least `additional` more dense slots and values.
94    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    /// Returns the value capacity available without growing both internal vectors.
100    pub fn capacity(&self) -> usize {
101        self.slots.capacity().min(self.active.capacity())
102    }
103
104    /// Inserts a value for a live entity, returning the value it replaced.
105    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        // The occupied slot belongs to a stale generation. Assigning the new
129        // value preserves the one active-list entry for this entity slot. The
130        // stale value is dropped only after the slot records a consistent new
131        // generation/value pair, including when its destructor unwinds.
132        let stale = slot.value.replace(value);
133        slot.generation = entity.generation();
134        drop(stale);
135        Ok(None)
136    }
137
138    /// Gets a shared reference for a live entity.
139    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    /// Gets a mutable reference for a live entity.
149    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    /// Gets the existing value or inserts one produced by `insert`.
159    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    /// Removes and returns the value for a live entity.
189    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    /// Removes entries whose recorded entity generation is no longer live.
209    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    /// Removes all scratch values.
234    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    /// Returns the number of stored values, including entries that have become stale.
244    pub fn len(&self) -> usize {
245        self.active.len()
246    }
247
248    /// Returns whether no scratch values are stored.
249    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}