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 UnsafeArrayDebugView where T : unmanaged { private readonly UnsafeArray _array; public UnsafeArrayDebugView(UnsafeArray array) { _array = array; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public T[] Items { get { var count = _array.Count; var result = new T[count]; for (int i = 0; i < count; i++) { result[i] = _array[i]; } return result; } } } /// /// A structure for managing an array of unmanaged types with unsafe memory operations. /// /// Represents a type that can be stored in an unmanaged memory context. [DebuggerTypeProxy(typeof(UnsafeArrayDebugView<>))] public unsafe struct UnsafeArray : IUnsafeCollection where T : unmanaged { public struct Enumerator : IEnumerator { private readonly UnsafeArray* _collection; private int _index; public readonly ref T Current => ref _collection->_buffer[_index]; readonly T IEnumerator.Current => Current; readonly object IEnumerator.Current => Current; public Enumerator(UnsafeArray* collection) { _collection = collection; _index = -1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { _index++; return _index < _collection->_count; } public void Reset() { _index = -1; } public void Dispose() { } } private T* _buffer; private int _count; private MemoryHandle _memoryHandle; private AllocationHandle _allocationHandle; public readonly int Count => _count; public readonly int Length => _count; public readonly ref T this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { CheckIndexBounds(index); return ref UnsafeUtility.ReadArrayElementRef(_buffer, index); } } public readonly ref T this[uint index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { CheckIndexBounds((int)index); return ref UnsafeUtility.ReadArrayElementRef(_buffer, index); } } public readonly bool IsCreated => _buffer != null && _allocationHandle.pAllocator != null && _memoryHandle.IsValid; public Enumerator GetEnumerator() => new((UnsafeArray*)UnsafeUtility.AddressOf(ref this)); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// /// Invalid constructor, use or instead. /// public UnsafeArray() : this(0, Allocator.Invalid) { } /// /// Initializes a new instance of UnsafeArray with a specified number of elements and an allocation handle. /// /// Specifies the number of elements to allocate in the array, 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. /// Thrown when the specified number of elements is less than or equal to zero. public UnsafeArray(int count, AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None) { if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count), "Count can not be less than zero."); } MemoryHandle memHandle; var buff = handle.Alloc(handle.pAllocator, (nuint)(count * sizeof(T)), AlignOf(), allocationOption, &memHandle); _buffer = (T*)buff; _memoryHandle = memHandle; _allocationHandle = handle; _count = count; } /// /// Initializes a new instance of UnsafeArray with a specified number of elements and an allocation type. /// /// Specifies the number of elements to allocate in the array, 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. /// Thrown when the specified number of elements is less than or equal to zero. public UnsafeArray(int count, Allocator allocator, AllocationOption allocationOption = AllocationOption.None) : this(count, AllocationManager.GetAllocationHandle(allocator), allocationOption) { } /// /// Initializes an UnsafeArray with a pointer to a buffer and a count of elements. This does not copy the data. /// /// A pointer to the memory location that holds the elements of the array. /// The total size of the data. /// /// When using this constructor, the user is responsible for managing the memory pointed to by the buffer. /// Disposing of the UnsafeArray does not free the memory and only release the reference. The memory should be freed manually when no longer needed. /// Use constructor and if you are not sure what you are doing. /// public UnsafeArray(T* buffer, int count) { _buffer = buffer; _count = count; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private readonly void ThrowIfNotCreated() { if (!IsCreated) { throw new InvalidOperationException("The UnsafeArray is not created."); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] [Conditional("ENABLE_COLLECTION_CHECKS")] private readonly void CheckIndexBounds(int index) { ThrowIfNotCreated(); if (index >= _count) { throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range."); } } /// /// Returns a read-only view of the current collection. /// /// A that provides a read-only view of the elements in the current collection. [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly ReadOnlyUnsafeCollection AsReadOnly() { return new ReadOnlyUnsafeCollection(_buffer, _count); } /// public void Resize(int newSize, AllocationOption option = AllocationOption.None) { ThrowIfNotCreated(); if (newSize == Count) { return; } MemoryHandle memHandle = _memoryHandle; var elemSize = SizeOf(); _buffer = (T*)_allocationHandle.Realloc(_allocationHandle.pAllocator, _buffer, (nuint)Count * elemSize, (nuint)newSize * elemSize, AlignOf(), option, &memHandle); _memoryHandle = memHandle; _count = newSize; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly void Clear() { ThrowIfNotCreated(); MemClear(_buffer, (nuint)(Count * sizeof(T))); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly void* GetUnsafePtr() { ThrowIfNotCreated(); return _buffer; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Span AsSpan() { ThrowIfNotCreated(); return new Span(_buffer, _count); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Span AsSpan(int start, int length) { ThrowIfNotCreated(); return new Span(_buffer + start, length); } /// /// Reinterprets the underlying buffer as an array of a different unmanaged type without copying the data. /// /// The unmanaged type to reinterpret the buffer as. /// An UnsafeArray that views the same memory as the original array, but as elements of type U. /// Thrown if the total size of the buffer in bytes is not a multiple of the size of type U. public readonly UnsafeArray Reinterpret() where U : unmanaged { ThrowIfNotCreated(); var totalSize = (nuint)(Count * sizeof(T)); if (totalSize % (nuint)sizeof(U) != 0) { throw new InvalidOperationException("Cannot reinterpret array: size mismatch."); } var newCount = (int)(totalSize / (nuint)sizeof(U)); return new UnsafeArray((U*)_buffer, newCount); } /// public void Dispose() { if (!IsCreated) { return; } if (_allocationHandle.pAllocator != null) { _allocationHandle.Free(_allocationHandle.pAllocator, _buffer, _memoryHandle); } _buffer = null; _count = 0; } }