Skip to main content

gloss_hecs/
entities.rs

1use core::{
2    cmp,
3    convert::TryFrom,
4    fmt,
5    iter::ExactSizeIterator,
6    mem,
7    num::{NonZeroU32, NonZeroU64},
8    ops::Range,
9    sync::atomic::{AtomicIsize, Ordering},
10};
11#[cfg(feature = "std")]
12use std::error::Error;
13#[allow(unused_imports)]
14use std::vec::Vec;
15
16use gloss_utils::abi_stable_aliases::std_types::RVec;
17#[cfg(not(target_arch = "wasm32"))]
18use gloss_utils::abi_stable_aliases::StableAbi;
19
20/// Lightweight unique ID, or handle, of an entity
21///
22/// Obtained from `World::spawn`. Can be stored to refer to an entity in the
23/// future.
24///
25/// Enable the `serde` feature on the crate to make this `Serialize`able. Some
26/// applications may be able to save space by only serializing the output of
27/// `Entity::id`.
28#[derive(Clone, Copy, Hash, Eq, Ord, PartialEq, PartialOrd)]
29#[repr(C)]
30#[cfg_attr(not(target_arch = "wasm32"), derive(StableAbi))]
31#[cfg_attr(not(target_arch = "wasm32"), sabi(missing_field(panic)))]
32pub struct Entity {
33    pub(crate) generation: NonZeroU32,
34    pub(crate) id: u32,
35}
36
37impl Entity {
38    /// An [`Entity`] that does not necessarily correspond to data in any
39    /// `World`
40    ///
41    /// Useful as a dummy value. It is possible (albeit unlikely) for a `World`
42    /// to contain this entity.
43    pub const DANGLING: Entity = Entity {
44        generation: match NonZeroU32::new(u32::MAX) {
45            Some(x) => x,
46            None => unreachable!(),
47        },
48        id: u32::MAX,
49    };
50
51    /// Convert to a form convenient for passing outside of rust
52    ///
53    /// No particular structure is guaranteed for the returned bits.
54    ///
55    /// Useful for storing entity IDs externally, or in conjunction with
56    /// `Entity::from_bits` and `World::spawn_at` for easy serialization.
57    /// Alternatively, consider `id` for more compact representation.
58    pub fn to_bits(self) -> NonZeroU64 {
59        unsafe { NonZeroU64::new_unchecked((u64::from(self.generation.get()) << 32) | u64::from(self.id)) }
60    }
61
62    /// Reconstruct an `Entity` previously destructured with `to_bits` if the
63    /// bitpattern is valid, else `None`
64    ///
65    /// Useful for storing entity IDs externally, or in conjunction with
66    /// `Entity::to_bits` and `World::spawn_at` for easy serialization.
67    #[allow(clippy::cast_possible_truncation)]
68    pub fn from_bits(bits: u64) -> Option<Self> {
69        Some(Self {
70            generation: NonZeroU32::new((bits >> 32) as u32)?,
71            id: bits as u32,
72        })
73    }
74
75    /// Extract a transiently unique identifier
76    ///
77    /// No two simultaneously-live entities share the same ID, but dead
78    /// entities' IDs may collide with both live and dead entities. Useful
79    /// for compactly representing entities within a specific snapshot of
80    /// the world, such as when serializing.
81    ///
82    /// See also `World::find_entity_from_id`.
83    pub fn id(self) -> u32 {
84        self.id
85    }
86}
87
88impl fmt::Debug for Entity {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        write!(f, "{}v{}", self.id, self.generation)
91    }
92}
93
94#[cfg(feature = "serde")]
95impl serde::Serialize for Entity {
96    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
97    where
98        S: serde::Serializer,
99    {
100        self.to_bits().serialize(serializer)
101    }
102}
103
104#[cfg(feature = "serde")]
105impl<'de> serde::Deserialize<'de> for Entity {
106    fn deserialize<D>(deserializer: D) -> Result<Entity, D::Error>
107    where
108        D: serde::Deserializer<'de>,
109    {
110        let bits = u64::deserialize(deserializer)?;
111
112        match Entity::from_bits(bits) {
113            Some(ent) => Ok(ent),
114            None => Err(serde::de::Error::invalid_value(
115                serde::de::Unexpected::Unsigned(bits),
116                &"`a valid `Entity` bitpattern",
117            )),
118        }
119    }
120}
121
122/// An iterator returning a sequence of Entity values from
123/// `Entities::reserve_entities`.
124pub struct ReserveEntitiesIterator<'a> {
125    // Metas, so we can recover the current generation for anything in the freelist.
126    meta: &'a [EntityMeta],
127
128    // Reserved IDs formerly in the freelist to hand out.
129    id_iter: core::slice::Iter<'a, u32>,
130
131    // New Entity IDs to hand out, outside the range of meta.len().
132    id_range: core::ops::Range<u32>,
133}
134
135impl Iterator for ReserveEntitiesIterator<'_> {
136    type Item = Entity;
137
138    fn next(&mut self) -> Option<Self::Item> {
139        self.id_iter
140            .next()
141            .map(|&id| Entity {
142                generation: self.meta[id as usize].generation,
143                id,
144            })
145            .or_else(|| {
146                self.id_range.next().map(|id| Entity {
147                    generation: NonZeroU32::new(1).unwrap(),
148                    id,
149                })
150            })
151    }
152
153    fn size_hint(&self) -> (usize, Option<usize>) {
154        let len = self.id_iter.len() + self.id_range.len();
155        (len, Some(len))
156    }
157}
158
159impl ExactSizeIterator for ReserveEntitiesIterator<'_> {}
160
161#[derive(Default)]
162#[repr(C)]
163#[cfg_attr(not(target_arch = "wasm32"), derive(StableAbi))]
164pub(crate) struct Entities {
165    pub meta: RVec<EntityMeta>,
166
167    // The `pending` and `free_cursor` fields describe three sets of Entity IDs
168    // that have been freed or are in the process of being allocated:
169    //
170    // - The `freelist` IDs, previously freed by `free()`. These IDs are available to any of `alloc()`, `reserve_entity()` or `reserve_entities()`.
171    //   Allocation will always prefer these over brand new IDs.
172    //
173    // - The `reserved` list of IDs that were once in the freelist, but got reserved by `reserve_entities` or `reserve_entity()`. They are now
174    //   waiting for `flush()` to make them fully allocated.
175    //
176    // - The count of new IDs that do not yet exist in `self.meta()`, but which we have handed out and reserved. `flush()` will allocate room for
177    //   them in `self.meta()`.
178    //
179    // The contents of `pending` look like this:
180    //
181    // ```
182    // ----------------------------
183    // |  freelist  |  reserved   |
184    // ----------------------------
185    //              ^             ^
186    //          free_cursor   pending.len()
187    // ```
188    //
189    // As IDs are allocated, `free_cursor` is atomically decremented, moving
190    // items from the freelist into the reserved list by sliding over the boundary.
191    //
192    // Once the freelist runs out, `free_cursor` starts going negative.
193    // The more negative it is, the more IDs have been reserved starting exactly at
194    // the end of `meta.len()`.
195    //
196    // This formulation allows us to reserve any number of IDs first from the freelist
197    // and then from the new IDs, using only a single atomic subtract.
198    //
199    // Once `flush()` is done, `free_cursor` will equal `pending.len()`.
200    pending: RVec<u32>,
201    free_cursor: AtomicIsize,
202    len: u32,
203}
204
205#[allow(clippy::cast_possible_wrap)]
206#[allow(clippy::cast_sign_loss)]
207#[allow(clippy::cast_possible_truncation)]
208impl Entities {
209    /// Reserve entity IDs concurrently
210    ///
211    /// Storage for entity generation and location is lazily allocated by
212    /// calling `flush`.
213    pub fn reserve_entities(&self, count: u32) -> ReserveEntitiesIterator<'_> {
214        // Use one atomic subtract to grab a range of new IDs. The range might be
215        // entirely nonnegative, meaning all IDs come from the freelist, or entirely
216        // negative, meaning they are all new IDs to allocate, or a mix of both.
217        let range_end = self.free_cursor.fetch_sub(count as isize, Ordering::Relaxed);
218        let range_start = range_end - count as isize;
219
220        let freelist_range = range_start.max(0) as usize..range_end.max(0) as usize;
221
222        let (new_id_start, new_id_end) = if range_start >= 0 {
223            // We satisfied all requests from the freelist.
224            (0, 0)
225        } else {
226            // We need to allocate some new Entity IDs outside of the range of self.meta.
227            //
228            // `range_start` covers some negative territory, e.g. `-3..6`.
229            // Since the nonnegative values `0..6` are handled by the freelist, that
230            // means we need to handle the negative range here.
231            //
232            // In this example, we truncate the end to 0, leaving us with `-3..0`.
233            // Then we negate these values to indicate how far beyond the end of
234            // `meta.end()` to go, yielding `meta.len()+0 .. meta.len()+3`.
235            let base = self.meta.len() as isize;
236
237            let new_id_end = u32::try_from(base - range_start).expect("too many entities");
238
239            // `new_id_end` is in range, so no need to check `start`.
240            let new_id_start = (base - range_end.min(0)) as u32;
241
242            (new_id_start, new_id_end)
243        };
244
245        ReserveEntitiesIterator {
246            meta: &self.meta[..],
247            id_iter: self.pending[freelist_range].iter(),
248            id_range: new_id_start..new_id_end,
249        }
250    }
251
252    /// Reserve one entity ID concurrently
253    ///
254    /// Equivalent to `self.reserve_entities(1).next().unwrap()`, but more
255    /// efficient.
256    pub fn reserve_entity(&self) -> Entity {
257        let n = self.free_cursor.fetch_sub(1, Ordering::Relaxed);
258        if n > 0 {
259            // Allocate from the freelist.
260            let id = self.pending[(n - 1) as usize];
261            Entity {
262                generation: self.meta[id as usize].generation,
263                id,
264            }
265        } else {
266            // Grab a new ID, outside the range of `meta.len()`. `flush()` must
267            // eventually be called to make it valid.
268            //
269            // As `self.free_cursor` goes more and more negative, we return IDs farther
270            // and farther beyond `meta.len()`.
271            Entity {
272                generation: NonZeroU32::new(1).unwrap(),
273                id: u32::try_from(self.meta.len() as isize - n).expect("too many entities"),
274            }
275        }
276    }
277
278    /// Check that we do not have pending work requiring `flush()` to be called.
279    fn verify_flushed(&mut self) {
280        debug_assert!(!self.needs_flush(), "flush() needs to be called before this operation is legal");
281    }
282
283    /// Allocate an entity ID directly
284    ///
285    /// Location should be written immediately.
286    pub fn alloc(&mut self) -> Entity {
287        self.verify_flushed();
288
289        self.len += 1;
290        if let Some(id) = self.pending.pop() {
291            let new_free_cursor = self.pending.len() as isize;
292            self.free_cursor.store(new_free_cursor, Ordering::Relaxed); // Not racey due to &mut self
293            Entity {
294                generation: self.meta[id as usize].generation,
295                id,
296            }
297        } else {
298            let id = u32::try_from(self.meta.len()).expect("too many entities");
299            self.meta.push(EntityMeta::EMPTY);
300            Entity {
301                generation: NonZeroU32::new(1).unwrap(),
302                id,
303            }
304        }
305    }
306
307    /// Allocate and set locations for many entity IDs laid out contiguously in
308    /// an archetype
309    ///
310    /// `self.finish_alloc_many()` must be called after!
311    pub fn alloc_many(&mut self, n: u32, archetype: u32, mut first_index: u32) -> AllocManyState {
312        self.verify_flushed();
313
314        let fresh = (n as usize).saturating_sub(self.pending.len()) as u32;
315        assert!((self.meta.len() + fresh as usize) < u32::MAX as usize, "too many entities");
316        let pending_end = self.pending.len().saturating_sub(n as usize);
317        for &id in &self.pending[pending_end..] {
318            self.meta[id as usize].location = Location {
319                archetype,
320                index: first_index,
321            };
322            first_index += 1;
323        }
324
325        let fresh_start = self.meta.len() as u32;
326        self.meta.extend((first_index..(first_index + fresh)).map(|index| EntityMeta {
327            generation: NonZeroU32::new(1).unwrap(),
328            location: Location { archetype, index },
329        }));
330
331        self.len += n;
332
333        AllocManyState {
334            fresh: fresh_start..(fresh_start + fresh),
335            pending_end,
336        }
337    }
338
339    /// Remove entities used by `alloc_many` from the freelist
340    ///
341    /// This is an awkward separate function to avoid borrowck issues in
342    /// `SpawnColumnBatchIter`.
343    pub fn finish_alloc_many(&mut self, pending_end: usize) {
344        self.pending.truncate(pending_end);
345    }
346
347    /// Allocate a specific entity ID, overwriting its generation
348    ///
349    /// Returns the location of the entity currently using the given ID, if any.
350    /// Location should be written immediately.
351    pub fn alloc_at(&mut self, entity: Entity) -> Option<Location> {
352        self.verify_flushed();
353
354        let loc = if entity.id as usize >= self.meta.len() {
355            self.pending.extend((self.meta.len() as u32)..entity.id);
356            let new_free_cursor = self.pending.len() as isize;
357            self.free_cursor.store(new_free_cursor, Ordering::Relaxed); // Not racey due to &mut self
358            self.meta.resize(entity.id as usize + 1, EntityMeta::EMPTY);
359            self.len += 1;
360            None
361        } else if let Some(index) = self.pending.iter().position(|item| *item == entity.id) {
362            self.pending.swap_remove(index);
363            let new_free_cursor = self.pending.len() as isize;
364            self.free_cursor.store(new_free_cursor, Ordering::Relaxed); // Not racey due to &mut self
365            self.len += 1;
366            None
367        } else {
368            Some(mem::replace(&mut self.meta[entity.id as usize].location, EntityMeta::EMPTY.location))
369        };
370
371        self.meta[entity.id as usize].generation = entity.generation;
372
373        loc
374    }
375
376    /// Destroy an entity, allowing it to be reused
377    ///
378    /// Must not be called while reserved entities are awaiting `flush()`.
379    pub fn free(&mut self, entity: Entity) -> Result<Location, NoSuchEntity> {
380        self.verify_flushed();
381
382        let meta = self.meta.get_mut(entity.id as usize).ok_or(NoSuchEntity)?;
383        if meta.generation != entity.generation || meta.location.index == u32::MAX {
384            return Err(NoSuchEntity);
385        }
386
387        meta.generation = NonZeroU32::new(u32::from(meta.generation).wrapping_add(1)).unwrap_or_else(|| NonZeroU32::new(1).unwrap());
388
389        let loc = mem::replace(&mut meta.location, EntityMeta::EMPTY.location);
390
391        self.pending.push(entity.id);
392
393        let new_free_cursor = self.pending.len() as isize;
394        self.free_cursor.store(new_free_cursor, Ordering::Relaxed); // Not racey due to &mut self
395        self.len -= 1;
396
397        Ok(loc)
398    }
399
400    /// Ensure at least `n` allocations can succeed without reallocating
401    pub fn reserve(&mut self, additional: u32) {
402        self.verify_flushed();
403
404        let freelist_size = self.free_cursor.load(Ordering::Relaxed);
405        let shortfall = additional as isize - freelist_size;
406        if shortfall > 0 {
407            self.meta.reserve(shortfall as usize);
408        }
409    }
410
411    pub fn contains(&self, entity: Entity) -> bool {
412        // match self.meta.get(entity.id as usize) {
413        //     Some(meta) => {
414        //         meta.generation == entity.generation
415        //             && (meta.location.index != u32::MAX
416        //                 || self.pending[self.free_cursor.load(Ordering::Relaxed).max(0) as usize..].contains(&entity.id))
417        //     }
418        //     None => {
419        //         // Check if this could have been obtained from `reserve_entity`
420        //         let free = self.free_cursor.load(Ordering::Relaxed);
421        //         entity.generation.get() == 1 && free < 0 && (entity.id as isize) <
422        // (free.abs() + self.meta.len() as isize)     }
423        // }
424        if let Some(meta) = self.meta.get(entity.id as usize) {
425            meta.generation == entity.generation
426                && (meta.location.index != u32::MAX || self.pending[self.free_cursor.load(Ordering::Relaxed).max(0) as usize..].contains(&entity.id))
427        } else {
428            // Check if this could have been obtained from `reserve_entity`
429            let free = self.free_cursor.load(Ordering::Relaxed);
430            entity.generation.get() == 1 && free < 0 && (entity.id as isize) < (free.abs() + self.meta.len() as isize)
431        }
432    }
433
434    pub fn clear(&mut self) {
435        self.meta.clear();
436        self.pending.clear();
437        self.free_cursor.store(0, Ordering::Relaxed); // Not racey due to &mut self
438        self.len = 0;
439    }
440
441    /// Access the location storage of an entity
442    ///
443    /// Must not be called on pending entities.
444    pub fn get_mut(&mut self, entity: Entity) -> Result<&mut Location, NoSuchEntity> {
445        let meta = self.meta.get_mut(entity.id as usize).ok_or(NoSuchEntity)?;
446        if meta.generation == entity.generation && meta.location.index != u32::MAX {
447            Ok(&mut meta.location)
448        } else {
449            Err(NoSuchEntity)
450        }
451    }
452
453    /// Returns `Ok(Location { archetype: 0, index: undefined })` for pending
454    /// entities
455    pub fn get(&self, entity: Entity) -> Result<Location, NoSuchEntity> {
456        if self.meta.len() <= entity.id as usize {
457            // Check if this could have been obtained from `reserve_entity`
458            let free = self.free_cursor.load(Ordering::Relaxed);
459            if entity.generation.get() == 1 && free < 0 && (entity.id as isize) < (free.abs() + self.meta.len() as isize) {
460                return Ok(Location {
461                    archetype: 0,
462                    index: u32::MAX,
463                });
464            }
465            return Err(NoSuchEntity);
466        }
467        let meta = &self.meta[entity.id as usize];
468        if meta.generation != entity.generation || meta.location.index == u32::MAX {
469            return Err(NoSuchEntity);
470        }
471        Ok(meta.location)
472    }
473
474    /// Panics if the given id would represent an index outside of `meta`.
475    ///
476    /// # Safety
477    /// Must only be called for currently allocated `id`s.
478    pub unsafe fn resolve_unknown_gen(&self, id: u32) -> Entity {
479        let meta_len = self.meta.len();
480
481        if meta_len > id as usize {
482            let meta = &self.meta[id as usize];
483            Entity {
484                generation: meta.generation,
485                id,
486            }
487        } else {
488            // See if it's pending, but not yet flushed.
489            let free_cursor = self.free_cursor.load(Ordering::Relaxed);
490            let num_pending = cmp::max(-free_cursor, 0) as usize;
491
492            if meta_len + num_pending > id as usize {
493                // Pending entities will have the first generation.
494                Entity {
495                    generation: NonZeroU32::new(1).unwrap(),
496                    id,
497                }
498            } else {
499                panic!("entity id is out of range");
500            }
501        }
502    }
503
504    fn needs_flush(&mut self) -> bool {
505        // Not racey due to &mut self
506        self.free_cursor.load(Ordering::Relaxed) != self.pending.len() as isize
507    }
508
509    /// Allocates space for entities previously reserved with `reserve_entity`
510    /// or `reserve_entities`, then initializes each one using the supplied
511    /// function.
512    pub fn flush(&mut self, mut init: impl FnMut(u32, &mut Location)) {
513        // Not racey due because of self is &mut.
514        let free_cursor = self.free_cursor.load(Ordering::Relaxed);
515
516        let new_free_cursor = if free_cursor >= 0 {
517            free_cursor as usize
518        } else {
519            let old_meta_len = self.meta.len();
520            let new_meta_len = old_meta_len + -free_cursor as usize;
521            self.meta.resize(new_meta_len, EntityMeta::EMPTY);
522
523            self.len += -free_cursor as u32;
524            for (id, meta) in self.meta.iter_mut().enumerate().skip(old_meta_len) {
525                init(id as u32, &mut meta.location);
526            }
527
528            self.free_cursor.store(0, Ordering::Relaxed);
529            0
530        };
531
532        self.len += (self.pending.len() - new_free_cursor) as u32;
533        for id in self.pending.drain(new_free_cursor..) {
534            init(id, &mut self.meta[id as usize].location);
535        }
536    }
537
538    #[inline]
539    pub fn len(&self) -> u32 {
540        self.len
541    }
542}
543
544#[derive(Copy, Clone)]
545#[repr(C)]
546#[cfg_attr(not(target_arch = "wasm32"), derive(StableAbi))]
547pub(crate) struct EntityMeta {
548    pub generation: NonZeroU32,
549    pub location: Location,
550}
551
552impl EntityMeta {
553    const EMPTY: EntityMeta = EntityMeta {
554        generation: match NonZeroU32::new(1) {
555            Some(x) => x,
556            None => unreachable!(),
557        },
558        location: Location {
559            archetype: 0,
560            index: u32::MAX, // dummy value, to be filled in
561        },
562    };
563}
564
565#[derive(Copy, Clone)]
566#[repr(C)]
567#[cfg_attr(not(target_arch = "wasm32"), derive(StableAbi))]
568pub(crate) struct Location {
569    pub archetype: u32,
570    pub index: u32,
571}
572
573/// Error indicating that no entity with a particular ID exists
574#[derive(Debug, Clone, Eq, PartialEq)]
575pub struct NoSuchEntity;
576
577impl fmt::Display for NoSuchEntity {
578    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
579        f.pad("no such entity")
580    }
581}
582
583#[cfg(feature = "std")]
584impl Error for NoSuchEntity {}
585
586#[derive(Clone)]
587pub(crate) struct AllocManyState {
588    pub pending_end: usize,
589    fresh: Range<u32>,
590}
591
592impl AllocManyState {
593    pub fn next(&mut self, entities: &Entities) -> Option<u32> {
594        if self.pending_end < entities.pending.len() {
595            let id = entities.pending[self.pending_end];
596            self.pending_end += 1;
597            Some(id)
598        } else {
599            self.fresh.next()
600        }
601    }
602
603    pub fn len(&self, entities: &Entities) -> usize {
604        self.fresh.len() + (entities.pending.len() - self.pending_end)
605    }
606}
607
608#[cfg(test)]
609mod tests {
610    use super::*;
611    use hashbrown::{HashMap, HashSet};
612    use rand::{rngs::StdRng, Rng, SeedableRng};
613
614    #[test]
615    fn entity_bits_roundtrip() {
616        let e = Entity {
617            generation: NonZeroU32::new(0xDEAD_BEEF).unwrap(),
618            id: 0xBAAD_F00D,
619        };
620        assert_eq!(Entity::from_bits(e.to_bits().into()).unwrap(), e);
621    }
622
623    #[test]
624    fn alloc_and_free() {
625        let mut rng = StdRng::seed_from_u64(0xFEED_FACE_DEAD_F00D);
626
627        let mut e = Entities::default();
628        let mut first_unused = 0u32;
629        let mut id_to_gen: HashMap<u32, u32> = HashMap::default();
630        let mut free_set: HashSet<u32> = HashSet::default();
631        let mut len = 0;
632
633        for _ in 0..100 {
634            let alloc = rng.gen_bool(0.7);
635            if alloc || first_unused == 0 {
636                let entity = e.alloc();
637                e.meta[entity.id as usize].location.index = 0;
638                len += 1;
639
640                let id = entity.id;
641                if !free_set.is_empty() {
642                    // This should have come from the freelist.
643                    assert!(free_set.remove(&id));
644                } else if id >= first_unused {
645                    first_unused = id + 1;
646                }
647
648                e.get_mut(entity).unwrap().index = 37;
649
650                assert!(id_to_gen.insert(id, entity.generation.get()).is_none());
651            } else {
652                // Free a random ID, whether or not it's in use, and check for errors.
653                let id = rng.gen_range(0..first_unused);
654
655                let generation = id_to_gen.remove(&id);
656                let entity = Entity {
657                    id,
658                    generation: NonZeroU32::new(generation.unwrap_or_else(|| NonZeroU32::new(1).unwrap().get())).unwrap(),
659                };
660
661                assert_eq!(e.free(entity).is_ok(), generation.is_some());
662                if generation.is_some() {
663                    len -= 1;
664                }
665
666                free_set.insert(id);
667            }
668            assert_eq!(e.len(), len);
669        }
670    }
671
672    #[test]
673    fn alloc_at() {
674        let mut e = Entities::default();
675        let mut old = Vec::new();
676
677        for _ in 0..2 {
678            let entity = e.alloc();
679            e.meta[entity.id as usize].location.index = 0;
680            old.push(entity);
681            e.free(entity).unwrap();
682        }
683
684        assert_eq!(e.len(), 0);
685
686        let id = old.first().unwrap().id();
687        assert!(old.iter().all(|entity| entity.id() == id));
688
689        let entity = *old.last().unwrap();
690        // The old ID shouldn't exist at this point, and should exist
691        // in the pending list.
692        assert!(!e.contains(entity));
693        assert!(e.pending.contains(&entity.id()));
694        // Allocating an entity at an unused location should not cause a location to be
695        // returned.
696        assert!(e.alloc_at(entity).is_none());
697        e.meta[entity.id as usize].location.index = 0;
698        assert!(e.contains(entity));
699        // The entity in question should not exist in the free-list once allocated.
700        assert!(!e.pending.contains(&entity.id()));
701        assert_eq!(e.len(), 1);
702        // Allocating at the same id again should cause a location to be returned
703        // this time around.
704        assert!(e.alloc_at(entity).is_some());
705        e.meta[entity.id as usize].location.index = 0;
706        assert!(e.contains(entity));
707        assert_eq!(e.len(), 1);
708
709        // Allocating an Entity should cause the new empty locations
710        // to be located in the free list.
711        assert_eq!(e.meta.len(), 1);
712        assert!(e
713            .alloc_at(Entity {
714                id: 3,
715                generation: NonZeroU32::new(2).unwrap(),
716            })
717            .is_none());
718        e.meta[entity.id as usize].location.index = 0;
719        assert_eq!(e.pending.len(), 2);
720        assert_eq!(&e.pending, &[1, 2]);
721        assert_eq!(e.meta.len(), 4);
722    }
723
724    #[test]
725    fn contains() {
726        let mut e = Entities::default();
727
728        for _ in 0..2 {
729            let entity = e.alloc();
730            e.meta[entity.id as usize].location.index = 0;
731            assert!(e.contains(entity));
732
733            e.free(entity).unwrap();
734            assert!(!e.contains(entity));
735        }
736
737        // Reserved but not flushed are still "contained".
738        for _ in 0..3 {
739            let entity = e.reserve_entity();
740            assert!(e.contains(entity));
741            assert!(!e.contains(Entity {
742                id: entity.id,
743                generation: NonZeroU32::new(2).unwrap(),
744            }));
745            assert!(!e.contains(Entity {
746                id: entity.id + 1,
747                generation: NonZeroU32::new(1).unwrap(),
748            }));
749        }
750    }
751
752    // Shared test code parameterized by how we want to allocate an Entity block.
753    #[allow(clippy::cast_possible_truncation)]
754    fn reserve_test_helper(reserve_n: impl FnOnce(&mut Entities, u32) -> Vec<Entity>) {
755        let mut e = Entities::default();
756
757        // Allocate 10 items.
758        let mut v1: Vec<Entity> = (0..10).map(|_| e.alloc()).collect();
759        for &entity in &v1 {
760            e.meta[entity.id as usize].location.index = 0;
761        }
762        assert_eq!(v1.iter().map(|e| e.id).max(), Some(9));
763        for &entity in v1.iter() {
764            assert!(e.contains(entity));
765            e.get_mut(entity).unwrap().index = 37;
766        }
767
768        // Put the last 4 on the freelist.
769        for entity in v1.drain(6..) {
770            e.free(entity).unwrap();
771        }
772        assert_eq!(e.free_cursor.load(Ordering::Relaxed), 4);
773
774        // Reserve 10 entities, so 4 will come from the freelist.
775        // This means we will have allocated 10 + 10 - 4 total items, so max id is 15.
776        let v2 = reserve_n(&mut e, 10);
777        assert_eq!(v2.iter().map(|e| e.id).max(), Some(15));
778
779        // Reserved IDs still count as "contained".
780        assert!(v2.iter().all(|&entity| e.contains(entity)));
781
782        // We should have exactly IDs 0..16
783        let mut v3: Vec<Entity> = v1.iter().chain(v2.iter()).copied().collect();
784        assert_eq!(v3.len(), 16);
785        v3.sort_by_key(|entity| entity.id);
786        for (i, entity) in v3.into_iter().enumerate() {
787            assert_eq!(entity.id, i as u32);
788        }
789
790        // 6 will come from pending.
791        assert_eq!(e.free_cursor.load(Ordering::Relaxed), -6);
792
793        let mut flushed = Vec::new();
794        e.flush(|id, loc| {
795            loc.index = 0;
796            flushed.push(id);
797        });
798        flushed.sort_unstable();
799
800        assert_eq!(flushed, (6..16).collect::<Vec<_>>());
801    }
802
803    #[test]
804    fn reserve_entity() {
805        reserve_test_helper(|e, n| (0..n).map(|_| e.reserve_entity()).collect());
806    }
807
808    #[test]
809    fn reserve_entities() {
810        reserve_test_helper(|e, n| e.reserve_entities(n).collect());
811    }
812
813    #[test]
814    fn reserve_grows() {
815        let mut e = Entities::default();
816        let _ = e.reserve_entity();
817        e.flush(|_, l| {
818            l.index = 0;
819        });
820        assert_eq!(e.len(), 1);
821    }
822
823    #[test]
824    fn reserve_grows_mixed() {
825        let mut e = Entities::default();
826        let a = e.alloc();
827        e.meta[a.id as usize].location.index = 0;
828        let b = e.alloc();
829        e.meta[b.id as usize].location.index = 0;
830        e.free(a).unwrap();
831        let _ = e.reserve_entities(3);
832        e.flush(|_, l| {
833            l.index = 0;
834        });
835        assert_eq!(e.len(), 4);
836    }
837
838    #[test]
839    fn alloc_at_regression() {
840        let mut e = Entities::default();
841        assert!(e
842            .alloc_at(Entity {
843                generation: NonZeroU32::new(1).unwrap(),
844                id: 1
845            })
846            .is_none());
847        assert!(!e.contains(Entity {
848            generation: NonZeroU32::new(1).unwrap(),
849            id: 0
850        }));
851    }
852}