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