using System; using System.Diagnostics; using System.Runtime.CompilerServices; using Unity.Jobs; using Unity.Mathematics; namespace Unity.Collections.LowLevel.Unsafe { /// /// An unmanaged, untyped, heterogeneous buffer. /// /// /// The values written to an individual append buffer can be of different types. /// [GenerateTestsForBurstCompatibility] public unsafe struct UnsafeAppendBuffer : INativeDisposable { /// /// The internal buffer where the content is stored. /// /// The internal buffer where the content is stored. [NativeDisableUnsafePtrRestriction] public byte* Ptr; /// /// The size in bytes of the currently-used portion of the internal buffer. /// /// The size in bytes of the currently-used portion of the internal buffer. public int Length; /// /// The size in bytes of the internal buffer. /// /// The size in bytes of the internal buffer. public int Capacity; /// /// The allocator used to create the internal buffer. /// /// The allocator used to create the internal buffer. public AllocatorManager.AllocatorHandle Allocator; /// /// The byte alignment used when allocating the internal buffer. /// /// The byte alignment used when allocating the internal buffer. Is always a non-zero power of 2. public readonly int Alignment; /// /// Initializes and returns an instance of UnsafeAppendBuffer. /// /// The initial allocation size in bytes of the internal buffer. /// The byte alignment of the allocation. Must be a non-zero power of 2. /// The allocator to use. public UnsafeAppendBuffer(int initialCapacity, int alignment, AllocatorManager.AllocatorHandle allocator) { CheckAlignment(alignment); Alignment = alignment; Allocator = allocator; Ptr = null; Length = 0; Capacity = 0; SetCapacity(math.max(initialCapacity, 1)); } /// /// Initializes and returns an instance of UnsafeAppendBuffer that aliases an existing buffer. /// /// The capacity will be set to `length`, and will be set to 0. /// /// The buffer to alias. /// The length in bytes of the buffer. public UnsafeAppendBuffer(void* ptr, int length) { Alignment = 0; Allocator = AllocatorManager.None; Ptr = (byte*)ptr; Length = 0; Capacity = length; } /// /// Whether the append buffer is empty. /// /// True if the append buffer is empty. public readonly bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Length == 0; } /// /// Whether this append buffer has been allocated (and not yet deallocated). /// /// True if this append buffer has been allocated (and not yet deallocated). public readonly bool IsCreated { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Ptr != null; } /// /// Releases all resources (memory and safety handles). /// public void Dispose() { if (!IsCreated) { return; } if (CollectionHelper.ShouldDeallocate(Allocator)) { Memory.Unmanaged.Free(Ptr, Allocator); Allocator = AllocatorManager.Invalid; } Ptr = null; Length = 0; Capacity = 0; } /// /// Creates and schedules a job that will dispose this append buffer. /// /// The handle of a job which the new job will depend upon. /// The handle of a new job that will dispose this append buffer. The new job depends upon inputDeps. public JobHandle Dispose(JobHandle inputDeps) { if (!IsCreated) { return inputDeps; } if (CollectionHelper.ShouldDeallocate(Allocator)) { var jobHandle = new UnsafeDisposeJob { Ptr = Ptr, Allocator = Allocator }.Schedule(inputDeps); Ptr = null; Allocator = AllocatorManager.Invalid; return jobHandle; } Ptr = null; return inputDeps; } /// /// Sets the length to 0. /// /// Does not change the capacity. public void Reset() { Length = 0; } /// /// Sets the size in bytes of the internal buffer. /// /// Does nothing if the new capacity is less than or equal to the current capacity. /// A new capacity in bytes. public void SetCapacity(int capacity) { if (capacity <= Capacity) { return; } capacity = math.max(64, math.ceilpow2(capacity)); var newPtr = (byte*)Memory.Unmanaged.Allocate(capacity, Alignment, Allocator); if (Ptr != null) { UnsafeUtility.MemCpy(newPtr, Ptr, Length); Memory.Unmanaged.Free(Ptr, Allocator); } Ptr = newPtr; Capacity = capacity; } /// /// Sets the length in bytes. /// /// If the new length exceeds the capacity, capacity is expanded to the new length. /// The new length. public void ResizeUninitialized(int length) { SetCapacity(length); Length = length; } /// /// Appends an element to the end of this append buffer. /// /// The type of the element. /// The value to be appended. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] public void Add(T value) where T : unmanaged { var structSize = UnsafeUtility.SizeOf(); SetCapacity(Length + structSize); void* addr = Ptr + Length; if (CollectionHelper.IsAligned((ulong)addr, UnsafeUtility.AlignOf())) UnsafeUtility.CopyStructureToPtr(ref value, addr); else UnsafeUtility.MemCpy(addr, &value, structSize); Length += structSize; } /// /// Appends an element to the end of this append buffer. /// /// The value itself is stored, not the pointer. /// A pointer to the value to be appended. /// The size in bytes of the value to be appended. public void Add(void* ptr, int structSize) { SetCapacity(Length + structSize); UnsafeUtility.MemCpy(Ptr + Length, ptr, structSize); Length += structSize; } /// /// Appends the elements of a buffer to the end of this append buffer. /// /// The type of the buffer's elements. /// The values themselves are stored, not their pointers. /// A pointer to the buffer whose values will be appended. /// The number of elements to append. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] public void AddArray(void* ptr, int length) where T : unmanaged { Add(length); if (length != 0) Add(ptr, length * UnsafeUtility.SizeOf()); } /// /// Appends all elements of an array to the end of this append buffer. /// /// The type of the elements. /// The array whose elements will all be appended. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] public void Add(NativeArray value) where T : unmanaged { Add(value.Length); Add(NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr(value), UnsafeUtility.SizeOf() * value.Length); } /// /// Removes and returns the last element of this append buffer. /// /// The type of the element to remove. /// It is your responsibility to specify the correct type. Do not pop when the append buffer is empty. /// The element removed from the end of this append buffer. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] public T Pop() where T : unmanaged { int structSize = UnsafeUtility.SizeOf(); long ptr = (long)Ptr; long size = Length; long addr = ptr + size - structSize; T data; if (CollectionHelper.IsAligned((ulong)addr, UnsafeUtility.AlignOf())) data = UnsafeUtility.ReadArrayElement((void*)addr, 0); else UnsafeUtility.MemCpy(&data, (void*)addr, structSize); Length -= structSize; return data; } /// /// Removes and copies the last element of this append buffer. /// /// It is your responsibility to specify the correct `structSize`. Do not pop when the append buffer is empty. /// The location to which the removed element will be copied. /// The size of the element to remove and copy. public void Pop(void* ptr, int structSize) { long data = (long)Ptr; long size = Length; long addr = data + size - structSize; UnsafeUtility.MemCpy(ptr, (void*)addr, structSize); Length -= structSize; } /// /// Returns a reader for this append buffer. /// /// A reader for the append buffer. public Reader AsReader() { return new Reader(ref this); } /// /// A reader for UnsafeAppendBuffer. /// [GenerateTestsForBurstCompatibility] public unsafe struct Reader { /// /// The internal buffer where the content is stored. /// /// The internal buffer where the content is stored. public readonly byte* Ptr; /// /// The length in bytes of the append buffer's content. /// /// The length in bytes of the append buffer's content. public readonly int Size; /// /// The location of the next read (expressed as a byte offset from the start). /// /// The location of the next read (expressed as a byte offset from the start). public int Offset; /// /// Initializes and returns an instance of UnsafeAppendBuffer.Reader. /// /// A reference to the append buffer to read. public Reader(ref UnsafeAppendBuffer buffer) { Ptr = buffer.Ptr; Size = buffer.Length; Offset = 0; } /// /// Initializes and returns an instance of UnsafeAppendBuffer.Reader that reads from a buffer. /// /// The buffer will be read *as if* it is an UnsafeAppendBuffer whether it was originally allocated as one or not. /// The buffer to read as an UnsafeAppendBuffer. /// The length in bytes of the public Reader(void* ptr, int length) { Ptr = (byte*)ptr; Size = length; Offset = 0; } /// /// Whether the offset has advanced past the last of the append buffer's content. /// /// Whether the offset has advanced past the last of the append buffer's content. public bool EndOfBuffer => Offset == Size; /// /// Reads an element from the append buffer. /// /// Advances the reader's offset by the size of T. /// The type of element to read. /// Output for the element read. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] public void ReadNext(out T value) where T : unmanaged { var structSize = UnsafeUtility.SizeOf(); CheckBounds(structSize); void* addr = Ptr + Offset; if (CollectionHelper.IsAligned((ulong)addr, UnsafeUtility.AlignOf())) UnsafeUtility.CopyPtrToStructure(addr, out value); else fixed (void* pValue = &value) UnsafeUtility.MemCpy(pValue, addr, structSize); Offset += structSize; } /// /// Reads an element from the append buffer. /// /// Advances the reader's offset by the size of T. /// The type of element to read. /// The element read. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] public T ReadNext() where T : unmanaged { var structSize = UnsafeUtility.SizeOf(); CheckBounds(structSize); T value; void* addr = Ptr + Offset; if (CollectionHelper.IsAligned((ulong)addr, UnsafeUtility.AlignOf())) value = UnsafeUtility.ReadArrayElement(addr, 0); else UnsafeUtility.MemCpy(&value, addr, structSize); Offset += structSize; return value; } /// /// Reads an element from the append buffer. /// /// Advances the reader's offset by `structSize`. /// The size of the element to read. /// A pointer to where the read element resides in the append buffer. public void* ReadNext(int structSize) { CheckBounds(structSize); var value = (void*)((IntPtr)Ptr + Offset); Offset += structSize; return value; } /// /// Reads an element from the append buffer. /// /// Advances the reader's offset by the size of T. /// The type of element to read. /// Outputs a new array with length of 1. The read element is copied to the single index of this array. /// The allocator to use. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] public void ReadNext(out NativeArray value, AllocatorManager.AllocatorHandle allocator) where T : unmanaged { var length = ReadNext(); value = CollectionHelper.CreateNativeArray(length, allocator, NativeArrayOptions.UninitializedMemory); var size = length * UnsafeUtility.SizeOf(); if (size > 0) { var ptr = ReadNext(size); UnsafeUtility.MemCpy(NativeArrayUnsafeUtility.GetUnsafePtr(value), ptr, size); } } /// /// Reads an array from the append buffer. /// /// An array stored in the append buffer starts with an int specifying the number of values in the array. /// The first element of an array immediately follows this int. /// /// Advances the reader's offset by the size of the array (plus an int). /// The type of elements in the array to read. /// Output which is the number of elements in the read array. /// A pointer to where the first element of the read array resides in the append buffer. [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] public void* ReadNextArray(out int length) where T : unmanaged { length = ReadNext(); return (length == 0) ? null : ReadNext(length * UnsafeUtility.SizeOf()); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] void CheckBounds(int structSize) { if (Offset + structSize > Size) { throw new ArgumentException($"Requested value outside bounds of UnsafeAppendOnlyBuffer. Remaining bytes: {Size - Offset} Requested: {structSize}"); } } } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] static void CheckAlignment(int alignment) { var zeroAlignment = alignment == 0; var powTwoAlignment = ((alignment - 1) & alignment) == 0; var validAlignment = (!zeroAlignment) && powTwoAlignment; if (!validAlignment) { throw new ArgumentException($"Specified alignment must be non-zero positive power of two. Requested: {alignment}"); } } } }