using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Unity.Jobs;
using System.Runtime.InteropServices;
namespace Unity.Collections.LowLevel.Unsafe
{
///
/// A fixed-size circular buffer.
///
/// The type of the elements.
[DebuggerDisplay("Length = {Length}, Capacity = {Capacity}, IsCreated = {IsCreated}, IsEmpty = {IsEmpty}")]
[DebuggerTypeProxy(typeof(UnsafeRingQueueDebugView<>))]
[GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
[StructLayout(LayoutKind.Sequential)]
public unsafe struct UnsafeRingQueue
: INativeDisposable
where T : unmanaged
{
///
/// The internal buffer where the content is stored.
///
/// The internal buffer where the content is stored.
[NativeDisableUnsafePtrRestriction]
public T* Ptr;
///
/// The allocator used to create the internal buffer.
///
/// The allocator used to create the internal buffer.
public AllocatorManager.AllocatorHandle Allocator;
internal readonly int m_Capacity;
internal int m_Filled;
internal int m_Write;
internal int m_Read;
///
/// Whether the queue is empty.
///
/// True if the queue is empty or the queue has not been constructed.
public readonly bool IsEmpty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => m_Filled == 0;
}
///
/// The number of elements currently in this queue.
///
/// The number of elements currently in this queue.
public readonly int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => m_Filled;
}
///
/// The number of elements that fit in the internal buffer.
///
/// The number of elements that fit in the internal buffer.
public readonly int Capacity
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => m_Capacity;
}
///
/// Initializes and returns an instance of UnsafeRingQueue which aliasing an existing buffer.
///
/// An existing buffer to set as the internal buffer.
/// The capacity.
public UnsafeRingQueue(T* ptr, int capacity)
{
Ptr = ptr;
Allocator = AllocatorManager.None;
m_Capacity = capacity;
m_Filled = 0;
m_Write = 0;
m_Read = 0;
}
///
/// Initializes and returns an instance of UnsafeRingQueue.
///
/// The capacity.
/// The allocator to use.
/// Whether newly allocated bytes should be zeroed out.
public UnsafeRingQueue(int capacity, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory)
{
Allocator = allocator;
m_Capacity = capacity;
m_Filled = 0;
m_Write = 0;
m_Read = 0;
var sizeInBytes = capacity * UnsafeUtility.SizeOf();
Ptr = (T*)Memory.Unmanaged.Allocate(sizeInBytes, 16, allocator);
if (options == NativeArrayOptions.ClearMemory)
{
UnsafeUtility.MemClear(Ptr, sizeInBytes);
}
}
internal static UnsafeRingQueue* Alloc(AllocatorManager.AllocatorHandle allocator)
{
UnsafeRingQueue* data = (UnsafeRingQueue*)Memory.Unmanaged.Allocate(sizeof(UnsafeRingQueue), UnsafeUtility.AlignOf>(), allocator);
return data;
}
internal static void Free(UnsafeRingQueue* data)
{
if (data == null)
{
throw new InvalidOperationException("UnsafeRingQueue has yet to be created or has been destroyed!");
}
var allocator = data->Allocator;
data->Dispose();
Memory.Unmanaged.Free(data, allocator);
}
///
/// Whether this queue has been allocated (and not yet deallocated).
///
/// True if this queue 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;
}
///
/// Creates and schedules a job that will dispose this queue.
///
/// The handle of a job which the new job will depend upon.
/// The handle of a new job that will dispose this queue. 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;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
bool TryEnqueueInternal(T value)
{
if (m_Filled == m_Capacity)
return false;
Ptr[m_Write] = value;
m_Write++;
if (m_Write == m_Capacity)
m_Write = 0;
m_Filled++;
return true;
}
///
/// Adds an element at the front of the queue.
///
/// Does nothing if the queue is full.
/// The value to be added.
/// True if the value was added.
public bool TryEnqueue(T value)
{
return TryEnqueueInternal(value);
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
static void ThrowQueueFull()
{
throw new InvalidOperationException("Trying to enqueue into full queue.");
}
///
/// Adds an element at the front of the queue.
///
/// The value to be added.
/// Thrown if the queue was full.
public void Enqueue(T value)
{
if (!TryEnqueueInternal(value))
{
ThrowQueueFull();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
bool TryDequeueInternal(out T item)
{
item = Ptr[m_Read];
if (m_Filled == 0)
return false;
m_Read = m_Read + 1;
if (m_Read == m_Capacity)
m_Read = 0;
m_Filled--;
return true;
}
///
/// Removes the element from the end of the queue.
///
/// Does nothing if the queue is empty.
/// Outputs the element removed.
/// True if an element was removed.
public bool TryDequeue(out T item)
{
return TryDequeueInternal(out item);
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
static void ThrowQueueEmpty()
{
throw new InvalidOperationException("Trying to dequeue from an empty queue");
}
///
/// Removes the element from the end of the queue.
///
/// Thrown if the queue was empty.
/// Returns the removed element.
public T Dequeue()
{
if (!TryDequeueInternal(out T item))
{
ThrowQueueEmpty();
}
return item;
}
}
internal sealed class UnsafeRingQueueDebugView
where T : unmanaged
{
UnsafeRingQueue Data;
public UnsafeRingQueueDebugView(UnsafeRingQueue data)
{
Data = data;
}
public unsafe T[] Items
{
get
{
T[] result = new T[Data.Length];
var read = Data.m_Read;
var capacity = Data.m_Capacity;
for (var i = 0; i < result.Length; ++i)
{
result[i] = Data.Ptr[(read + i) % capacity];
}
return result;
}
}
}
}