using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections.Contracts; using Misaki.HighPerformance.LowLevel.Utilities; using System.Collections; using System.Diagnostics; 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 struct Enumerator : IEnumerator { private readonly UnsafeSlotMap* _collection; private int _currentIndex; public readonly ref T Current => ref _collection->_data[_currentIndex]; readonly T IEnumerator.Current => Current; readonly object? IEnumerator.Current => Current; public Enumerator(UnsafeSlotMap* collection) { _collection = collection; _currentIndex = 0; } public bool MoveNext() { _currentIndex = _collection->_validBits.NextSetBit(_currentIndex + 1); return _currentIndex != -1; } public void Reset() { _currentIndex = 0; } public void Dispose() { } } 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; public Enumerator GetEnumerator() { return new((UnsafeSlotMap*)UnsafeUtility.AddressOf(ref this)); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Invalid constructor. Use or instead."/> /// public UnsafeSlotMap() : this(0, Allocator.Invalid) { } /// /// 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. public UnsafeSlotMap(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None) : this(capacity, AllocationManager.GetAllocationHandle(allocator), allocationOption) { } /// /// 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; Add(default, out _); } public readonly void* GetUnsafePtr() { return (T*)_data.GetUnsafePtr() + 1; } public void Dispose() { _data.Dispose(); _generations.Dispose(); _freeSlots.Dispose(); _validBits.Dispose(); _count = 0; _capacity = 0; } }