87 lines
2.4 KiB
C#
87 lines
2.4 KiB
C#
|
|
namespace OECS;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 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.
|
||
|
|
/// </summary>
|
||
|
|
internal class EntityAllocator
|
||
|
|
{
|
||
|
|
private const int DefaultCapacity = 1024;
|
||
|
|
private const uint FirstValidId = 2; // 0 = Null, 1 = Singleton
|
||
|
|
|
||
|
|
private byte[] _versions;
|
||
|
|
private readonly Stack<uint> _freeIds;
|
||
|
|
private uint _nextId;
|
||
|
|
|
||
|
|
public EntityAllocator(int initialCapacity = DefaultCapacity)
|
||
|
|
{
|
||
|
|
_versions = new byte[initialCapacity];
|
||
|
|
_freeIds = new Stack<uint>();
|
||
|
|
_nextId = FirstValidId;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Allocates a new entity. Reuses a free ID if available, otherwise
|
||
|
|
/// bumps the allocator.
|
||
|
|
/// </summary>
|
||
|
|
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]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Frees an entity ID, making it available for reuse.
|
||
|
|
/// Increments the version to invalidate any outstanding references.
|
||
|
|
/// </summary>
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Returns true if the entity's version matches the current version for its ID.
|
||
|
|
/// </summary>
|
||
|
|
public bool IsAlive(Entity entity)
|
||
|
|
{
|
||
|
|
uint id = entity.Id;
|
||
|
|
if (entity.IsNull) return false;
|
||
|
|
if (id == 1) return true; // Singleton is always alive.
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|