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