using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections.Contracts; using Misaki.HighPerformance.LowLevel.Contracts; using Misaki.HighPerformance.LowLevel.Utilities; using System.Collections; using System.Runtime.CompilerServices; namespace Misaki.HighPerformance.LowLevel.Collections; /// /// 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. 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 writer for an UnsafeList. /// /// /// Use to create a parallel writer for a list. /// public unsafe struct ParallelWriter { /// /// The UnsafeList to write to. /// public 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]; } public Enumerator GetEnumerator() => new ((UnsafeList*)UnsafeUtility.AddressOf(ref this)); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// /// Provides a parallel writer for the current list, enabling thread-safe additions to the list. /// /// A instance that can be used to add items to the list in a thread-safe manner. public ParallelWriter AsParallelWriter() => new((UnsafeList*)UnsafeUtility.AddressOf(ref this)); /// /// Converts the current list to an UnsafeArray representation. /// /// A new instance. [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly UnsafeArray AsUnsafeArray() => new((T*)_array.GetUnsafePtr(), _count); /// /// 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, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None) { _array = new UnsafeArray(capacity, ref 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, ref 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 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(Capacity + (int)(Capacity * 0.5f)); } 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 . /// The number of elements to add from the span. Must be non-negative and less than or /// equal to the length of . public void AddRange(Span values, int count) { var newSize = _count + count; if (newSize > Capacity) { Resize(Capacity + count); } fixed (T* ptr = values) { 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; } } public void Clear() { _array.Clear(); _count = 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly void* GetUnsafePtr() { return _array.GetUnsafePtr(); } public void Dispose() { _array.Dispose(); _count = 0; } }