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 UnsafeListDebugView where T : unmanaged { private readonly UnsafeList _list; public UnsafeListDebugView(UnsafeList list) { _list = list; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public T[] Items { get { var array = new T[_list.Count]; for (var i = 0; i < _list.Count; i++) { array[i] = _list[i]; } return array; } } } /// /// A collection that allows for unsafe operations on a list of unmanaged types. /// /// Represents a type that can be stored in the collection, constrained to unmanaged types for performance and safety. [DebuggerTypeProxy(typeof(UnsafeListDebugView<>))] public unsafe struct UnsafeList : IUnsafeCollection where T : unmanaged { public struct Enumerator : IEnumerator { private readonly UnsafeList* _collection; private int _index; public readonly ref T Current => ref _collection->_array[_index]; readonly T IEnumerator.Current => Current; readonly object IEnumerator.Current => Current; public Enumerator(UnsafeList* collection) { _collection = collection; _index = -1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { _index++; return _index < _collection->_count; } public void Reset() { _index = -1; } public readonly void Dispose() { } } /// /// A Parallel reader for an UnsafeList. /// /// /// Use to create a parallel reader for a list. /// The list must live at least as long as the parallel reader, and the parallel reader must not be used after the list is disposed. /// public readonly unsafe struct ParallelReader { public readonly UnsafeList* listData; public readonly int Count => listData->_count; public ref readonly T this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref listData->_array[index]; } public ref readonly T this[uint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref listData->_array[index]; } internal ParallelReader(UnsafeList* list) { listData = list; } public readonly Enumerator GetEnumerator() { return new Enumerator(listData); } public readonly ReadOnlySpan AsSpan() { return new ReadOnlySpan(listData->_array.GetUnsafePtr(), listData->_count); } } /// /// A parallel writer for an UnsafeList. /// /// /// Use to create a parallel writer for a list. /// The list must live at least as long as the parallel writer, and the parallel writer must not be used after the list is disposed. /// public readonly unsafe struct ParallelWriter { public readonly UnsafeList* listData; internal ParallelWriter(UnsafeList* list) { listData = list; } /// /// Adds a value to a collection without resizing it, ensuring capacity is checked before insertion. /// /// The value to be added to the collection. public void AddNoResize(T value) { var idx = Interlocked.Increment(ref listData->_count) - 1; listData->CheckNoResizeCapacity(idx, 1); UnsafeUtility.WriteArrayElement(listData->_array.GetUnsafePtr(), idx, value); } /// /// Adds a specified number of elements from a pointer to a buffer without resizing the underlying storage. /// /// Points to the source data to be copied into the buffer. /// Indicates the number of elements to be added from the source data. public void AddRangeNoResize(ReadOnlySpan collection, int count) { var index = Interlocked.Add(ref listData->_count, count) - count; listData->CheckNoResizeCapacity(index, count); fixed (T* pCollection = collection) { MemCpy(UnsafeUtility.ReadArrayElementUnsafe(listData->_array.GetUnsafePtr(), index), pCollection, (uint)(count * sizeof(T))); } } } private UnsafeArray _array; private int _count; public readonly int Count => _count; public readonly int Capacity => _array.Count; public readonly bool IsCreated => _array.IsCreated; public readonly ref T this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _array[index]; } public readonly ref T this[uint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _array[index]; } /// /// Invalid constructor, use or instead. /// public UnsafeList() : this(0, Allocator.Invalid) { } /// /// Initializes a new instance of UnsafeList with a specified number of initial capacity and an allocation handle. /// /// Specifies the number of initial capacity to allocate in the list, which must be greater than zero. /// A reference to an AllocationHandle that manages the memory allocation for the array. /// Specifies how the memory should be allocated. public UnsafeList(int capacity, AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None) { _array = new UnsafeArray(capacity, handle, allocationOption); _count = 0; } /// /// Initializes a new instance of UnsafeList with a specified number of initial capacity and an allocation type. /// /// Specifies the number of initial capacity to allocate in the list, 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. public UnsafeList(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None) : this(capacity, AllocationManager.GetAllocationHandle(allocator), allocationOption) { } private readonly void CheckNoResizeCapacity(int count) { CheckNoResizeCapacity(count, Count); } private readonly void CheckNoResizeCapacity(int index, int count) { if (index + count > Capacity) { throw new Exception($"AddNoResize assumes that list capacity is sufficient (Capacity {Capacity}, Size {Count}), requested count {count}!"); } } private readonly void CheckIndexCount(int index, int count) { if (count < 0) { throw new ArgumentOutOfRangeException($"Value for count {count} must be positive."); } if (index < 0) { throw new ArgumentOutOfRangeException($"Value for index {index} must be positive."); } if (index > Count) { throw new ArgumentOutOfRangeException($"Value for index {index} is out of bounds."); } if (index + count > Count) { throw new ArgumentOutOfRangeException($"Value for count {count} is out of bounds."); } } public Enumerator GetEnumerator() { return new((UnsafeList*)UnsafeUtility.AddressOf(ref this)); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Provides a parallel reader for the current list, enabling thread-safe read operations. /// /// /// The list must live at least as long as the parallel reader, and the parallel reader must not be used after the list is disposed. /// For example, if you need to access the list in job system and wait that job in another stack frame, please always allocate the list struct itself on heap. /// Otherwise the parallel reader will be invalid after the stack frame that creates the list is popped, even if the list's internal array is still valid. /// /// A instance that can be used to read items from the list in a thread-safe manner. public ParallelReader AsParallelReader() { return new((UnsafeList*)UnsafeUtility.AddressOf(ref this)); } /// /// Provides a parallel writer for the current list, enabling thread-safe additions to the list. /// /// /// The list must live at least as long as the parallel writer, and the parallel writer must not be used after the list is disposed. /// For example, if you need to access the list in job system and wait that job in another stack frame, please always allocate the list struct itself on heap. /// Otherwise the parallel writer will be invalid after the stack frame that creates the list is popped, even if the list's internal array is still valid. /// /// A instance that can be used to add items to the list in a thread-safe manner. public ParallelWriter AsParallelWriter() { return new((UnsafeList*)UnsafeUtility.AddressOf(ref this)); } /// /// Converts the current list to an UnsafeArray representation. /// /// /// The returned shares the same underlying data as the list and does not own the memory. /// /// A new instance. [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly UnsafeArray AsUnsafeArray() { return new UnsafeArray((T*)_array.GetUnsafePtr(), _count); } /// /// Converts the current list to a read-only collection that provides unsafe access to its elements. /// /// A new instance that allows for read-only access to the list's elements without copying. public readonly ReadOnlyUnsafeCollection AsReadOnly() { return new ReadOnlyUnsafeCollection((T*)_array.GetUnsafePtr(), _count); } /// /// Adds a new element to the end of the list, resizing the internal array if necessary. /// /// The element to be added to the list. public void Add(T value) { if (_count >= Capacity) { Resize(Math.Max(1, Capacity * 2)); } UnsafeUtility.WriteArrayElement(_array.GetUnsafePtr(), _count, value); _count++; } /// /// Adds the specified value to the collection without resizing the underlying storage. /// /// The value to add to the collection. public void AddNoResize(T value) { CheckNoResizeCapacity(1); UnsafeUtility.WriteArrayElement(_array.GetUnsafePtr(), _count, value); _count++; } /// /// Adds a range of elements to the collection. /// /// A span containing the elements to add. The span must not exceed the specified . public void AddRange(Span values) { var newSize = _count + values.Length; if (newSize > Capacity) { Resize(Capacity + values.Length); } fixed (T* ptr = values) { MemCpy(UnsafeUtility.ReadArrayElementUnsafe(_array.GetUnsafePtr(), _count), ptr, (uint)(values.Length * sizeof(T))); } _count += values.Length; } /// /// Adds the elements of the specified collection to the current list without resizing the underlying storage. /// /// A read-only span containing the elements to add. The span must not exceed the available capacity. public void AddRangeNoResize(ReadOnlySpan collection) { CheckNoResizeCapacity(collection.Length); fixed (T* pCollection = collection) { MemCpy(UnsafeUtility.ReadArrayElementUnsafe(_array.GetUnsafePtr(), _count), pCollection, (uint)(collection.Length * sizeof(T))); } _count += collection.Length; } /// /// Adds a range of elements from a pointer to the collection without resizing the underlying storage. /// /// Points to the source data to be copied into the collection. /// Indicates the number of elements to be added from the source data. public void AddRangeNoResize(T* ptr, int count) { CheckNoResizeCapacity(count); MemCpy(UnsafeUtility.ReadArrayElementUnsafe(_array.GetUnsafePtr(), _count), ptr, (uint)(count * sizeof(T))); _count += count; } /// /// Removes a range of elements from the list starting at the specified index. /// /// The zero-based index at which to start removing elements. /// The number of elements to remove. public void RemoveRange(int start, int length) { CheckIndexCount(start, length); if (length <= 0) { return; } var copyFrom = Math.Min(start + length, _count); MemCpy(UnsafeUtility.ReadArrayElementUnsafe(_array.GetUnsafePtr(), start), UnsafeUtility.ReadArrayElementUnsafe(_array.GetUnsafePtr(), copyFrom), (uint)((_count - copyFrom) * sizeof(T)) ); _count -= length; } /// /// Removes the element at the specified index from the collection. /// /// The zero-based index of the element to remove. public void RemoveAt(int index) { RemoveRange(index, 1); } /// /// Removes a range of elements from the list starting at the specified index by swapping them with the last elements. /// /// The zero-based index at which to start removing elements. /// The number of elements to remove. public void RemoveRangeSwapBack(int start, int length) { CheckIndexCount(start, length); if (length <= 0) { return; } var copyFrom = Math.Min(_count - length, start + length); MemCpy(UnsafeUtility.ReadArrayElementUnsafe(_array.GetUnsafePtr(), start), UnsafeUtility.ReadArrayElementUnsafe(_array.GetUnsafePtr(), copyFrom), (uint)((_count - copyFrom) * sizeof(T)) ); _count -= length; } /// /// Removes the element at the specified index by swapping it with the last element and reducing the collection /// size. /// /// The zero-based index of the element to remove. Must be within the bounds of the collection. public void RemoveAtSwapBack(int index) { RemoveRangeSwapBack(index, 1); } public void Resize(int newSize, AllocationOption option = AllocationOption.None) { _array.Resize(newSize, option); if (_count > newSize) { _count = newSize; } } public void Clear() { _array.Clear(); _count = 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly void* GetUnsafePtr() { return _array.GetUnsafePtr(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Span AsSpan() { return _array.AsSpan(0, _count); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Span AsSpan(int start, int length) { CheckIndexCount(start, length); return _array.AsSpan(start, length); } public void Dispose() { _array.Dispose(); _count = 0; } }