namespace OECS; /// /// Manages entity ID allocation and recycling. /// /// Uses a free-list for recycled IDs and a bump allocator for new IDs. /// Each ID slot tracks a version byte that increments on recycle to /// prevent use-after-free bugs. /// internal class EntityAllocator { private const int DefaultCapacity = 1024; private const uint FirstValidId = 1; // 0 = Null private byte[] _versions; private readonly Stack _freeIds; private uint _nextId; public EntityAllocator(int initialCapacity = DefaultCapacity) { _versions = new byte[initialCapacity]; _freeIds = new Stack(); _nextId = FirstValidId; } /// /// Allocates a new entity. Reuses a free ID if available, otherwise /// bumps the allocator. /// public Entity Allocate() { uint id; if (_freeIds.Count > 0) { id = _freeIds.Pop(); } else { id = _nextId++; EnsureCapacity(id); // First time this ID is used — start at version 1. // 0 means "never alive" and is used by the sentinel. _versions[id] = 1; } return new Entity(id, _versions[id]); } /// /// Frees an entity ID, making it available for reuse. /// Increments the version to invalidate any outstanding references. /// public void Free(Entity entity) { uint id = entity.Id; EnsureCapacity(id); // Increment version (wrap at 255 back to 1; 0 means "never alive"). _versions[id]++; if (_versions[id] == 0) _versions[id] = 1; _freeIds.Push(id); } /// /// Reserves a specific entity ID and version for deserialization. /// Advances past the reserved ID so future /// allocations don't collide. /// public void Reserve(Entity entity) { uint id = entity.Id; EnsureCapacity(id); _versions[id] = (byte)entity.Version; if (id >= _nextId) _nextId = id + 1; } /// /// Returns true if the entity's version matches the current version for its ID. /// public bool IsAlive(Entity entity) { uint id = entity.Id; if (entity.IsNull) return false; if (id >= _versions.Length) return false; return _versions[id] == entity.Version && _versions[id] != 0; } private void EnsureCapacity(uint id) { if (id >= _versions.Length) { int newSize = Math.Max((int)id + 1, _versions.Length * 2); Array.Resize(ref _versions, newSize); } } }