using System.Runtime.CompilerServices; namespace OECS; /// /// Non-generic interface for sparse set operations that don't require /// knowing the component type at compile time (e.g., entity destruction). /// internal interface ISparseSet { void Remove(Entity entity); bool Contains(Entity entity); int Count { get; } } /// /// A sparse set storing components of type . /// /// Uses a dense array (packed, no holes) and a sparse array (maps entity ID /// to dense index) for O(1) add, remove, and lookup. Swap-remove keeps /// removals cheap at the cost of iteration order instability. /// internal class SparseSet : ISparseSet where T : struct { private const int DefaultCapacity = 64; private T[] _dense; private Entity[] _denseEntities; private int[] _sparse; private int _count; public SparseSet(int initialCapacity = DefaultCapacity) { _dense = new T[initialCapacity]; _denseEntities = new Entity[initialCapacity]; _sparse = new int[initialCapacity]; Array.Fill(_sparse, -1); } /// /// Number of components currently stored. /// public int Count => _count; /// /// The dense array of component values (first elements are valid). /// public T[] Dense => _dense; /// /// The dense array of entity IDs, parallel to . /// public Entity[] DenseEntities => _denseEntities; /// /// Adds a component for the given entity. Replaces if already present. /// public void Add(Entity entity, T component) { EnsureSparseCapacity(entity.Id); int index = _sparse[entity.Id]; if (index != -1) { // Already exists — replace in place. _dense[index] = component; return; } EnsureDenseCapacity(_count + 1); index = _count; _dense[index] = component; _denseEntities[index] = entity; _sparse[entity.Id] = index; _count++; } /// /// Removes the component for the given entity. No-op if not present. /// public void Remove(Entity entity) { if (entity.Id >= (uint)_sparse.Length) return; int index = _sparse[entity.Id]; if (index == -1) return; // Swap-remove: move the last element into the removed slot. int lastIndex = _count - 1; if (index != lastIndex) { _dense[index] = _dense[lastIndex]; _denseEntities[index] = _denseEntities[lastIndex]; _sparse[_denseEntities[index].Id] = index; } _dense[lastIndex] = default!; _denseEntities[lastIndex] = default; _sparse[entity.Id] = -1; _count--; } /// /// Returns a reference to the component for the given entity. /// Throws if the entity does not have this component. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T Get(Entity entity) { int index = _sparse[entity.Id]; return ref _dense[index]; } /// /// Returns true if the entity has this component. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Contains(Entity entity) { return entity.Id < (uint)_sparse.Length && _sparse[entity.Id] != -1; } /// /// Removes all components. /// public void Clear() { Array.Clear(_dense, 0, _count); for (int i = 0; i < _count; i++) { _sparse[_denseEntities[i].Id] = -1; } _count = 0; } private void EnsureSparseCapacity(uint entityId) { if (entityId >= (uint)_sparse.Length) { int newSize = Math.Max((int)entityId + 1, _sparse.Length * 2); Array.Resize(ref _sparse, newSize); Array.Fill(_sparse, -1, _sparse.Length - (newSize - _sparse.Length), newSize - _sparse.Length); } } private void EnsureDenseCapacity(int required) { if (required > _dense.Length) { int newSize = Math.Max(required, _dense.Length * 2); Array.Resize(ref _dense, newSize); Array.Resize(ref _denseEntities, newSize); } } }