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 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) { } [MethodImpl(MethodImplOptions.AggressiveInlining)] [Conditional("MHP_ENABLE_SAFETY_CHECKS")] private readonly void CheckNoResizeCapacity(int count) { CheckNoResizeCapacity(count, Count); } [MethodImpl(MethodImplOptions.AggressiveInlining)] [Conditional("MHP_ENABLE_SAFETY_CHECKS")] 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."); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Enumerator GetEnumerator() { return new((UnsafeList*)UnsafeUtility.AddressOf(ref this)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] 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. [MethodImpl(MethodImplOptions.AggressiveInlining)] 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. [MethodImpl(MethodImplOptions.AggressiveInlining)] 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. 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 a range of elements from a pointer to the collection. /// /// 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 AddRange(T* ptr, int count) { var newSize = _count + count; if (newSize > Capacity) { Resize(Capacity + count); } MemCpy(UnsafeUtility.ReadArrayElementUnsafe(_array.GetUnsafePtr(), _count), ptr, (uint)(count * sizeof(T))); _count += count; } /// /// 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; } } /// /// Sets the count of the collection to a new value without modifying the underlying storage. /// /// /// This method will not initialize new elements, so it should be used with caution. The new count must be between 0 and the current capacity of the collection. /// /// The new count value to set for the collection. /// Thrown when the new count is outside the valid range. public void UnsafeSetCount(int newCount) { if (newCount < 0 || newCount > Capacity) { throw new ArgumentOutOfRangeException(nameof(newCount), $"Value for newCount {newCount} must be between 0 and Capacity {Capacity}."); } _count = newCount; } public void 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); } /// /// Copies elements from a source UnsafeCollection to a destination Span, ensuring both have the same size. /// /// Represents the target span where elements are copied to. public readonly void CopyTo(Span destination) { var size = Math.Min(destination.Length, Count); fixed (T* pDest = destination) { MemCpy(pDest, _array.GetUnsafePtr(), (uint)(size * sizeof(T))); } } /// /// Copies a range of elements from a source collection to a destination span, ensuring both are adequately sized. /// /// The span where the elements will be copied to. /// The starting index in the source collection for the copy operation. /// The starting index in the destination span where the elements will be placed. /// The number of elements to copy from the source to the destination. /// Thrown when the specified range exceeds the bounds of the source collection or destination span. public readonly void CopyTo(Span destination, int sourceIndex, int destinationIndex, int length) { if (sourceIndex + length > _count || destinationIndex + length > destination.Length) { throw new ArgumentOutOfRangeException(nameof(length), "Source collection or destination span is too small for the specified range."); } fixed (T* pDest = destination) { MemCpy(pDest + destinationIndex, (byte*)_array.GetUnsafePtr() + sourceIndex * sizeof(T), (nuint)(length * sizeof(T))); } } /// /// Copies elements from a source span to a destination unsafe collection, ensuring both have the same size. /// /// Represents the span containing the elements to be copied to the unsafe collection. public void CopyFrom(ReadOnlySpan source) { if (_count < source.Length) { Resize(source.Length); } fixed (T* pSrc = source) { MemCpy(_array.GetUnsafePtr(), pSrc, (nuint)(source.Length * sizeof(T))); } } /// /// Copies a specified range of elements from a source span to a destination collection. /// /// The span containing the elements to be copied. /// The starting index in the source span from which to begin copying. /// The starting index in the destination collection where the elements will be placed. /// The number of elements to copy from the source span to the destination collection. /// Thrown when the specified range exceeds the bounds of the source span or destination collection. public void CopyFrom(ReadOnlySpan source, int sourceIndex, int destinationIndex, int length) { if (sourceIndex + length > source.Length) { throw new ArgumentOutOfRangeException(nameof(length), "Source span or destination collection is too small for the specified range."); } if (destinationIndex + length > _count) { Resize(destinationIndex + length); } fixed (T* pSrc = source) { MemCpy((byte*)_array.GetUnsafePtr() + destinationIndex * sizeof(T), pSrc + sourceIndex, (nuint)(length * sizeof(T))); } } /// /// Creates a new containing the elements. /// /// A containing all elements. public readonly List ToList() { var list = new List(_count); var span = new Span(_array.GetUnsafePtr(), _count); list.AddRange(span); return list; } public void Dispose() { _array.Dispose(); _count = 0; } public static implicit operator ReadOnlyUnsafeCollection(UnsafeList list) { return list.AsReadOnly(); } public static implicit operator Span(UnsafeList list) { return list.AsSpan(); } }