using System; using System.Diagnostics; using Unity.Jobs; using Unity.Mathematics; namespace Unity.Collections.LowLevel.Unsafe { [BurstCompatible] internal struct RingControl { internal RingControl(int capacity) { Capacity = capacity; Current = 0; Write = 0; Read = 0; } internal void Reset() { Current = 0; Write = 0; Read = 0; } internal int Distance(int from, int to) { var diff = to - from; return diff < 0 ? Capacity - math.abs(diff) : diff; } internal int Available() { return Distance(Read, Current); } internal int Reserve(int count) { var dist = Distance(Write, Read) - 1; var maxCount = dist < 0 ? Capacity - 1 : dist; var absCount = math.abs(count); var test = absCount - maxCount; count = test < 0 ? count : maxCount; Write = (Write + count) % Capacity; return count; } internal int Commit(int count) { var maxCount = Distance(Current, Write); var absCount = math.abs(count); var test = absCount - maxCount; count = test < 0 ? count : maxCount; Current = (Current + count) % Capacity; return count; } internal int Consume(int count) { var maxCount = Distance(Read, Current); var absCount = math.abs(count); var test = absCount - maxCount; count = test < 0 ? count : maxCount; Read = (Read + count) % Capacity; return count; } internal int Length => Distance(Read, Write); internal readonly int Capacity; internal int Current; internal int Write; internal int Read; } /// /// A fixed-size circular buffer. /// /// The type of the elements. [DebuggerDisplay("Length = {Length}, Capacity = {Capacity}, IsCreated = {IsCreated}, IsEmpty = {IsEmpty}")] [DebuggerTypeProxy(typeof(UnsafeRingQueueDebugView<>))] [BurstCompatible(GenericTypeArguments = new [] { typeof(int) })] 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 RingControl Control; /// /// Whether the queue is empty. /// /// True if the queue is empty or the queue has not been constructed. public bool IsEmpty => !IsCreated || Length == 0; /// /// The number of elements currently in this queue. /// /// The number of elements currently in this queue. public int Length => Control.Length; /// /// The number of elements that fit in the internal buffer. /// /// The number of elements that fit in the internal buffer. public int Capacity => Control.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; Control = new RingControl(capacity); } /// /// 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) { capacity += 1; Allocator = allocator; Control = new RingControl(capacity); var sizeInBytes = capacity * UnsafeUtility.SizeOf(); Ptr = (T*)Memory.Unmanaged.Allocate(sizeInBytes, 16, allocator); if (options == NativeArrayOptions.ClearMemory) { UnsafeUtility.MemClear(Ptr, sizeInBytes); } } /// /// Whether this queue has been allocated (and not yet deallocated). /// /// True if this queue 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; } /// /// 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. [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; } /// /// 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) { if (1 != Control.Reserve(1)) { return false; } Ptr[Control.Current] = value; Control.Commit(1); return true; } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 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 (!TryEnqueue(value)) { ThrowQueueFull(); } } /// /// 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) { item = Ptr[Control.Read]; return 1 == Control.Consume(1); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 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 (!TryDequeue(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.Control.Read; var capacity = Data.Control.Capacity; for (var i = 0; i < result.Length; ++i) { result[i] = Data.Ptr[(read + i) % capacity]; } return result; } } } }