A game about forced loneliness, made by TACStudios
at master 296 lines 9.8 kB view raw
1using System; 2using System.Diagnostics; 3using System.Runtime.CompilerServices; 4using Unity.Jobs; 5using System.Runtime.InteropServices; 6 7namespace Unity.Collections.LowLevel.Unsafe 8{ 9 /// <summary> 10 /// A fixed-size circular buffer. 11 /// </summary> 12 /// <typeparam name="T">The type of the elements.</typeparam> 13 [DebuggerDisplay("Length = {Length}, Capacity = {Capacity}, IsCreated = {IsCreated}, IsEmpty = {IsEmpty}")] 14 [DebuggerTypeProxy(typeof(UnsafeRingQueueDebugView<>))] 15 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })] 16 [StructLayout(LayoutKind.Sequential)] 17 public unsafe struct UnsafeRingQueue<T> 18 : INativeDisposable 19 where T : unmanaged 20 { 21 /// <summary> 22 /// The internal buffer where the content is stored. 23 /// </summary> 24 /// <value>The internal buffer where the content is stored.</value> 25 [NativeDisableUnsafePtrRestriction] 26 public T* Ptr; 27 28 /// <summary> 29 /// The allocator used to create the internal buffer. 30 /// </summary> 31 /// <value>The allocator used to create the internal buffer.</value> 32 public AllocatorManager.AllocatorHandle Allocator; 33 34 internal readonly int m_Capacity; 35 internal int m_Filled; 36 internal int m_Write; 37 internal int m_Read; 38 39 /// <summary> 40 /// Whether the queue is empty. 41 /// </summary> 42 /// <value>True if the queue is empty or the queue has not been constructed.</value> 43 public readonly bool IsEmpty 44 { 45 [MethodImpl(MethodImplOptions.AggressiveInlining)] 46 get => m_Filled == 0; 47 } 48 49 /// <summary> 50 /// The number of elements currently in this queue. 51 /// </summary> 52 /// <value>The number of elements currently in this queue.</value> 53 public readonly int Length 54 { 55 [MethodImpl(MethodImplOptions.AggressiveInlining)] 56 get => m_Filled; 57 } 58 59 /// <summary> 60 /// The number of elements that fit in the internal buffer. 61 /// </summary> 62 /// <value>The number of elements that fit in the internal buffer.</value> 63 public readonly int Capacity 64 { 65 [MethodImpl(MethodImplOptions.AggressiveInlining)] 66 get => m_Capacity; 67 } 68 69 /// <summary> 70 /// Initializes and returns an instance of UnsafeRingQueue which aliasing an existing buffer. 71 /// </summary> 72 /// <param name="ptr">An existing buffer to set as the internal buffer.</param> 73 /// <param name="capacity">The capacity.</param> 74 public UnsafeRingQueue(T* ptr, int capacity) 75 { 76 Ptr = ptr; 77 Allocator = AllocatorManager.None; 78 m_Capacity = capacity; 79 m_Filled = 0; 80 m_Write = 0; 81 m_Read = 0; 82 } 83 84 /// <summary> 85 /// Initializes and returns an instance of UnsafeRingQueue. 86 /// </summary> 87 /// <param name="capacity">The capacity.</param> 88 /// <param name="allocator">The allocator to use.</param> 89 /// <param name="options">Whether newly allocated bytes should be zeroed out.</param> 90 public UnsafeRingQueue(int capacity, AllocatorManager.AllocatorHandle allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory) 91 { 92 Allocator = allocator; 93 m_Capacity = capacity; 94 m_Filled = 0; 95 m_Write = 0; 96 m_Read = 0; 97 var sizeInBytes = capacity * UnsafeUtility.SizeOf<T>(); 98 Ptr = (T*)Memory.Unmanaged.Allocate(sizeInBytes, 16, allocator); 99 100 if (options == NativeArrayOptions.ClearMemory) 101 { 102 UnsafeUtility.MemClear(Ptr, sizeInBytes); 103 } 104 } 105 106 internal static UnsafeRingQueue<T>* Alloc(AllocatorManager.AllocatorHandle allocator) 107 { 108 UnsafeRingQueue<T>* data = (UnsafeRingQueue<T>*)Memory.Unmanaged.Allocate(sizeof(UnsafeRingQueue<T>), UnsafeUtility.AlignOf<UnsafeRingQueue<T>>(), allocator); 109 return data; 110 } 111 112 internal static void Free(UnsafeRingQueue<T>* data) 113 { 114 if (data == null) 115 { 116 throw new InvalidOperationException("UnsafeRingQueue has yet to be created or has been destroyed!"); 117 } 118 var allocator = data->Allocator; 119 data->Dispose(); 120 Memory.Unmanaged.Free(data, allocator); 121 } 122 123 /// <summary> 124 /// Whether this queue has been allocated (and not yet deallocated). 125 /// </summary> 126 /// <value>True if this queue has been allocated (and not yet deallocated).</value> 127 public readonly bool IsCreated 128 { 129 [MethodImpl(MethodImplOptions.AggressiveInlining)] 130 get => Ptr != null; 131 } 132 133 /// <summary> 134 /// Releases all resources (memory and safety handles). 135 /// </summary> 136 public void Dispose() 137 { 138 if (!IsCreated) 139 { 140 return; 141 } 142 143 if (CollectionHelper.ShouldDeallocate(Allocator)) 144 { 145 Memory.Unmanaged.Free(Ptr, Allocator); 146 Allocator = AllocatorManager.Invalid; 147 } 148 149 Ptr = null; 150 } 151 152 /// <summary> 153 /// Creates and schedules a job that will dispose this queue. 154 /// </summary> 155 /// <param name="inputDeps">The handle of a job which the new job will depend upon.</param> 156 /// <returns>The handle of a new job that will dispose this queue. The new job depends upon inputDeps.</returns> 157 public JobHandle Dispose(JobHandle inputDeps) 158 { 159 if (!IsCreated) 160 { 161 return inputDeps; 162 } 163 164 if (CollectionHelper.ShouldDeallocate(Allocator)) 165 { 166 var jobHandle = new UnsafeDisposeJob { Ptr = Ptr, Allocator = Allocator }.Schedule(inputDeps); 167 168 Ptr = null; 169 Allocator = AllocatorManager.Invalid; 170 171 return jobHandle; 172 } 173 174 Ptr = null; 175 176 return inputDeps; 177 } 178 179 [MethodImpl(MethodImplOptions.AggressiveInlining)] 180 bool TryEnqueueInternal(T value) 181 { 182 if (m_Filled == m_Capacity) 183 return false; 184 Ptr[m_Write] = value; 185 m_Write++; 186 if (m_Write == m_Capacity) 187 m_Write = 0; 188 m_Filled++; 189 return true; 190 } 191 192 /// <summary> 193 /// Adds an element at the front of the queue. 194 /// </summary> 195 /// <remarks>Does nothing if the queue is full.</remarks> 196 /// <param name="value">The value to be added.</param> 197 /// <returns>True if the value was added.</returns> 198 public bool TryEnqueue(T value) 199 { 200 return TryEnqueueInternal(value); 201 } 202 203 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 204 static void ThrowQueueFull() 205 { 206 throw new InvalidOperationException("Trying to enqueue into full queue."); 207 } 208 209 /// <summary> 210 /// Adds an element at the front of the queue. 211 /// </summary> 212 /// <param name="value">The value to be added.</param> 213 /// <exception cref="InvalidOperationException">Thrown if the queue was full.</exception> 214 public void Enqueue(T value) 215 { 216 if (!TryEnqueueInternal(value)) 217 { 218 ThrowQueueFull(); 219 } 220 } 221 222 [MethodImpl(MethodImplOptions.AggressiveInlining)] 223 bool TryDequeueInternal(out T item) 224 { 225 item = Ptr[m_Read]; 226 if (m_Filled == 0) 227 return false; 228 m_Read = m_Read + 1; 229 if (m_Read == m_Capacity) 230 m_Read = 0; 231 m_Filled--; 232 return true; 233 } 234 235 /// <summary> 236 /// Removes the element from the end of the queue. 237 /// </summary> 238 /// <remarks>Does nothing if the queue is empty.</remarks> 239 /// <param name="item">Outputs the element removed.</param> 240 /// <returns>True if an element was removed.</returns> 241 public bool TryDequeue(out T item) 242 { 243 return TryDequeueInternal(out item); 244 } 245 246 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 247 static void ThrowQueueEmpty() 248 { 249 throw new InvalidOperationException("Trying to dequeue from an empty queue"); 250 } 251 252 /// <summary> 253 /// Removes the element from the end of the queue. 254 /// </summary> 255 /// <exception cref="InvalidOperationException">Thrown if the queue was empty.</exception> 256 /// <returns>Returns the removed element.</returns> 257 public T Dequeue() 258 { 259 if (!TryDequeueInternal(out T item)) 260 { 261 ThrowQueueEmpty(); 262 } 263 264 return item; 265 } 266 } 267 268 internal sealed class UnsafeRingQueueDebugView<T> 269 where T : unmanaged 270 { 271 UnsafeRingQueue<T> Data; 272 273 public UnsafeRingQueueDebugView(UnsafeRingQueue<T> data) 274 { 275 Data = data; 276 } 277 278 public unsafe T[] Items 279 { 280 get 281 { 282 T[] result = new T[Data.Length]; 283 284 var read = Data.m_Read; 285 var capacity = Data.m_Capacity; 286 287 for (var i = 0; i < result.Length; ++i) 288 { 289 result[i] = Data.Ptr[(read + i) % capacity]; 290 } 291 292 return result; 293 } 294 } 295 } 296}