using System; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Unity.Burst; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; namespace Unity.Collections { /// /// A fixed-size circular buffer. For single-threaded uses only. /// /// /// This container can't be used in parallel jobs, just on single-thread (for example: main thread, or single IJob). /// /// The type of the elements. [StructLayout(LayoutKind.Sequential)] [NativeContainer] [DebuggerDisplay("Length = {Length}, Capacity = {Capacity}, IsCreated = {IsCreated}, IsEmpty = {IsEmpty}")] [DebuggerTypeProxy(typeof(NativeRingQueueDebugView<>))] [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(int) })] public unsafe struct NativeRingQueue : INativeDisposable where T : unmanaged { #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate>(); #endif [NativeDisableUnsafePtrRestriction] internal UnsafeRingQueue* m_RingQueue; /// /// 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 => m_RingQueue != null && m_RingQueue->IsCreated; } /// /// 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_RingQueue == null || m_RingQueue->Length == 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 { CheckRead(); return CollectionHelper.AssumePositive(m_RingQueue->Length); } } /// /// 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 { CheckRead(); return CollectionHelper.AssumePositive(m_RingQueue->Capacity); } } /// /// Initializes and returns an instance of NativeRingQueue. /// /// The capacity. /// The allocator to use. /// Whether newly allocated bytes should be zeroed out. public NativeRingQueue(int capacity, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory) { CollectionHelper.CheckAllocator(allocator); #if ENABLE_UNITY_COLLECTIONS_CHECKS m_Safety = CollectionHelper.CreateSafetyHandle(allocator); CollectionHelper.SetStaticSafetyId>(ref m_Safety, ref s_staticSafetyId.Data); #endif m_RingQueue = UnsafeRingQueue.Alloc(allocator); *m_RingQueue = new UnsafeRingQueue(capacity, allocator, options); } /// /// Releases all resources (memory and safety handles). /// public void Dispose() { #if ENABLE_UNITY_COLLECTIONS_CHECKS if (!AtomicSafetyHandle.IsDefaultValue(m_Safety)) { AtomicSafetyHandle.CheckExistsAndThrow(m_Safety); } #endif if (!IsCreated) { return; } #if ENABLE_UNITY_COLLECTIONS_CHECKS CollectionHelper.DisposeSafetyHandle(ref m_Safety); #endif UnsafeRingQueue.Free(m_RingQueue); m_RingQueue = 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 ENABLE_UNITY_COLLECTIONS_CHECKS if (!AtomicSafetyHandle.IsDefaultValue(m_Safety)) { AtomicSafetyHandle.CheckExistsAndThrow(m_Safety); } #endif if (!IsCreated) { return inputDeps; } #if ENABLE_UNITY_COLLECTIONS_CHECKS var jobHandle = new NativeRingQueueDisposeJob { Data = new NativeRingQueueDispose { m_QueueData = (UnsafeRingQueue*)m_RingQueue, m_Safety = m_Safety } }.Schedule(inputDeps); AtomicSafetyHandle.Release(m_Safety); #else var jobHandle = new NativeRingQueueDisposeJob { Data = new NativeRingQueueDispose { m_QueueData = (UnsafeRingQueue*)m_RingQueue } }.Schedule(inputDeps); #endif m_RingQueue = null; return jobHandle; } /// /// 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) { CheckWrite(); return m_RingQueue->TryEnqueue(value); } /// /// 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) { CheckWrite(); m_RingQueue->Enqueue(value); } /// /// 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) { CheckRead(); return m_RingQueue->TryDequeue(out item); } /// /// Removes the element from the end of the queue. /// /// Thrown if the queue was empty. /// Returns the removed element. public T Dequeue() { CheckRead(); return m_RingQueue->Dequeue(); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly void CheckRead() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly void CheckWrite() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); #endif } } internal unsafe sealed class NativeRingQueueDebugView where T : unmanaged { UnsafeRingQueue* Data; public NativeRingQueueDebugView(NativeRingQueue data) { Data = data.m_RingQueue; } public 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; } } } [NativeContainer] [GenerateTestsForBurstCompatibility] internal unsafe struct NativeRingQueueDispose { [NativeDisableUnsafePtrRestriction] public UnsafeRingQueue* m_QueueData; #if ENABLE_UNITY_COLLECTIONS_CHECKS public AtomicSafetyHandle m_Safety; #endif public void Dispose() { UnsafeRingQueue.Free(m_QueueData); } } [BurstCompile] internal unsafe struct NativeRingQueueDisposeJob : IJob { public NativeRingQueueDispose Data; public void Execute() { Data.Dispose(); } } }