A game about forced loneliness, made by TACStudios
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}