using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections.Contracts; using Misaki.HighPerformance.LowLevel.Contracts; using Misaki.HighPerformance.LowLevel.Helpers; using System.Collections; using System.Runtime.CompilerServices; namespace Misaki.HighPerformance.LowLevel.Collections; /// /// A sparse set data structure that provides O(1) insertion, deletion, and lookup operations. /// The sparse set uses three arrays: a dense array for storing values, a sparse array for mapping indices, /// and a reverse array for mapping dense indices back to sparse indices. /// Sparse indices work like entity IDs and are automatically generated. /// /// Represents a type that can be stored in the sparse set, constrained to unmanaged types for performance and safety. public unsafe struct UnsafeSparseSet : IUnsafeCollection where T : unmanaged { public struct Enumerator : IEnumerator { private UnsafeSparseSet* _collection; private int _index; private T _value; public Enumerator(UnsafeSparseSet* collection) { _collection = collection; _index = -1; _value = default; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { _index++; if (_index < _collection->_count) { _value = UnsafeUtilities.ReadArrayElement(_collection->_dense.GetUnsafePtr(), _index); return true; } _value = default; return false; } public void Reset() { _index = -1; } public readonly T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _value; } readonly object IEnumerator.Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Current; } public unsafe readonly void Dispose() { } } public struct ParallelWriter { private UnsafeSparseSet* _sparseSet; internal ParallelWriter(UnsafeSparseSet* sparseSet) { _sparseSet = sparseSet; } /// /// Adds a value to the sparse set without resizing the internal arrays. /// /// The value to add to the sparse set. /// Returns the sparse index assigned to the value. -1 if the sparse index is out of bounds. public int AddNoResize(T value) { int sparseIndex; if (_sparseSet->_freeCount > 0) { var index = Interlocked.Decrement(ref _sparseSet->_freeCount); sparseIndex = _sparseSet->_freeList[index]; } else { sparseIndex = Interlocked.Increment(ref _sparseSet->_nextId) - 1; } if (sparseIndex >= _sparseSet->_sparse.Count) { return -1; } var count = Interlocked.Increment(ref _sparseSet->_count) - 1; _sparseSet->_dense[count] = value; _sparseSet->_sparse[sparseIndex] = count; _sparseSet->_reverse[count] = sparseIndex; return sparseIndex; } /// /// Attempts to add a value at the specified sparse index without resizing the underlying collection. /// /// The index in the sparse array where the value should be added. Must be within the valid range of the sparse /// array. /// The value to add to the collection. /// if the value was successfully added at the specified index; otherwise, . public bool AddAtNoResize(int sparseIndex, T value) { if (sparseIndex < 0 || sparseIndex >= _sparseSet->_sparse.Count) { return false; } if (_sparseSet->Contains(sparseIndex)) { return false; } if (_sparseSet->_count >= _sparseSet->_dense.Count) { return false; } var count = Interlocked.Increment(ref _sparseSet->_count) - 1; _sparseSet->_dense[count] = value; _sparseSet->_sparse[sparseIndex] = count; _sparseSet->_reverse[count] = sparseIndex; return true; } /// /// Removes a value at the specified sparse index without resizing the internal arrays. /// /// The sparse index of the value to remove. /// Returns if the value was successfully removed; otherwise, . public bool RemoveNoResize(int sparseIndex) { if (!_sparseSet->Contains(sparseIndex)) { return false; } var denseIndex = _sparseSet->_sparse[sparseIndex]; var lastIndex = _sparseSet->_count - 1; if (denseIndex != lastIndex) { var lastValue = _sparseSet->_dense[lastIndex]; var lastSparseIndex = _sparseSet->_reverse[lastIndex]; _sparseSet->_dense[denseIndex] = lastValue; _sparseSet->_reverse[denseIndex] = lastSparseIndex; _sparseSet->_sparse[lastSparseIndex] = denseIndex; } _sparseSet->_sparse[sparseIndex] = -1; if (_sparseSet->_freeCount >= _sparseSet->_freeList.Count) { return false; } _sparseSet->_freeList[_sparseSet->_freeCount] = sparseIndex; Interlocked.Increment(ref _sparseSet->_freeCount); Interlocked.Decrement(ref _sparseSet->_count); return true; } } private UnsafeArray _dense; private UnsafeArray _sparse; private UnsafeArray _reverse; // Maps dense index to sparse index private UnsafeArray _freeList; // Stack of available sparse indices private int _count; private int _nextId; // Next available sparse index private int _freeCount; // Number of items in the free list public readonly int Count => _count; public readonly int Capacity => _dense.Count; public readonly bool IsCreated => _dense.IsCreated && _sparse.IsCreated && _reverse.IsCreated && _freeList.IsCreated; public readonly ref T this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _dense[index]; } public IEnumerator GetEnumerator() => new Enumerator((UnsafeSparseSet*)UnsafeUtilities.AddressOf(ref this)); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// /// Constructs an UnsafeSparseSet with a default size of 1 and uses the Persistent allocator. /// public UnsafeSparseSet() : this(1, Allocator.Persistent) { } /// /// Initializes a new instance of UnsafeSparseSet with a specified capacity and an allocation handle. /// /// Specifies the initial capacity of the sparse set, which must be greater than zero. /// A reference to an AllocationHandle that manages the memory allocation for the sparse set. /// Specifies how the memory should be allocated. /// Thrown when the specified capacity is less than or equal to zero. public UnsafeSparseSet(int capacity, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None) { if (capacity <= 0) { throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero."); } _dense = new UnsafeArray(capacity, ref handle, allocationOption); _sparse = new UnsafeArray(capacity, ref handle, allocationOption); _reverse = new UnsafeArray(capacity, ref handle, allocationOption); _freeList = new UnsafeArray(capacity, ref handle, allocationOption); _count = 0; _nextId = 0; _freeCount = 0; Clear(); } /// /// Initializes a new instance of UnsafeSparseSet with a specified capacity and an allocation type. /// /// Specifies the initial capacity of the sparse set, which must be greater than zero. /// Specifies the allocator to use for memory allocation, which determines the memory management strategy. /// Determines how the memory should be allocated. /// Thrown when the specified capacity is less than or equal to zero. public UnsafeSparseSet(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None) : this(capacity, ref AllocationManager.GetAllocationHandle(allocator), allocationOption) { } /// /// Adds a value to the sparse set and returns a unique sparse index for the value. /// /// The value to add to the sparse set. /// A unique sparse index that can be used to reference this value. public int Add(T value) { int sparseIndex; // Try to reuse a freed sparse index first if (_freeCount > 0) { _freeCount--; sparseIndex = _freeList[_freeCount]; } else { // Use the next available ID sparseIndex = _nextId++; // Resize sparse array if necessary if (sparseIndex >= _sparse.Count) { ResizeSparse(sparseIndex + 1); } } // Resize dense arrays if necessary if (_count >= _dense.Count) { var newCapacity = _dense.Count + Math.Max(1, _dense.Count / 2); _dense.Resize(newCapacity); _reverse.Resize(newCapacity); } // Add the value to the dense array and update mappings _dense[_count] = value; _sparse[sparseIndex] = _count; _reverse[_count] = sparseIndex; _count++; return sparseIndex; } /// /// Adds a value to the sparse set at the specified sparse index. /// This method is provided for compatibility when you need to specify the exact sparse index. /// /// The index in the sparse array where the value should be mapped. /// The value to add to the sparse set. /// True if the value was added, false if the sparse index is already occupied. public bool AddAt(int sparseIndex, T value) { if (sparseIndex < 0) { throw new ArgumentOutOfRangeException(nameof(sparseIndex), "Sparse index must be non-negative."); } if (sparseIndex >= _sparse.Count) { ResizeSparse(sparseIndex + 1); } if (Contains(sparseIndex)) { return false; } if (_count >= _dense.Count) { var newCapacity = _dense.Count + Math.Max(1, _dense.Count / 2); _dense.Resize(newCapacity); _reverse.Resize(newCapacity); } // Add the value to the dense array and update mappings _dense[_count] = value; _sparse[sparseIndex] = _count; _reverse[_count] = sparseIndex; // Store reverse mapping _count++; // Update _nextId if we're using a higher ID if (sparseIndex >= _nextId) { _nextId = sparseIndex + 1; } return true; } /// /// Removes the value at the specified sparse index. /// /// The sparse index of the value to remove. /// True if the value was removed, false if the sparse index was not found. public bool Remove(int sparseIndex) { if (!Contains(sparseIndex)) { return false; } var denseIndex = _sparse[sparseIndex]; var lastIndex = _count - 1; if (denseIndex != lastIndex) { // Move the last element to the position of the removed element var lastValue = _dense[lastIndex]; var lastSparseIndex = _reverse[lastIndex]; // Get sparse index of last element _dense[denseIndex] = lastValue; _reverse[denseIndex] = lastSparseIndex; // Update the sparse mapping for the moved element _sparse[lastSparseIndex] = denseIndex; } // Mark the sparse index as unused and add to free list _sparse[sparseIndex] = -1; // Add the freed sparse index to the free list for reuse if (_freeCount >= _freeList.Count) { _freeList.Resize(_freeList.Count + Math.Max(1, _freeList.Count / 2)); } _freeList[_freeCount] = sparseIndex; _freeCount++; _count--; return true; } /// /// Checks if the sparse set contains a value at the specified sparse index. /// /// The sparse index to check. /// True if the sparse index is valid and contains a value, false otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly bool Contains(int sparseIndex) { if (sparseIndex < 0 || sparseIndex >= _sparse.Count) { return false; } var denseIndex = _sparse[sparseIndex]; return denseIndex >= 0 && denseIndex < _count; } /// /// Gets the value at the specified sparse index. /// /// The sparse index to retrieve the value from. /// When this method returns, contains the value at the specified sparse index, if found. /// True if the sparse index contains a value, false otherwise. public readonly bool TryGetValue(int sparseIndex, out T value) { if (Contains(sparseIndex)) { var denseIndex = _sparse[sparseIndex]; value = _dense[denseIndex]; return true; } value = default; return false; } /// /// Gets the value at the specified sparse index. /// /// The sparse index to retrieve the value from. /// The value at the specified sparse index. /// Thrown when the sparse index is not found. public readonly T GetValue(int sparseIndex) { if (!Contains(sparseIndex)) { throw new ArgumentOutOfRangeException(nameof(sparseIndex), "Sparse index not found in the set."); } var denseIndex = _sparse[sparseIndex]; return _dense[denseIndex]; } /// /// Updates the value at the specified sparse index. /// /// The sparse index of the value to update. /// The new value. /// True if the value was updated, false if the sparse index was not found. public bool SetValue(int sparseIndex, T value) { if (!Contains(sparseIndex)) { return false; } var denseIndex = _sparse[sparseIndex]; _dense[denseIndex] = value; return true; } private void ResizeSparse(int newSize) { var oldSize = _sparse.Count; _sparse.Resize(newSize); _sparse.AsSpan()[oldSize..newSize].Fill(-1); } /// public void Clear() { if (!IsCreated) { return; } _sparse.AsSpan().Fill(-1); _count = 0; _nextId = 0; _freeCount = 0; } /// public void Resize(int newSize) { if (newSize <= 0) { throw new ArgumentOutOfRangeException(nameof(newSize), "New size must be greater than zero."); } _dense.Resize(newSize); _reverse.Resize(newSize); _freeList.Resize(newSize); if (newSize > _sparse.Count) { ResizeSparse(newSize); } if (_count > newSize) { _count = newSize; } } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly void* GetUnsafePtr() { return _dense.GetUnsafePtr(); } /// /// Converts the current sparse set to an UnsafeArray representation using its dense array. /// /// Returns a new UnsafeArray instance initialized with the dense array's pointer and count. [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly UnsafeArray AsUnsafeArray() { return new UnsafeArray((T*)_dense.GetUnsafePtr(), _count); } /// public void Dispose() { _dense.Dispose(); _sparse.Dispose(); _reverse.Dispose(); _freeList.Dispose(); _count = 0; _nextId = 0; _freeCount = 0; } }