Skip to main content

EntityAllocator

Struct EntityAllocator 

Source
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

Source

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());
Source

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);
Source

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());
Source

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
  • true if the entity was valid and successfully deallocated
  • false if 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 fails
Source

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));
Source

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);
Source

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);
Source

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());
Source

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

Source

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));
}
Source

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

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for EntityAllocator

Source§

fn default() -> EntityAllocator

Creates an empty entity allocator.

Equivalent to EntityAllocator::new().

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<S> FromSample<S> for S

Source§

fn from_sample_(s: S) -> S

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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 more
Source§

impl<F, T> IntoSample<T> for F
where T: FromSample<F>,

Source§

fn into_sample(self) -> T

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<R, P> ReadPrimitive<R> for P
where R: Read + ReadEndian<P>, P: Default,

Source§

fn read_from_little_endian(read: &mut R) -> Result<Self, Error>

Read this value from the supplied reader. Same as ReadEndian::read_from_little_endian().
Source§

fn read_from_big_endian(read: &mut R) -> Result<Self, Error>

Read this value from the supplied reader. Same as ReadEndian::read_from_big_endian().
Source§

fn read_from_native_endian(read: &mut R) -> Result<Self, Error>

Read this value from the supplied reader. Same as ReadEndian::read_from_native_endian().
Source§

impl<T, U> ToSample<U> for T
where U: FromSample<T>,

Source§

fn to_sample_(self) -> U

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,

Source§

impl<T> Event for T
where T: Send + Sync + 'static,

Source§

impl<T> Resource for T
where T: Send + Sync + 'static,