Repository: bevyengine/bevy
File: crates/bevy_ecs/src/entity/remote_allocator.rs (Rust)

Code before:
    fn remote_alloc(&self) -> Entity {
        self.free
            .remote_alloc()
            .unwrap_or_else(|| self.fresh.alloc())
    }

    /// Marks the allocator as closed, but it will still function normally.
    fn close(&self) {
        self.is_closed.store(true, Ordering::Release);
    }

    /// Returns true if [`Self::close`] has been called.
    fn is_closed(&self) -> bool {
        self.is_closed.load(Ordering::Acquire)
    }
}

/// This keeps track of freed entities and allows the allocation of new ones.
///
/// Note that this must not implement [`Clone`].
/// The allocator assumes that it is the only one with [`FreeList::free`] permissions.
/// If this were cloned, that assumption would be broken, leading to undefined behavior.
/// This is in contrast to the [`RemoteAllocator`], which may be cloned freely.
pub(super) struct Allocator {
    shared: Arc<SharedAllocator>,
    quick_free: ArrayVec<Entity, 64>,
}

impl Default for Allocator {
    fn default() -> Self {
        Self::new()
    }
}

impl Allocator {
    /// Constructs a new [`Allocator`]
    pub(super) fn new() -> Self {
        Self {
            shared: Arc::new(SharedAllocator::new()),
            quick_free: ArrayVec::new(),
        }
    }

    /// Allocates a new [`Entity`], reusing a freed index if one exists.
    #[inline]
    pub(super) fn alloc(&self) -> Entity {
        // SAFETY: violating safety requires a `&mut self` to exist, but rust does not allow that.
        unsafe { self.shared.alloc() }
    }

    /// The total number of indices given out.

Review comment (suggestion):
> It also means that there will be at most 64 freed entities that simply can't be allocated. 

Would it make sense to have `Allocator::alloc` check these first, and only call `shared.alloc()` when `quick_free` is empty?  That would allow those to be reused, and would sometimes reduce the number of atomic ops required during allocation, at the cost of an extra non-atomic op when `quick_free` was empty.  

Code after:
    #[inline]
    fn remote_alloc(&self) -> Entity {
        self.free
            .remote_alloc()
            .unwrap_or_else(|| self.fresh.alloc())
    }

    /// Marks the allocator as closed, but it will still function normally.
    fn close(&self) {
        self.is_closed.store(true, Ordering::Release);
    }

    /// Returns true if [`Self::close`] has been called.
    fn is_closed(&self) -> bool {
        self.is_closed.load(Ordering::Acquire)
    }
}

/// This keeps track of freed entities and allows the allocation of new ones.
///
/// Note that this must not implement [`Clone`].
/// The allocator assumes that it is the only one with [`FreeList::free`] permissions.
/// If this were cloned, that assumption would be broken, leading to undefined behavior.
/// This is in contrast to the [`RemoteAllocator`], which may be cloned freely.
pub(super) struct Allocator {
    /// The shared allocator state, which we share with any [`RemoteAllocator`]s.
    shared: Arc<SharedAllocator>,
    /// The local free list.
    /// We use this to amortize the cost of freeing to the shared allocator since that is expensive.
    local_free: Box<ArrayVec<Entity, 128>>,
}

impl Default for Allocator {
    fn default() -> Self {
        Self::new()
    }
}

impl Allocator {
    /// Constructs a new [`Allocator`]
    pub(super) fn new() -> Self {
        Self {
            shared: Arc::new(SharedAllocator::new()),
            local_free: Box::new(ArrayVec::new()),
        }
    }

    /// Allocates a new [`Entity`], reusing a freed index if one exists.
    #[inline]
    pub(super) fn alloc(&self) -> Entity {
        // SAFETY: violating safety requires a `&mut self` to exist, but rust does not allow that.