using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections.Contracts; using Misaki.HighPerformance.LowLevel.Utilities; using System.Collections; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace Misaki.HighPerformance.LowLevel.Collections; internal class UnsafeSlotMapDebugView where T : unmanaged { private readonly UnsafeSlotMap _slotMap; public UnsafeSlotMapDebugView(UnsafeSlotMap slotMap) { _slotMap = slotMap; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public T[] Items { get { var items = new List(_slotMap.Count); var enumerator = _slotMap.GetEnumerator(); while (enumerator.MoveNext()) { items.Add(enumerator.Current); } return items.ToArray(); } } } /// /// Provides an unsafe, high-performance slot map for storing and managing unmanaged values, supporting fast insertion, /// removal, and lookup by slot index and Generation. /// /// The type of value to store in the slot map. Must be unmanaged. [DebuggerTypeProxy(typeof(UnsafeSlotMapDebugView<>))] public unsafe struct UnsafeSlotMap : IUnsafeCollection where T : unmanaged { public ref struct Enumerator { private ref UnsafeSlotMap _collection; private int _currentIndex; public readonly ref T Current => ref _collection._data[_currentIndex]; public Enumerator(ref UnsafeSlotMap collection) { _collection = ref collection; _currentIndex = -1; } public bool MoveNext() { _currentIndex = _collection._validBits.NextSetBit(_currentIndex + 1); return _currentIndex != -1; } public void Reset() { _currentIndex = -1; } } private UnsafeArray _data; private UnsafeArray _generations; private UnsafeQueue _freeSlots; private UnsafeBitSet _validBits; private int _count; private int _capacity; public readonly int Count => _count; public readonly int Capacity => _capacity; public readonly bool IsCreated => _data.IsCreated && _generations.IsCreated && _freeSlots.IsCreated && _validBits.IsCreated; /// /// Initializes a new instance of UnsafeSlotMap with a default size of 1 and a persistent allocation handle. /// public UnsafeSlotMap() : this(1, AllocationHandle.Persistent) { } /// /// Initializes a new instance of the UnsafeSlotMap class with the specified capacity, allocation handle, and /// allocation options. /// /// The number of slots to allocate for the map. Must be greater than zero. /// A reference to the allocation handle used to manage memory for the slot map. /// The allocation options to use when creating internal data structures. The default is AllocationOption.None. /// Thrown when capacity is less than or equal to zero. public UnsafeSlotMap(int capacity, AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None) { if (capacity <= 0) { throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero."); } _data = new UnsafeArray(capacity, handle, allocationOption); _generations = new UnsafeArray(capacity, handle, allocationOption); _freeSlots = new UnsafeQueue(capacity, handle, allocationOption); _validBits = new UnsafeBitSet(capacity, handle, allocationOption); if (!allocationOption.HasFlag(AllocationOption.Clear)) { _generations.AsSpan().Clear(); _validBits.ClearAll(); } _count = 0; _capacity = capacity; } /// /// Initializes a new instance of the UnsafeSlotMap class with the specified capacity, allocator, and allocation /// options. /// /// The initial number of slots to allocate for the map. Must be greater than zero. /// The allocator to use for memory management of the slot map. /// The allocation option that determines how memory is allocated. The default is AllocationOption.None. [Obsolete("Use AllocationHandle instead.")] public UnsafeSlotMap(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None) : this(capacity, AllocationManager.GetAllocationHandle(allocator), allocationOption) { } [MethodImpl(MethodImplOptions.AggressiveInlining)] [UnscopedRef] public Enumerator GetEnumerator() { return new Enumerator(ref this); } /// /// Adds the specified item to the collection and returns the index of the slot where it was stored. /// /// The item to add to the collection. /// When this method returns, contains the Generation number associated with the slot where the item was stored. /// The index of the slot in which the item was stored. public int Add(T item, out int generation) { if (_count >= _capacity) { Resize(Math.Max(1, _capacity * 2)); } int index; if (_freeSlots.Count == 0) { index = _count; } else { index = _freeSlots.Dequeue(); } _data[index] = item; _validBits.SetBit(index); _count++; generation = _generations[index]; return index; } /// /// Attempts to remove the item at the specified slot index and Generation from the collection. /// /// The zero-based index of the slot to remove. Must be within the valid range of slot indices. /// The Generation value associated with the slot. Removal succeeds only if this matches the current Generation of the slot. /// When this method returns, contains the item that was removed if the removal was successful; otherwise, the default value for type . /// true if the item was successfully removed; otherwise, false. public bool Remove(int slotIndex, int generation, out T item) { item = default; if (slotIndex < 0 || slotIndex >= _capacity) { return false; } ref var gen = ref _generations[slotIndex]; if (gen != generation) { return false; } item = _data[slotIndex]; gen++; _validBits.ClearBit(slotIndex); _freeSlots.Enqueue(slotIndex); _count--; return true; } /// /// Attempts to remove the item at the specified slot index and Generation from the collection. /// /// The zero-based index of the slot to remove. Must be within the valid range of slot indices. /// The Generation value associated with the slot. Removal succeeds only if this matches the current Generation of /// the slot. /// true if the item was successfully removed; otherwise, false. public bool Remove(int slotIndex, int generation) { return Remove(slotIndex, generation, out _); } /// /// Determines whether the specified slot index contains a valid entry with the given Generation. /// /// The zero-based index of the slot to check. Must be greater than or equal to 0 and less than the current capacity. /// The Generation value to compare against the slot's Generation. /// true if the slot at the specified index is valid and its Generation matches the specified value; otherwise, false. public readonly bool Contains(int slotIndex, int generation) { if (slotIndex < 0 || slotIndex >= _capacity) { return false; } if (_validBits.IsSet(slotIndex) && _generations[slotIndex] == generation) { return true; } return false; } /// /// Attempts to retrieve the element at the specified slot index and Generation. /// /// The zero-based index of the slot to retrieve. Must be within the valid range of slots. /// The Generation identifier associated with the slot. Used to verify that the slot has not been replaced or /// invalidated. /// When this method returns, contains the element at the specified slot and Generation if found; otherwise, the /// default value for type . /// true if the element at the specified slot index and Generation is found; otherwise, false. public readonly bool TryGetElementAt(int slotIndex, int generation, out T value) { if (!Contains(slotIndex, generation)) { value = default; return false; } value = _data[slotIndex]; return true; } /// /// Retrieves the element stored at the specified slot index and Generation. /// /// The zero-based index of the slot from which to retrieve the element. Must be within the valid range of allocated slots. /// The Generation identifier associated with the slot. Used to ensure the element has not been replaced or removed since allocation. /// The element of type stored at the specified slot and Generation. /// Thrown when is less than zero or greater than or equal to the capacity. /// Thrown when the specified slot is not occupied or the Generation does not match. public readonly T GetElementAt(int slotIndex, int generation) { if (!Contains(slotIndex, generation)) { throw new InvalidOperationException("The specified slot is not occupied or the generation does not match."); } return _data[slotIndex]; } /// /// Returns a reference to the element at the specified slot index and Generation, if it exists; otherwise, returns /// a null reference. /// /// The zero-based index of the slot to retrieve. Must be within the valid range of allocated slots. /// The expected Generation value for the slot. Used to verify that the slot has not been recycled or replaced. /// When this method returns, contains if a valid element exists at the specified slot and Generation; otherwise, . /// A reference to the element of type at the specified slot and Generation if it exists; otherwise, a null reference. public ref T GetElementReferenceAt(int slotIndex, int generation, out bool exist) { if (!Contains(slotIndex, generation)) { exist = false; return ref Unsafe.NullRef(); } exist = true; return ref _data[slotIndex]; } public void Resize(int newSize, AllocationOption option = AllocationOption.None) { _data.Resize(newSize, option); _generations.Resize(newSize, option | AllocationOption.Clear); _freeSlots.Resize(newSize, option); _validBits.Resize(newSize, option); _capacity = newSize; } public void Clear() { _generations.Clear(); _freeSlots.Clear(); _validBits.ClearAll(); _count = 0; } public readonly void* GetUnsafePtr() { return (T*)_data.GetUnsafePtr(); } public void Dispose() { _data.Dispose(); _generations.Dispose(); _freeSlots.Dispose(); _validBits.Dispose(); _count = 0; _capacity = 0; } }