using System; using System.Runtime.InteropServices; using System.Threading; using Unity.Collections.LowLevel.Unsafe; using Unity.Burst; using Unity.Jobs; using Unity.Jobs.LowLevel.Unsafe; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Collections.Generic; using System.Collections; namespace Unity.Collections { /// /// An unmanaged queue. /// /// The type of the elements. [StructLayout(LayoutKind.Sequential)] [NativeContainer] [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] public unsafe struct NativeQueue : INativeDisposable where T : unmanaged { [NativeDisableUnsafePtrRestriction] UnsafeQueue* m_Queue; #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle m_Safety; static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate>(); #endif /// /// Initializes and returns an instance of NativeQueue. /// /// The allocator to use. public NativeQueue(AllocatorManager.AllocatorHandle allocator) { #if ENABLE_UNITY_COLLECTIONS_CHECKS m_Safety = CollectionHelper.CreateSafetyHandle(allocator); CollectionHelper.InitNativeContainer(m_Safety); CollectionHelper.SetStaticSafetyId>(ref m_Safety, ref s_staticSafetyId.Data); #endif m_Queue = UnsafeQueue.Alloc(allocator); *m_Queue = new UnsafeQueue(allocator); } /// /// Returns true if this queue is empty. /// /// True if this queue has no items or if the queue has not been constructed. public readonly bool IsEmpty() { if (IsCreated) { CheckRead(); return m_Queue->IsEmpty(); } return true; } /// /// Returns the current number of elements in this queue. /// /// Note that getting the count requires traversing the queue's internal linked list of blocks. /// Where possible, cache this value instead of reading the property repeatedly. /// The current number of elements in this queue. public readonly int Count { get { CheckRead(); return m_Queue->Count; } } /// /// Returns the element at the front of this queue without removing it. /// /// The element at the front of this queue. public T Peek() { CheckRead(); return m_Queue->Peek(); } /// /// Adds an element at the back of this queue. /// /// The value to be enqueued. public void Enqueue(T value) { CheckWrite(); m_Queue->Enqueue(value); } /// /// Removes and returns the element at the front of this queue. /// /// Thrown if this queue is empty. /// The element at the front of this queue. public T Dequeue() { CheckWrite(); return m_Queue->Dequeue(); } /// /// Removes and outputs the element at the front of this queue. /// /// Outputs the removed element. /// True if this queue was not empty. public bool TryDequeue(out T item) { CheckWrite(); return m_Queue->TryDequeue(out item); } /// /// Returns an array containing a copy of this queue's content. /// /// The allocator to use. /// An array containing a copy of this queue's content. The elements are ordered in the same order they were /// enqueued, *e.g.* the earliest enqueued element is copied to index 0 of the array. public NativeArray ToArray(AllocatorManager.AllocatorHandle allocator) { CheckRead(); return m_Queue->ToArray(allocator); } /// /// Removes all elements from this queue. /// public void Clear() { CheckWrite(); m_Queue->Clear(); } /// /// 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_Queue != null && m_Queue->IsCreated; } /// /// 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 UnsafeQueue.Free(m_Queue); m_Queue = null; } /// /// Creates and schedules a job that releases all resources (memory and safety handles) of this queue. /// /// The dependency for the new job. /// The handle of the new job. The job depends upon `inputDeps` and releases all resources (memory and safety handles) of this queue. 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 NativeQueueDisposeJob { Data = new NativeQueueDispose { m_QueueData = (UnsafeQueue*)m_Queue, m_Safety = m_Safety } }.Schedule(inputDeps); AtomicSafetyHandle.Release(m_Safety); #else var jobHandle = new NativeQueueDisposeJob { Data = new NativeQueueDispose { m_QueueData = (UnsafeQueue*)m_Queue } }.Schedule(inputDeps); #endif m_Queue = null; return jobHandle; } /// /// An enumerator over the values of a container. /// /// /// In an enumerator's initial state, is invalid. /// The first call advances the enumerator to the first value. /// [NativeContainer] [NativeContainerIsReadOnly] public struct Enumerator : IEnumerator { #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; #endif internal UnsafeQueue.Enumerator m_Enumerator; /// /// Does nothing. /// public void Dispose() { } /// /// Advances the enumerator to the next value. /// /// True if `Current` is valid to read after the call. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif return m_Enumerator.MoveNext(); } /// /// Resets the enumerator to its initial state. /// public void Reset() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif m_Enumerator.Reset(); } /// /// The current value. /// /// The current value. public T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => m_Enumerator.Current; } object IEnumerator.Current => Current; } /// /// Returns a readonly version of this NativeQueue instance. /// /// ReadOnly containers point to the same underlying data as the NativeQueue it is made from. /// ReadOnly instance for this. public ReadOnly AsReadOnly() { return new ReadOnly(ref this); } /// /// A read-only alias for the value of a NativeQueue. Does not have its own allocated storage. /// [NativeContainer] [NativeContainerIsReadOnly] public struct ReadOnly : IEnumerable { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle m_Safety; internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate(); #endif UnsafeQueue.ReadOnly m_ReadOnly; internal ReadOnly(ref NativeQueue data) { #if ENABLE_UNITY_COLLECTIONS_CHECKS m_Safety = data.m_Safety; CollectionHelper.SetStaticSafetyId(ref m_Safety, ref s_staticSafetyId.Data); #endif m_ReadOnly = new UnsafeQueue.ReadOnly(ref *data.m_Queue); } /// /// Whether this container been allocated (and not yet deallocated). /// /// True if this container has been allocated (and not yet deallocated). public readonly bool IsCreated { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => m_ReadOnly.IsCreated; } /// /// Returns true if this queue is empty. /// /// Note that getting the count requires traversing the queue's internal linked list of blocks. /// Where possible, cache this value instead of reading the property repeatedly. /// True if this queue has no items or if the queue has not been constructed. public readonly bool IsEmpty() { CheckRead(); return m_ReadOnly.IsEmpty(); } /// /// Returns the current number of elements in this queue. /// /// Note that getting the count requires traversing the queue's internal linked list of blocks. /// Where possible, cache this value instead of reading the property repeatedly. /// The current number of elements in this queue. public readonly int Count { get { CheckRead(); return m_ReadOnly.Count; } } /// /// The element at an index. /// /// An index. /// The element at the index. /// Thrown if the index is out of bounds. public readonly T this[int index] { get { CheckRead(); return m_ReadOnly[index]; } } /// /// Returns an enumerator over the items of this container. /// /// An enumerator over the items of this container. public readonly Enumerator GetEnumerator() { #if ENABLE_UNITY_COLLECTIONS_CHECKS var ash = m_Safety; AtomicSafetyHandle.CheckGetSecondaryDataPointerAndThrow(ash); AtomicSafetyHandle.UseSecondaryVersion(ref ash); #endif return new Enumerator { #if ENABLE_UNITY_COLLECTIONS_CHECKS m_Safety = ash, #endif m_Enumerator = m_ReadOnly.GetEnumerator(), }; } /// /// This method is not implemented. Use instead. /// /// Throws NotImplementedException. /// Method is not implemented. IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } /// /// This method is not implemented. Use instead. /// /// Throws NotImplementedException. /// Method is not implemented. IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly void CheckRead() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckReadAndThrow(m_Safety); #endif } } /// /// Returns a parallel writer for this queue. /// /// A parallel writer for this queue. public ParallelWriter AsParallelWriter() { ParallelWriter writer; #if ENABLE_UNITY_COLLECTIONS_CHECKS writer.m_Safety = m_Safety; CollectionHelper.SetStaticSafetyId(ref writer.m_Safety, ref ParallelWriter.s_staticSafetyId.Data); #endif writer.unsafeWriter = m_Queue->AsParallelWriter(); return writer; } /// /// A parallel writer for a NativeQueue. /// /// /// Use to create a parallel writer for a NativeQueue. /// [NativeContainer] [NativeContainerIsAtomicWriteOnly] [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] public unsafe struct ParallelWriter { internal UnsafeQueue.ParallelWriter unsafeWriter; #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate(); #endif /// /// Adds an element at the back of the queue. /// /// The value to be enqueued. public void Enqueue(T value) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); #endif unsafeWriter.Enqueue(value); } /// /// Adds an element at the back of the queue. /// /// The value to be enqueued. /// The thread index which must be set by a field from a job struct with the attribute. internal void Enqueue(T value, int threadIndexOverride) { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); #endif unsafeWriter.Enqueue(value, threadIndexOverride); } } [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)] void CheckWrite() { #if ENABLE_UNITY_COLLECTIONS_CHECKS AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); #endif } } [NativeContainer] [GenerateTestsForBurstCompatibility] internal unsafe struct NativeQueueDispose { [NativeDisableUnsafePtrRestriction] public UnsafeQueue* m_QueueData; #if ENABLE_UNITY_COLLECTIONS_CHECKS internal AtomicSafetyHandle m_Safety; #endif public void Dispose() { UnsafeQueue.Free(m_QueueData); } } [BurstCompile] struct NativeQueueDisposeJob : IJob { public NativeQueueDispose Data; public void Execute() { Data.Dispose(); } } }