k/ECS/Core/Common/SparseSet.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace Sandbox.k.ECS.Core.Common;

/// <summary>
/// A sparse set data structure that efficiently stores entity-value pairs where entities are non-negative integers.
/// Provides O(1) lookup, insertion, and removal operations.
/// </summary>
/// <typeparam name="T">The type of values associated with entities</typeparam>
public class SparseSet<T> : IEnumerable<int>
{
    // The sparse array maps entity IDs to indices in the dense array
    private List<int> _sparse = new List<int>();
    
    // The dense array contains the actual entity IDs in the order they were added
    private List<int> _dense = new List<int>();
    
    // Stores values associated with entities
    private List<T> _values = new List<T>();

    /// <summary>
    /// Gets the number of entities in the set
    /// </summary>
    public int Count => _dense.Count;

    /// <summary>
    /// Adds an entity to the sparse set without an associated value
    /// </summary>
    /// <param name="entity">The entity ID to add</param>
    public void Add(int entity)
    {
        // Use default value for the type
        Add(entity, default);
    }

    /// <summary>
    /// Adds an entity to the sparse set with an associated value
    /// </summary>
    /// <param name="entity">The entity ID to add</param>
    /// <param name="value">The value to associate with the entity</param>
    public void Add(int entity, T value)
    {
        if (entity < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(entity), "Entity ID must be non-negative");
        }

        // Ensure the sparse array is large enough
        EnsureSparseCapacity(entity);

        // If entity already exists, update its value
        if (Contains(entity))
        {
            int index = _sparse[entity];
            _values[index] = value;
            return;
        }

        // Add entity to dense array and store its index in sparse array
        _sparse[entity] = _dense.Count;
        _dense.Add(entity);
        _values.Add(value);
    }

    /// <summary>
    /// Removes an entity from the sparse set
    /// </summary>
    /// <param name="entity">The entity ID to remove</param>
    public void Remove(int entity)
    {
        if (entity < 0 || entity >= _sparse.Count || !Contains(entity))
        {
            return;
        }

        // Get index of entity in dense array
        int denseIndex = _sparse[entity];
        
        // Get the last entity in dense array
        int lastEntity = _dense[_dense.Count - 1];
        
        // Move the last entity to the position of the removed entity
        _dense[denseIndex] = lastEntity;
        _values[denseIndex] = _values[_values.Count - 1];
        
        // Update sparse array
        _sparse[lastEntity] = denseIndex;
        
        // Remove the last element from dense array
        _dense.RemoveAt(_dense.Count - 1);
        _values.RemoveAt(_values.Count - 1);
    }

    /// <summary>
    /// Checks if an entity exists in the sparse set
    /// </summary>
    /// <param name="entity">The entity ID to check</param>
    /// <returns>True if the entity exists, false otherwise</returns>
    public bool Contains(int entity)
    {
	    return entity >= 0 && 
	           entity < _sparse.Count && 
	           _sparse[entity] >= 0 &&  // Check that we have a valid index (not -1)
	           _sparse[entity] < _dense.Count && 
	           _dense[_sparse[entity]] == entity;
    }

    /// <summary>
    /// Clears all entities from the sparse set
    /// </summary>
    public void Clear()
    {
        _sparse.Clear();
        _dense.Clear();
        _values.Clear();
    }

    /// <summary>
    /// Tries to get the value associated with an entity
    /// </summary>
    /// <param name="entity">The entity ID</param>
    /// <param name="value">The associated value if found, default otherwise</param>
    /// <returns>True if the entity exists, false otherwise</returns>
    public bool TryGetValue(int entity, out T value)
    {
        if (Contains(entity))
        {
            value = _values[_sparse[entity]];
            return true;
        }

        value = default;
        return false;
    }

    /// <summary>
    /// Gets or sets the value associated with an entity
    /// </summary>
    /// <param name="entity">The entity ID</param>
    /// <returns>The value associated with the entity</returns>
    public T this[int entity]
    {
        get
        {
            if (!Contains(entity))
            {
                throw new KeyNotFoundException($"Entity {entity} does not exist in the sparse set");
            }
            return _values[_sparse[entity]];
        }
        set
        {
            Add(entity, value);
        }
    }

    /// <summary>
    /// Gets all entity-value pairs in the sparse set
    /// </summary>
    /// <returns>An enumerable of KeyValuePair containing entities and their associated values</returns>
    public IEnumerable<KeyValuePair<int, T>> GetAll()
    {
        for (int i = 0; i < _dense.Count; i++)
        {
            yield return new KeyValuePair<int, T>(_dense[i], _values[i]);
        }
    }

    /// <summary>
    /// Gets an enumerator that iterates through the entities in the sparse set
    /// </summary>
    public IEnumerator<int> GetEnumerator()
    {
        return _dense.GetEnumerator();
    }

    /// <summary>
    /// Gets a non-generic enumerator that iterates through the entities in the sparse set
    /// </summary>
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    /// <summary>
    /// Ensures the sparse array has enough capacity to store an entity with the given ID
    /// </summary>
    /// <param name="entity">The entity ID</param>
    private void EnsureSparseCapacity(int entity)
    {
        if (entity >= _sparse.Count)
        {
            // Calculate new capacity (at least entity + 1, but potentially more for efficiency)
            int newCapacity = Math.Max(entity + 1, _sparse.Count * 2);
            
            // Add placeholders to reach the new capacity
            var oldCount = _sparse.Count;
            for (int i = 0; i < newCapacity - oldCount; i++)
            {
                _sparse.Add(-1); // -1 indicates no mapping
            }
        }
    }
    
    public ref T GetRef(int entity)
    {
	    if (!Contains(entity))
		    throw new KeyNotFoundException($"Entity {entity} does not exist");
    
	    int index = _sparse[entity];
	    return ref CollectionsMarshal.AsSpan(_values)[index];
    }
}