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 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 ref struct Enumerator { private ref UnsafeList _collection; private int _index; public readonly ref T Current => ref _collection._array[_index]; public Enumerator(ref UnsafeList collection) { _collection = ref collection; _index = -1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { _index++; return _index < _collection._count; } public void Reset() { _index = -1; } } /// /// A Parallel reader for an UnsafeList. /// /// /// Use to create a parallel reader for a list. /// The list must live and the address of the list remain stable 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(ref *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 and the address of the list remain stable 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(scoped in 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) { MemoryUtility.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(1, AllocationHandle.Persistent) { } /// /// 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. [Obsolete("Use AllocationHandle instead.")] 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)] [UnscopedRef] public Enumerator GetEnumerator() { return new Enumerator(ref this); } /// /// 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(scoped in 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(scoped in 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) { MemoryUtility.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); } MemoryUtility.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) { MemoryUtility.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); MemoryUtility.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); MemoryUtility.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 numToCopy = Math.Min(length, _count - (start + length)); var copyFrom = _count - numToCopy; if (numToCopy > 0) { MemoryUtility.MemCpy(UnsafeUtility.ReadArrayElementUnsafe(_array.GetUnsafePtr(), start), UnsafeUtility.ReadArrayElementUnsafe(_array.GetUnsafePtr(), copyFrom), (uint)((_count - copyFrom) * sizeof(T))); } _count -= length; } 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) { MemoryUtility.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) { MemoryUtility.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) { MemoryUtility.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) { MemoryUtility.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(); } }