A game about forced loneliness, made by TACStudios
1using System;
2using System.Runtime.InteropServices;
3using System.Threading;
4using Unity.Collections.LowLevel.Unsafe;
5using Unity.Burst;
6using Unity.Jobs;
7using Unity.Jobs.LowLevel.Unsafe;
8using System.Diagnostics;
9using System.Runtime.CompilerServices;
10using System.Collections.Generic;
11using System.Collections;
12
13namespace Unity.Collections
14{
15 /// <summary>
16 /// An unmanaged queue.
17 /// </summary>
18 /// <typeparam name="T">The type of the elements.</typeparam>
19 [StructLayout(LayoutKind.Sequential)]
20 [NativeContainer]
21 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
22 public unsafe struct NativeQueue<T>
23 : INativeDisposable
24 where T : unmanaged
25 {
26 [NativeDisableUnsafePtrRestriction]
27 UnsafeQueue<T>* m_Queue;
28
29#if ENABLE_UNITY_COLLECTIONS_CHECKS
30 AtomicSafetyHandle m_Safety;
31
32 static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<NativeQueue<T>>();
33#endif
34
35 /// <summary>
36 /// Initializes and returns an instance of NativeQueue.
37 /// </summary>
38 /// <param name="allocator">The allocator to use.</param>
39 public NativeQueue(AllocatorManager.AllocatorHandle allocator)
40 {
41#if ENABLE_UNITY_COLLECTIONS_CHECKS
42 m_Safety = CollectionHelper.CreateSafetyHandle(allocator);
43
44 CollectionHelper.InitNativeContainer<T>(m_Safety);
45 CollectionHelper.SetStaticSafetyId<NativeQueue<T>>(ref m_Safety, ref s_staticSafetyId.Data);
46#endif
47 m_Queue = UnsafeQueue<T>.Alloc(allocator);
48 *m_Queue = new UnsafeQueue<T>(allocator);
49
50 }
51
52 /// <summary>
53 /// Returns true if this queue is empty.
54 /// </summary>
55 /// <returns>True if this queue has no items or if the queue has not been constructed.</returns>
56 public readonly bool IsEmpty()
57 {
58 if (IsCreated)
59 {
60 CheckRead();
61 return m_Queue->IsEmpty();
62 }
63 return true;
64 }
65
66 /// <summary>
67 /// Returns the current number of elements in this queue.
68 /// </summary>
69 /// <remarks>Note that getting the count requires traversing the queue's internal linked list of blocks.
70 /// Where possible, cache this value instead of reading the property repeatedly.</remarks>
71 /// <returns>The current number of elements in this queue.</returns>
72 public readonly int Count
73 {
74 get
75 {
76 CheckRead();
77 return m_Queue->Count;
78 }
79 }
80
81 /// <summary>
82 /// Returns the element at the front of this queue without removing it.
83 /// </summary>
84 /// <returns>The element at the front of this queue.</returns>
85 public T Peek()
86 {
87 CheckRead();
88 return m_Queue->Peek();
89 }
90
91 /// <summary>
92 /// Adds an element at the back of this queue.
93 /// </summary>
94 /// <param name="value">The value to be enqueued.</param>
95 public void Enqueue(T value)
96 {
97 CheckWrite();
98 m_Queue->Enqueue(value);
99 }
100
101 /// <summary>
102 /// Removes and returns the element at the front of this queue.
103 /// </summary>
104 /// <exception cref="InvalidOperationException">Thrown if this queue is empty.</exception>
105 /// <returns>The element at the front of this queue.</returns>
106 public T Dequeue()
107 {
108 CheckWrite();
109 return m_Queue->Dequeue();
110 }
111
112 /// <summary>
113 /// Removes and outputs the element at the front of this queue.
114 /// </summary>
115 /// <param name="item">Outputs the removed element.</param>
116 /// <returns>True if this queue was not empty.</returns>
117 public bool TryDequeue(out T item)
118 {
119 CheckWrite();
120 return m_Queue->TryDequeue(out item);
121 }
122
123 /// <summary>
124 /// Returns an array containing a copy of this queue's content.
125 /// </summary>
126 /// <param name="allocator">The allocator to use.</param>
127 /// <returns>An array containing a copy of this queue's content. The elements are ordered in the same order they were
128 /// enqueued, *e.g.* the earliest enqueued element is copied to index 0 of the array.</returns>
129 public NativeArray<T> ToArray(AllocatorManager.AllocatorHandle allocator)
130 {
131 CheckRead();
132 return m_Queue->ToArray(allocator);
133 }
134
135 /// <summary>
136 /// Removes all elements from this queue.
137 /// </summary>
138 public void Clear()
139 {
140 CheckWrite();
141 m_Queue->Clear();
142 }
143
144 /// <summary>
145 /// Whether this queue has been allocated (and not yet deallocated).
146 /// </summary>
147 /// <value>True if this queue has been allocated (and not yet deallocated).</value>
148 public readonly bool IsCreated
149 {
150 [MethodImpl(MethodImplOptions.AggressiveInlining)]
151 get => m_Queue != null && m_Queue->IsCreated;
152 }
153
154 /// <summary>
155 /// Releases all resources (memory and safety handles).
156 /// </summary>
157 public void Dispose()
158 {
159#if ENABLE_UNITY_COLLECTIONS_CHECKS
160 if (!AtomicSafetyHandle.IsDefaultValue(m_Safety))
161 {
162 AtomicSafetyHandle.CheckExistsAndThrow(m_Safety);
163 }
164#endif
165 if (!IsCreated)
166 {
167 return;
168 }
169
170#if ENABLE_UNITY_COLLECTIONS_CHECKS
171 CollectionHelper.DisposeSafetyHandle(ref m_Safety);
172#endif
173 UnsafeQueue<T>.Free(m_Queue);
174 m_Queue = null;
175 }
176
177 /// <summary>
178 /// Creates and schedules a job that releases all resources (memory and safety handles) of this queue.
179 /// </summary>
180 /// <param name="inputDeps">The dependency for the new job.</param>
181 /// <returns>The handle of the new job. The job depends upon `inputDeps` and releases all resources (memory and safety handles) of this queue.</returns>
182 public JobHandle Dispose(JobHandle inputDeps)
183 {
184#if ENABLE_UNITY_COLLECTIONS_CHECKS
185 if (!AtomicSafetyHandle.IsDefaultValue(m_Safety))
186 {
187 AtomicSafetyHandle.CheckExistsAndThrow(m_Safety);
188 }
189#endif
190 if (!IsCreated)
191 {
192 return inputDeps;
193 }
194
195#if ENABLE_UNITY_COLLECTIONS_CHECKS
196 var jobHandle = new NativeQueueDisposeJob { Data = new NativeQueueDispose { m_QueueData = (UnsafeQueue<int>*)m_Queue, m_Safety = m_Safety } }.Schedule(inputDeps);
197 AtomicSafetyHandle.Release(m_Safety);
198#else
199 var jobHandle = new NativeQueueDisposeJob { Data = new NativeQueueDispose { m_QueueData = (UnsafeQueue<int>*)m_Queue } }.Schedule(inputDeps);
200#endif
201 m_Queue = null;
202
203 return jobHandle;
204
205 }
206
207 /// <summary>
208 /// An enumerator over the values of a container.
209 /// </summary>
210 /// <remarks>
211 /// In an enumerator's initial state, <see cref="Current"/> is invalid.
212 /// The first <see cref="MoveNext"/> call advances the enumerator to the first value.
213 /// </remarks>
214 [NativeContainer]
215 [NativeContainerIsReadOnly]
216 public struct Enumerator : IEnumerator<T>
217 {
218#if ENABLE_UNITY_COLLECTIONS_CHECKS
219 internal AtomicSafetyHandle m_Safety;
220#endif
221
222 internal UnsafeQueue<T>.Enumerator m_Enumerator;
223
224 /// <summary>
225 /// Does nothing.
226 /// </summary>
227 public void Dispose() { }
228
229 /// <summary>
230 /// Advances the enumerator to the next value.
231 /// </summary>
232 /// <returns>True if `Current` is valid to read after the call.</returns>
233 [MethodImpl(MethodImplOptions.AggressiveInlining)]
234 public bool MoveNext()
235 {
236#if ENABLE_UNITY_COLLECTIONS_CHECKS
237 AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
238#endif
239 return m_Enumerator.MoveNext();
240 }
241
242 /// <summary>
243 /// Resets the enumerator to its initial state.
244 /// </summary>
245 public void Reset()
246 {
247#if ENABLE_UNITY_COLLECTIONS_CHECKS
248 AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
249#endif
250 m_Enumerator.Reset();
251 }
252
253 /// <summary>
254 /// The current value.
255 /// </summary>
256 /// <value>The current value.</value>
257 public T Current
258 {
259 [MethodImpl(MethodImplOptions.AggressiveInlining)]
260 get => m_Enumerator.Current;
261 }
262
263 object IEnumerator.Current => Current;
264 }
265
266 /// <summary>
267 /// Returns a readonly version of this NativeQueue instance.
268 /// </summary>
269 /// <remarks>ReadOnly containers point to the same underlying data as the NativeQueue it is made from.</remarks>
270 /// <returns>ReadOnly instance for this.</returns>
271 public ReadOnly AsReadOnly()
272 {
273 return new ReadOnly(ref this);
274 }
275
276 /// <summary>
277 /// A read-only alias for the value of a NativeQueue. Does not have its own allocated storage.
278 /// </summary>
279 [NativeContainer]
280 [NativeContainerIsReadOnly]
281 public struct ReadOnly
282 : IEnumerable<T>
283 {
284#if ENABLE_UNITY_COLLECTIONS_CHECKS
285 AtomicSafetyHandle m_Safety;
286 internal static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<ReadOnly>();
287#endif
288 UnsafeQueue<T>.ReadOnly m_ReadOnly;
289
290 internal ReadOnly(ref NativeQueue<T> data)
291 {
292#if ENABLE_UNITY_COLLECTIONS_CHECKS
293 m_Safety = data.m_Safety;
294 CollectionHelper.SetStaticSafetyId<ReadOnly>(ref m_Safety, ref s_staticSafetyId.Data);
295#endif
296 m_ReadOnly = new UnsafeQueue<T>.ReadOnly(ref *data.m_Queue);
297 }
298
299 /// <summary>
300 /// Whether this container been allocated (and not yet deallocated).
301 /// </summary>
302 /// <value>True if this container has been allocated (and not yet deallocated).</value>
303 public readonly bool IsCreated
304 {
305 [MethodImpl(MethodImplOptions.AggressiveInlining)]
306 get => m_ReadOnly.IsCreated;
307 }
308
309 /// <summary>
310 /// Returns true if this queue is empty.
311 /// </summary>
312 /// <remarks>Note that getting the count requires traversing the queue's internal linked list of blocks.
313 /// Where possible, cache this value instead of reading the property repeatedly.</remarks>
314 /// <returns>True if this queue has no items or if the queue has not been constructed.</returns>
315 public readonly bool IsEmpty()
316 {
317 CheckRead();
318 return m_ReadOnly.IsEmpty();
319 }
320
321 /// <summary>
322 /// Returns the current number of elements in this queue.
323 /// </summary>
324 /// <remarks>Note that getting the count requires traversing the queue's internal linked list of blocks.
325 /// Where possible, cache this value instead of reading the property repeatedly.</remarks>
326 /// <returns>The current number of elements in this queue.</returns>
327 public readonly int Count
328 {
329 get
330 {
331 CheckRead();
332 return m_ReadOnly.Count;
333 }
334 }
335
336 /// <summary>
337 /// The element at an index.
338 /// </summary>
339 /// <param name="index">An index.</param>
340 /// <value>The element at the index.</value>
341 /// <exception cref="IndexOutOfRangeException">Thrown if the index is out of bounds.</exception>
342 public readonly T this[int index]
343 {
344 get
345 {
346 CheckRead();
347 return m_ReadOnly[index];
348 }
349 }
350
351 /// <summary>
352 /// Returns an enumerator over the items of this container.
353 /// </summary>
354 /// <returns>An enumerator over the items of this container.</returns>
355 public readonly Enumerator GetEnumerator()
356 {
357#if ENABLE_UNITY_COLLECTIONS_CHECKS
358 var ash = m_Safety;
359 AtomicSafetyHandle.CheckGetSecondaryDataPointerAndThrow(ash);
360 AtomicSafetyHandle.UseSecondaryVersion(ref ash);
361#endif
362
363 return new Enumerator
364 {
365#if ENABLE_UNITY_COLLECTIONS_CHECKS
366 m_Safety = ash,
367#endif
368 m_Enumerator = m_ReadOnly.GetEnumerator(),
369 };
370 }
371
372 /// <summary>
373 /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
374 /// </summary>
375 /// <returns>Throws NotImplementedException.</returns>
376 /// <exception cref="NotImplementedException">Method is not implemented.</exception>
377 IEnumerator<T> IEnumerable<T>.GetEnumerator()
378 {
379 throw new NotImplementedException();
380 }
381
382 /// <summary>
383 /// This method is not implemented. Use <see cref="GetEnumerator"/> instead.
384 /// </summary>
385 /// <returns>Throws NotImplementedException.</returns>
386 /// <exception cref="NotImplementedException">Method is not implemented.</exception>
387 IEnumerator IEnumerable.GetEnumerator()
388 {
389 throw new NotImplementedException();
390 }
391
392 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
393 [MethodImpl(MethodImplOptions.AggressiveInlining)]
394 readonly void CheckRead()
395 {
396#if ENABLE_UNITY_COLLECTIONS_CHECKS
397 AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
398#endif
399 }
400 }
401
402 /// <summary>
403 /// Returns a parallel writer for this queue.
404 /// </summary>
405 /// <returns>A parallel writer for this queue.</returns>
406 public ParallelWriter AsParallelWriter()
407 {
408 ParallelWriter writer;
409
410#if ENABLE_UNITY_COLLECTIONS_CHECKS
411 writer.m_Safety = m_Safety;
412 CollectionHelper.SetStaticSafetyId<ParallelWriter>(ref writer.m_Safety, ref ParallelWriter.s_staticSafetyId.Data);
413#endif
414 writer.unsafeWriter = m_Queue->AsParallelWriter();
415
416 return writer;
417 }
418
419 /// <summary>
420 /// A parallel writer for a NativeQueue.
421 /// </summary>
422 /// <remarks>
423 /// Use <see cref="AsParallelWriter"/> to create a parallel writer for a NativeQueue.
424 /// </remarks>
425 [NativeContainer]
426 [NativeContainerIsAtomicWriteOnly]
427 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
428 public unsafe struct ParallelWriter
429 {
430 internal UnsafeQueue<T>.ParallelWriter unsafeWriter;
431
432#if ENABLE_UNITY_COLLECTIONS_CHECKS
433 internal AtomicSafetyHandle m_Safety;
434 internal static readonly SharedStatic<int> s_staticSafetyId = SharedStatic<int>.GetOrCreate<ParallelWriter>();
435#endif
436
437 /// <summary>
438 /// Adds an element at the back of the queue.
439 /// </summary>
440 /// <param name="value">The value to be enqueued.</param>
441 public void Enqueue(T value)
442 {
443#if ENABLE_UNITY_COLLECTIONS_CHECKS
444 AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
445#endif
446 unsafeWriter.Enqueue(value);
447 }
448
449 /// <summary>
450 /// Adds an element at the back of the queue.
451 /// </summary>
452 /// <param name="value">The value to be enqueued.</param>
453 /// <param name="threadIndexOverride">The thread index which must be set by a field from a job struct with the <see cref="NativeSetThreadIndexAttribute"/> attribute.</param>
454 internal void Enqueue(T value, int threadIndexOverride)
455 {
456#if ENABLE_UNITY_COLLECTIONS_CHECKS
457 AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
458#endif
459 unsafeWriter.Enqueue(value, threadIndexOverride);
460 }
461 }
462
463 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
464 [MethodImpl(MethodImplOptions.AggressiveInlining)]
465 readonly void CheckRead()
466 {
467#if ENABLE_UNITY_COLLECTIONS_CHECKS
468 AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
469#endif
470 }
471
472 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
473 [MethodImpl(MethodImplOptions.AggressiveInlining)]
474 void CheckWrite()
475 {
476#if ENABLE_UNITY_COLLECTIONS_CHECKS
477 AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
478#endif
479 }
480 }
481
482 [NativeContainer]
483 [GenerateTestsForBurstCompatibility]
484 internal unsafe struct NativeQueueDispose
485 {
486 [NativeDisableUnsafePtrRestriction]
487 public UnsafeQueue<int>* m_QueueData;
488
489#if ENABLE_UNITY_COLLECTIONS_CHECKS
490 internal AtomicSafetyHandle m_Safety;
491#endif
492
493 public void Dispose()
494 {
495 UnsafeQueue<int>.Free(m_QueueData);
496 }
497 }
498
499 [BurstCompile]
500 struct NativeQueueDisposeJob : IJob
501 {
502 public NativeQueueDispose Data;
503
504 public void Execute()
505 {
506 Data.Dispose();
507 }
508 }
509}