pub struct EntityAllocator { /* private fields */ }Expand description
Manages entity ID allocation with generation counting and free-list recycling.
The EntityAllocator is responsible for creating and invalidating entities
in a memory-efficient manner. It uses a generational index scheme where:
- Each slot has a generation counter that increments on deallocation
- Deallocated slots are recycled via a free list
- Entities carry their generation, allowing stale entity detection
§Design Pattern: Generational Arena Allocator
This pattern provides:
- O(1) allocation (pop from free list or push new entry)
- O(1) deallocation (push to free list, increment generation)
- O(1) liveness check (compare generations)
- Memory reuse without use-after-free bugs
§Difference from HandleAllocator
Unlike HandleAllocator<T>, which is
generic over a marker type for type safety, EntityAllocator is specifically
designed for ECS entities and produces non-generic Entity values.
§Example
use goud_engine::ecs::entity::EntityAllocator;
let mut allocator = EntityAllocator::new();
// Allocate some entities
let e1 = allocator.allocate();
let e2 = allocator.allocate();
assert!(allocator.is_alive(e1));
assert!(allocator.is_alive(e2));
// Deallocate e1
assert!(allocator.deallocate(e1));
assert!(!allocator.is_alive(e1)); // e1 is now stale
// Allocate again - may reuse e1's slot with new generation
let e3 = allocator.allocate();
assert!(allocator.is_alive(e3));
// e1 still references old generation, so it's still not alive
assert!(!allocator.is_alive(e1));§Thread Safety
EntityAllocator is NOT thread-safe. For concurrent access, wrap in
appropriate synchronization primitives (Mutex, RwLock, etc.).
Implementations§
Source§impl EntityAllocator
impl EntityAllocator
Sourcepub fn new() -> EntityAllocator
pub fn new() -> EntityAllocator
Creates a new, empty entity allocator.
The allocator starts with no pre-allocated capacity.
Use with_capacity for bulk allocation scenarios.
§Example
use goud_engine::ecs::entity::EntityAllocator;
let allocator = EntityAllocator::new();
assert_eq!(allocator.len(), 0);
assert!(allocator.is_empty());Sourcepub fn with_capacity(capacity: usize) -> EntityAllocator
pub fn with_capacity(capacity: usize) -> EntityAllocator
Creates a new entity allocator with pre-allocated capacity.
This is useful when you know approximately how many entities you’ll need, as it avoids repeated reallocations during bulk allocation.
Note that this only pre-allocates memory for the internal vectors;
it does not create any entities. Use allocate to
actually create entities.
§Arguments
capacity- The number of slots to pre-allocate
§Example
use goud_engine::ecs::entity::EntityAllocator;
// Pre-allocate space for 1000 entities
let mut allocator = EntityAllocator::with_capacity(1000);
// No entities allocated yet, but memory is reserved
assert_eq!(allocator.len(), 0);
assert!(allocator.is_empty());
// Allocations up to 1000 won't cause reallocation
for _ in 0..1000 {
allocator.allocate();
}
assert_eq!(allocator.len(), 1000);Sourcepub fn allocate(&mut self) -> Entity
pub fn allocate(&mut self) -> Entity
Allocates a new entity.
If there are slots in the free list, one is reused with an incremented generation. Otherwise, a new slot is created with generation 1.
§Returns
A new, valid entity that can be used in ECS operations.
§Panics
Panics if the number of slots exceeds u32::MAX - 1 (unlikely in practice,
as this would require over 4 billion entity allocations without reuse).
§Example
use goud_engine::ecs::entity::EntityAllocator;
let mut allocator = EntityAllocator::new();
let e1 = allocator.allocate();
let e2 = allocator.allocate();
assert_ne!(e1, e2);
assert!(!e1.is_placeholder());
assert!(!e2.is_placeholder());Sourcepub fn deallocate(&mut self, entity: Entity) -> bool
pub fn deallocate(&mut self, entity: Entity) -> bool
Deallocates an entity, making it invalid.
The slot’s generation is incremented, invalidating any entity references that use the old generation. The slot is added to the free list for future reuse.
§Arguments
entity- The entity to deallocate
§Returns
trueif the entity was valid and successfully deallocatedfalseif the entity was invalid (wrong generation, out of bounds, or PLACEHOLDER)
§Double Deallocation
Attempting to deallocate the same entity twice returns false on the
second attempt, as the generation will have already been incremented.
§Example
use goud_engine::ecs::entity::EntityAllocator;
let mut allocator = EntityAllocator::new();
let entity = allocator.allocate();
assert!(allocator.is_alive(entity));
assert!(allocator.deallocate(entity)); // First deallocation succeeds
assert!(!allocator.is_alive(entity));
assert!(!allocator.deallocate(entity)); // Second deallocation failsSourcepub fn is_alive(&self, entity: Entity) -> bool
pub fn is_alive(&self, entity: Entity) -> bool
Checks if an entity is currently alive.
An entity is “alive” if:
- It is not the PLACEHOLDER sentinel
- Its index is within bounds
- Its generation matches the current slot generation
§Arguments
entity- The entity to check
§Returns
true if the entity is alive, false otherwise.
§Example
use goud_engine::ecs::entity::EntityAllocator;
let mut allocator = EntityAllocator::new();
let entity = allocator.allocate();
assert!(allocator.is_alive(entity));
allocator.deallocate(entity);
assert!(!allocator.is_alive(entity));Sourcepub fn len(&self) -> usize
pub fn len(&self) -> usize
Returns the number of currently allocated (alive) entities.
This is the total capacity minus the number of free slots.
§Example
use goud_engine::ecs::entity::EntityAllocator;
let mut allocator = EntityAllocator::new();
assert_eq!(allocator.len(), 0);
let e1 = allocator.allocate();
let e2 = allocator.allocate();
assert_eq!(allocator.len(), 2);
allocator.deallocate(e1);
assert_eq!(allocator.len(), 1);Sourcepub fn capacity(&self) -> usize
pub fn capacity(&self) -> usize
Returns the total number of slots (both allocated and free).
This represents the high-water mark of allocations.
§Example
use goud_engine::ecs::entity::EntityAllocator;
let mut allocator = EntityAllocator::new();
let e1 = allocator.allocate();
let e2 = allocator.allocate();
assert_eq!(allocator.capacity(), 2);
allocator.deallocate(e1);
// Capacity remains 2, even though one slot is free
assert_eq!(allocator.capacity(), 2);Sourcepub fn is_empty(&self) -> bool
pub fn is_empty(&self) -> bool
Returns true if no entities are currently allocated.
§Example
use goud_engine::ecs::entity::EntityAllocator;
let mut allocator = EntityAllocator::new();
assert!(allocator.is_empty());
let entity = allocator.allocate();
assert!(!allocator.is_empty());
allocator.deallocate(entity);
assert!(allocator.is_empty());Sourcepub fn reserve(&mut self, additional: usize)
pub fn reserve(&mut self, additional: usize)
Reserves capacity for at least additional more entities.
This pre-allocates memory in the internal generations vector, avoiding reallocations during subsequent allocations. Use this when you know approximately how many entities you’ll need.
Note that this only affects the generations vector capacity. It does not create any entities or affect the free list.
§Arguments
additional- The number of additional slots to reserve
§Example
use goud_engine::ecs::entity::EntityAllocator;
let mut allocator = EntityAllocator::new();
// Allocate some entities
let initial = allocator.allocate_batch(100);
assert_eq!(allocator.len(), 100);
// Reserve space for 1000 more
allocator.reserve(1000);
// Now allocations up to capacity won't cause reallocation
let more = allocator.allocate_batch(1000);
assert_eq!(allocator.len(), 1100);Source§impl EntityAllocator
impl EntityAllocator
Sourcepub fn allocate_batch(&mut self, count: usize) -> Vec<Entity>
pub fn allocate_batch(&mut self, count: usize) -> Vec<Entity>
Allocates multiple entities at once.
This is more efficient than calling allocate in a loop
because it pre-allocates the result vector and minimizes reallocations.
§Arguments
count- The number of entities to allocate
§Returns
A vector containing count newly allocated entities. All entities are
guaranteed to be valid and unique.
§Panics
Panics if allocating count entities would exceed u32::MAX - 1 total slots.
§Performance
For large batch allocations, this method:
- Pre-allocates the result vector with exact capacity
- Reuses free slots first (LIFO order)
- Bulk-extends the generations vector for remaining slots
§Example
use goud_engine::ecs::entity::EntityAllocator;
let mut allocator = EntityAllocator::new();
// Allocate 1000 entities in one call
let entities = allocator.allocate_batch(1000);
assert_eq!(entities.len(), 1000);
assert_eq!(allocator.len(), 1000);
// All entities are unique and alive
for entity in &entities {
assert!(allocator.is_alive(*entity));
}Sourcepub fn deallocate_batch(&mut self, entities: &[Entity]) -> usize
pub fn deallocate_batch(&mut self, entities: &[Entity]) -> usize
Deallocates multiple entities at once.
This method attempts to deallocate each entity in the slice. Invalid entities (already deallocated, wrong generation, out of bounds, or PLACEHOLDER) are skipped without error.
§Arguments
entities- A slice of entities to deallocate
§Returns
The number of entities that were successfully deallocated.
§Example
use goud_engine::ecs::entity::EntityAllocator;
let mut allocator = EntityAllocator::new();
let entities = allocator.allocate_batch(100);
assert_eq!(allocator.len(), 100);
// Deallocate all at once
let deallocated = allocator.deallocate_batch(&entities);
assert_eq!(deallocated, 100);
assert_eq!(allocator.len(), 0);
// Second deallocation returns 0 (already dead)
let deallocated_again = allocator.deallocate_batch(&entities);
assert_eq!(deallocated_again, 0);Trait Implementations§
Source§impl Debug for EntityAllocator
impl Debug for EntityAllocator
Source§impl Default for EntityAllocator
impl Default for EntityAllocator
Source§fn default() -> EntityAllocator
fn default() -> EntityAllocator
Creates an empty entity allocator.
Equivalent to EntityAllocator::new().
Auto Trait Implementations§
impl Freeze for EntityAllocator
impl RefUnwindSafe for EntityAllocator
impl Send for EntityAllocator
impl Sync for EntityAllocator
impl Unpin for EntityAllocator
impl UnsafeUnpin for EntityAllocator
impl UnwindSafe for EntityAllocator
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<S> FromSample<S> for S
impl<S> FromSample<S> for S
fn from_sample_(s: S) -> S
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<F, T> IntoSample<T> for Fwhere
T: FromSample<F>,
impl<F, T> IntoSample<T> for Fwhere
T: FromSample<F>,
fn into_sample(self) -> T
Source§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<R, P> ReadPrimitive<R> for P
impl<R, P> ReadPrimitive<R> for P
Source§fn read_from_little_endian(read: &mut R) -> Result<Self, Error>
fn read_from_little_endian(read: &mut R) -> Result<Self, Error>
ReadEndian::read_from_little_endian().