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}");
}
}
}
}