297 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			297 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | ||
|  | using System.Diagnostics; | ||
|  | using System.Runtime.CompilerServices; | ||
|  | using Unity.Jobs; | ||
|  | using System.Runtime.InteropServices; | ||
|  | 
 | ||
|  | namespace Unity.Collections.LowLevel.Unsafe | ||
|  | { | ||
|  |     /// <summary> | ||
|  |     /// A fixed-size circular buffer. | ||
|  |     /// </summary> | ||
|  |     /// <typeparam name="T">The type of the elements.</typeparam> | ||
|  |     [DebuggerDisplay("Length = {Length}, Capacity = {Capacity}, IsCreated = {IsCreated}, IsEmpty = {IsEmpty}")] | ||
|  |     [DebuggerTypeProxy(typeof(UnsafeRingQueueDebugView<>))] | ||
|  |     [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] | ||
|  |     [StructLayout(LayoutKind.Sequential)] | ||
|  |     public unsafe struct UnsafeRingQueue<T> | ||
|  |         : INativeDisposable | ||
|  |         where T : unmanaged | ||
|  |     { | ||
|  |         /// <summary> | ||
|  |         /// The internal buffer where the content is stored. | ||
|  |         /// </summary> | ||
|  |         /// <value>The internal buffer where the content is stored.</value> | ||
|  |         [NativeDisableUnsafePtrRestriction] | ||
|  |         public T* Ptr; | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// The allocator used to create the internal buffer. | ||
|  |         /// </summary> | ||
|  |         /// <value>The allocator used to create the internal buffer.</value> | ||
|  |         public AllocatorManager.AllocatorHandle Allocator; | ||
|  | 
 | ||
|  |         internal readonly int m_Capacity; | ||
|  |         internal int m_Filled; | ||
|  |         internal int m_Write; | ||
|  |         internal int m_Read; | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Whether the queue is empty. | ||
|  |         /// </summary> | ||
|  |         /// <value>True if the queue is empty or the queue has not been constructed.</value> | ||
|  |         public readonly bool IsEmpty | ||
|  |         { | ||
|  |             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
|  |             get => m_Filled == 0; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// The number of elements currently in this queue. | ||
|  |         /// </summary> | ||
|  |         /// <value>The number of elements currently in this queue.</value> | ||
|  |         public readonly int Length | ||
|  |         { | ||
|  |             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
|  |             get => m_Filled; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// The number of elements that fit in the internal buffer. | ||
|  |         /// </summary> | ||
|  |         /// <value>The number of elements that fit in the internal buffer.</value> | ||
|  |         public readonly int Capacity | ||
|  |         { | ||
|  |             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
|  |             get => m_Capacity; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Initializes and returns an instance of UnsafeRingQueue which aliasing an existing buffer. | ||
|  |         /// </summary> | ||
|  |         /// <param name="ptr">An existing buffer to set as the internal buffer.</param> | ||
|  |         /// <param name="capacity">The capacity.</param> | ||
|  |         public UnsafeRingQueue(T* ptr, int capacity) | ||
|  |         { | ||
|  |             Ptr = ptr; | ||
|  |             Allocator = AllocatorManager.None; | ||
|  |             m_Capacity = capacity; | ||
|  |             m_Filled = 0; | ||
|  |             m_Write = 0; | ||
|  |             m_Read = 0; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Initializes and returns an instance of UnsafeRingQueue. | ||
|  |         /// </summary> | ||
|  |         /// <param name="capacity">The capacity.</param> | ||
|  |         /// <param name="allocator">The allocator to use.</param> | ||
|  |         /// <param name="options">Whether newly allocated bytes should be zeroed out.</param> | ||
|  |         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<T>(); | ||
|  |             Ptr = (T*)Memory.Unmanaged.Allocate(sizeInBytes, 16, allocator); | ||
|  | 
 | ||
|  |             if (options == NativeArrayOptions.ClearMemory) | ||
|  |             { | ||
|  |                 UnsafeUtility.MemClear(Ptr, sizeInBytes); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static UnsafeRingQueue<T>* Alloc(AllocatorManager.AllocatorHandle allocator) | ||
|  |         { | ||
|  |             UnsafeRingQueue<T>* data = (UnsafeRingQueue<T>*)Memory.Unmanaged.Allocate(sizeof(UnsafeRingQueue<T>), UnsafeUtility.AlignOf<UnsafeRingQueue<T>>(), allocator); | ||
|  |             return data; | ||
|  |         } | ||
|  | 
 | ||
|  |         internal static void Free(UnsafeRingQueue<T>* 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); | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Whether this queue has been allocated (and not yet deallocated). | ||
|  |         /// </summary> | ||
|  |         /// <value>True if this queue has been allocated (and not yet deallocated).</value> | ||
|  |         public readonly bool IsCreated | ||
|  |         { | ||
|  |             [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
|  |             get => Ptr != null; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Releases all resources (memory and safety handles). | ||
|  |         /// </summary> | ||
|  |         public void Dispose() | ||
|  |         { | ||
|  |             if (!IsCreated) | ||
|  |             { | ||
|  |                 return; | ||
|  |             } | ||
|  | 
 | ||
|  |             if (CollectionHelper.ShouldDeallocate(Allocator)) | ||
|  |             { | ||
|  |                 Memory.Unmanaged.Free(Ptr, Allocator); | ||
|  |                 Allocator = AllocatorManager.Invalid; | ||
|  |             } | ||
|  | 
 | ||
|  |             Ptr = null; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Creates and schedules a job that will dispose this queue. | ||
|  |         /// </summary> | ||
|  |         /// <param name="inputDeps">The handle of a job which the new job will depend upon.</param> | ||
|  |         /// <returns>The handle of a new job that will dispose this queue. The new job depends upon inputDeps.</returns> | ||
|  |         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; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Adds an element at the front of the queue. | ||
|  |         /// </summary> | ||
|  |         /// <remarks>Does nothing if the queue is full.</remarks> | ||
|  |         /// <param name="value">The value to be added.</param> | ||
|  |         /// <returns>True if the value was added.</returns> | ||
|  |         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."); | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Adds an element at the front of the queue. | ||
|  |         /// </summary> | ||
|  |         /// <param name="value">The value to be added.</param> | ||
|  |         /// <exception cref="InvalidOperationException">Thrown if the queue was full.</exception> | ||
|  |         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; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Removes the element from the end of the queue. | ||
|  |         /// </summary> | ||
|  |         /// <remarks>Does nothing if the queue is empty.</remarks> | ||
|  |         /// <param name="item">Outputs the element removed.</param> | ||
|  |         /// <returns>True if an element was removed.</returns> | ||
|  |         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"); | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Removes the element from the end of the queue. | ||
|  |         /// </summary> | ||
|  |         /// <exception cref="InvalidOperationException">Thrown if the queue was empty.</exception> | ||
|  |         /// <returns>Returns the removed element.</returns> | ||
|  |         public T Dequeue() | ||
|  |         { | ||
|  |             if (!TryDequeueInternal(out T item)) | ||
|  |             { | ||
|  |                 ThrowQueueEmpty(); | ||
|  |             } | ||
|  | 
 | ||
|  |             return item; | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     internal sealed class UnsafeRingQueueDebugView<T> | ||
|  |         where T : unmanaged | ||
|  |     { | ||
|  |         UnsafeRingQueue<T> Data; | ||
|  | 
 | ||
|  |         public UnsafeRingQueueDebugView(UnsafeRingQueue<T> 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; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | } |