using System; using System.Diagnostics; 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. /// [BurstCompatible] 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(initialCapacity); } /// /// 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 bool IsEmpty => 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 bool IsCreated => Ptr != null; /// /// Releases all resources (memory and safety handles). /// public void Dispose() { 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. [NotBurstCompatible /* This is not burst compatible because of IJob's use of a static IntPtr. Should switch to IJobBurstSchedulable in the future */] public JobHandle Dispose(JobHandle 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. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })] public void Add(T value) where T : struct { var structSize = UnsafeUtility.SizeOf(); SetCapacity(Length + structSize); UnsafeUtility.CopyStructureToPtr(ref value, Ptr + Length); 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. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })] public void AddArray(void* ptr, int length) where T : struct { 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. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })] public void Add(NativeArray value) where T : struct { Add(value.Length); Add(NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr(value), UnsafeUtility.SizeOf() * value.Length); } /// /// Appends the content of a string as UTF-16 to the end of this append buffer. /// /// Because some Unicode characters require two chars in UTF-16, each character is written as one or two chars (two or four bytes). /// /// The length of the string is itself appended before adding the first character. If the string is null, appends the int `-1` but no character data. /// /// A null terminator is not appended after the character data. /// The string to append. [NotBurstCompatible /* Deprecated */] [Obsolete("Please use `AddNBC` from `Unity.Collections.LowLevel.Unsafe.NotBurstCompatible` namespace instead. (RemovedAfter 2021-06-22)", false)] public void Add(string value) => NotBurstCompatible.Extensions.AddNBC(ref this, value); /// /// 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. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })] public T Pop() where T : struct { int structSize = UnsafeUtility.SizeOf(); long ptr = (long)Ptr; long size = Length; long addr = ptr + size - structSize; var data = UnsafeUtility.ReadArrayElement((void*)addr, 0); 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; } /// /// Copies this append buffer to a managed array of bytes. /// /// A managed array of bytes. [NotBurstCompatible /* Deprecated */] [Obsolete("Please use `ToBytesNBC` from `Unity.Collections.LowLevel.Unsafe.NotBurstCompatible` namespace instead. (RemovedAfter 2021-06-22)", false)] public byte[] ToBytes() => NotBurstCompatible.Extensions.ToBytesNBC(ref this); /// /// 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. /// [BurstCompatible] 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. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })] public void ReadNext(out T value) where T : struct { var structSize = UnsafeUtility.SizeOf(); CheckBounds(structSize); UnsafeUtility.CopyPtrToStructure(Ptr + Offset, out value); 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. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })] public T ReadNext() where T : struct { var structSize = UnsafeUtility.SizeOf(); CheckBounds(structSize); T value = UnsafeUtility.ReadArrayElement(Ptr + Offset, 0); 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. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })] public void ReadNext(out NativeArray value, AllocatorManager.AllocatorHandle allocator) where T : struct { var length = ReadNext(); value = CollectionHelper.CreateNativeArray(length, allocator); 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. [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })] public void* ReadNextArray(out int length) where T : struct { length = ReadNext(); return (length == 0) ? null : ReadNext(length * UnsafeUtility.SizeOf()); } #if !NET_DOTS /// /// Reads a UTF-16 string from the append buffer. /// /// Because some Unicode characters require two chars in UTF-16, each character is either one or two chars (two or four bytes). /// /// Assumes the string does not have a null terminator. /// /// Advances the reader's offset by the size of the string (in bytes). /// Outputs the string read from the append buffer. [NotBurstCompatible /* Deprecated */] [Obsolete("Please use `ReadNextNBC` from `Unity.Collections.LowLevel.Unsafe.NotBurstCompatible` namespace instead. (RemovedAfter 2021-06-22)", false)] public void ReadNext(out string value) => NotBurstCompatible.Extensions.ReadNextNBC(ref this, out value); #endif [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 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")] 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}"); } } } }