A game about forced loneliness, made by TACStudios
1using System;
2using System.Diagnostics;
3using System.Runtime.CompilerServices;
4using Unity.Jobs;
5using Unity.Mathematics;
6
7namespace Unity.Collections.LowLevel.Unsafe
8{
9 /// <summary>
10 /// An unmanaged, untyped, heterogeneous buffer.
11 /// </summary>
12 /// <remarks>
13 /// The values written to an individual append buffer can be of different types.
14 /// </remarks>
15 [GenerateTestsForBurstCompatibility]
16 public unsafe struct UnsafeAppendBuffer
17 : INativeDisposable
18 {
19 /// <summary>
20 /// The internal buffer where the content is stored.
21 /// </summary>
22 /// <value>The internal buffer where the content is stored.</value>
23 [NativeDisableUnsafePtrRestriction]
24 public byte* Ptr;
25
26 /// <summary>
27 /// The size in bytes of the currently-used portion of the internal buffer.
28 /// </summary>
29 /// <value>The size in bytes of the currently-used portion of the internal buffer.</value>
30 public int Length;
31
32 /// <summary>
33 /// The size in bytes of the internal buffer.
34 /// </summary>
35 /// <value>The size in bytes of the internal buffer.</value>
36 public int Capacity;
37
38 /// <summary>
39 /// The allocator used to create the internal buffer.
40 /// </summary>
41 /// <value>The allocator used to create the internal buffer.</value>
42 public AllocatorManager.AllocatorHandle Allocator;
43
44 /// <summary>
45 /// The byte alignment used when allocating the internal buffer.
46 /// </summary>
47 /// <value>The byte alignment used when allocating the internal buffer. Is always a non-zero power of 2.</value>
48 public readonly int Alignment;
49
50 /// <summary>
51 /// Initializes and returns an instance of UnsafeAppendBuffer.
52 /// </summary>
53 /// <param name="initialCapacity">The initial allocation size in bytes of the internal buffer.</param>
54 /// <param name="alignment">The byte alignment of the allocation. Must be a non-zero power of 2.</param>
55 /// <param name="allocator">The allocator to use.</param>
56 public UnsafeAppendBuffer(int initialCapacity, int alignment, AllocatorManager.AllocatorHandle allocator)
57 {
58 CheckAlignment(alignment);
59
60 Alignment = alignment;
61 Allocator = allocator;
62 Ptr = null;
63 Length = 0;
64 Capacity = 0;
65
66 SetCapacity(math.max(initialCapacity, 1));
67 }
68
69 /// <summary>
70 /// Initializes and returns an instance of UnsafeAppendBuffer that aliases an existing buffer.
71 /// </summary>
72 /// <remarks>The capacity will be set to `length`, and <see cref="Length"/> will be set to 0.
73 /// </remarks>
74 /// <param name="ptr">The buffer to alias.</param>
75 /// <param name="length">The length in bytes of the buffer.</param>
76 public UnsafeAppendBuffer(void* ptr, int length)
77 {
78 Alignment = 0;
79 Allocator = AllocatorManager.None;
80 Ptr = (byte*)ptr;
81 Length = 0;
82 Capacity = length;
83 }
84
85 /// <summary>
86 /// Whether the append buffer is empty.
87 /// </summary>
88 /// <value>True if the append buffer is empty.</value>
89 public readonly bool IsEmpty
90 {
91 [MethodImpl(MethodImplOptions.AggressiveInlining)]
92 get => Length == 0;
93 }
94
95 /// <summary>
96 /// Whether this append buffer has been allocated (and not yet deallocated).
97 /// </summary>
98 /// <value>True if this append buffer has been allocated (and not yet deallocated).</value>
99 public readonly bool IsCreated
100 {
101 [MethodImpl(MethodImplOptions.AggressiveInlining)]
102 get => Ptr != null;
103 }
104
105 /// <summary>
106 /// Releases all resources (memory and safety handles).
107 /// </summary>
108 public void Dispose()
109 {
110 if (!IsCreated)
111 {
112 return;
113 }
114
115 if (CollectionHelper.ShouldDeallocate(Allocator))
116 {
117 Memory.Unmanaged.Free(Ptr, Allocator);
118 Allocator = AllocatorManager.Invalid;
119 }
120
121 Ptr = null;
122 Length = 0;
123 Capacity = 0;
124 }
125
126 /// <summary>
127 /// Creates and schedules a job that will dispose this append buffer.
128 /// </summary>
129 /// <param name="inputDeps">The handle of a job which the new job will depend upon.</param>
130 /// <returns>The handle of a new job that will dispose this append buffer. The new job depends upon inputDeps.</returns>
131 public JobHandle Dispose(JobHandle inputDeps)
132 {
133 if (!IsCreated)
134 {
135 return inputDeps;
136 }
137
138 if (CollectionHelper.ShouldDeallocate(Allocator))
139 {
140 var jobHandle = new UnsafeDisposeJob { Ptr = Ptr, Allocator = Allocator }.Schedule(inputDeps);
141
142 Ptr = null;
143 Allocator = AllocatorManager.Invalid;
144
145 return jobHandle;
146 }
147
148 Ptr = null;
149
150 return inputDeps;
151 }
152
153 /// <summary>
154 /// Sets the length to 0.
155 /// </summary>
156 /// <remarks>Does not change the capacity.</remarks>
157 public void Reset()
158 {
159 Length = 0;
160 }
161
162 /// <summary>
163 /// Sets the size in bytes of the internal buffer.
164 /// </summary>
165 /// <remarks>Does nothing if the new capacity is less than or equal to the current capacity.</remarks>
166 /// <param name="capacity">A new capacity in bytes.</param>
167 public void SetCapacity(int capacity)
168 {
169 if (capacity <= Capacity)
170 {
171 return;
172 }
173
174 capacity = math.max(64, math.ceilpow2(capacity));
175
176 var newPtr = (byte*)Memory.Unmanaged.Allocate(capacity, Alignment, Allocator);
177 if (Ptr != null)
178 {
179 UnsafeUtility.MemCpy(newPtr, Ptr, Length);
180 Memory.Unmanaged.Free(Ptr, Allocator);
181 }
182
183 Ptr = newPtr;
184 Capacity = capacity;
185 }
186
187 /// <summary>
188 /// Sets the length in bytes.
189 /// </summary>
190 /// <remarks>If the new length exceeds the capacity, capacity is expanded to the new length.</remarks>
191 /// <param name="length">The new length.</param>
192 public void ResizeUninitialized(int length)
193 {
194 SetCapacity(length);
195 Length = length;
196 }
197
198 /// <summary>
199 /// Appends an element to the end of this append buffer.
200 /// </summary>
201 /// <typeparam name="T">The type of the element.</typeparam>
202 /// <param name="value">The value to be appended.</param>
203 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
204 public void Add<T>(T value) where T : unmanaged
205 {
206 var structSize = UnsafeUtility.SizeOf<T>();
207 SetCapacity(Length + structSize);
208
209 void* addr = Ptr + Length;
210 if (CollectionHelper.IsAligned((ulong)addr, UnsafeUtility.AlignOf<T>()))
211 UnsafeUtility.CopyStructureToPtr(ref value, addr);
212 else
213 UnsafeUtility.MemCpy(addr, &value, structSize);
214
215 Length += structSize;
216 }
217
218 /// <summary>
219 /// Appends an element to the end of this append buffer.
220 /// </summary>
221 /// <remarks>The value itself is stored, not the pointer.</remarks>
222 /// <param name="ptr">A pointer to the value to be appended.</param>
223 /// <param name="structSize">The size in bytes of the value to be appended.</param>
224 public void Add(void* ptr, int structSize)
225 {
226 SetCapacity(Length + structSize);
227 UnsafeUtility.MemCpy(Ptr + Length, ptr, structSize);
228 Length += structSize;
229 }
230
231 /// <summary>
232 /// Appends the elements of a buffer to the end of this append buffer.
233 /// </summary>
234 /// <typeparam name="T">The type of the buffer's elements.</typeparam>
235 /// <remarks>The values themselves are stored, not their pointers.</remarks>
236 /// <param name="ptr">A pointer to the buffer whose values will be appended.</param>
237 /// <param name="length">The number of elements to append.</param>
238 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
239 public void AddArray<T>(void* ptr, int length) where T : unmanaged
240 {
241 Add(length);
242
243 if (length != 0)
244 Add(ptr, length * UnsafeUtility.SizeOf<T>());
245 }
246
247 /// <summary>
248 /// Appends all elements of an array to the end of this append buffer.
249 /// </summary>
250 /// <typeparam name="T">The type of the elements.</typeparam>
251 /// <param name="value">The array whose elements will all be appended.</param>
252 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
253 public void Add<T>(NativeArray<T> value) where T : unmanaged
254 {
255 Add(value.Length);
256 Add(NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr(value), UnsafeUtility.SizeOf<T>() * value.Length);
257 }
258
259 /// <summary>
260 /// Removes and returns the last element of this append buffer.
261 /// </summary>
262 /// <typeparam name="T">The type of the element to remove.</typeparam>
263 /// <remarks>It is your responsibility to specify the correct type. Do not pop when the append buffer is empty.</remarks>
264 /// <returns>The element removed from the end of this append buffer.</returns>
265 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
266 public T Pop<T>() where T : unmanaged
267 {
268 int structSize = UnsafeUtility.SizeOf<T>();
269 long ptr = (long)Ptr;
270 long size = Length;
271 long addr = ptr + size - structSize;
272
273 T data;
274 if (CollectionHelper.IsAligned((ulong)addr, UnsafeUtility.AlignOf<T>()))
275 data = UnsafeUtility.ReadArrayElement<T>((void*)addr, 0);
276 else
277 UnsafeUtility.MemCpy(&data, (void*)addr, structSize);
278
279 Length -= structSize;
280 return data;
281 }
282
283 /// <summary>
284 /// Removes and copies the last element of this append buffer.
285 /// </summary>
286 /// <remarks>It is your responsibility to specify the correct `structSize`. Do not pop when the append buffer is empty.</remarks>
287 /// <param name="ptr">The location to which the removed element will be copied.</param>
288 /// <param name="structSize">The size of the element to remove and copy.</param>
289 public void Pop(void* ptr, int structSize)
290 {
291 long data = (long)Ptr;
292 long size = Length;
293 long addr = data + size - structSize;
294
295 UnsafeUtility.MemCpy(ptr, (void*)addr, structSize);
296 Length -= structSize;
297 }
298
299 /// <summary>
300 /// Returns a reader for this append buffer.
301 /// </summary>
302 /// <returns>A reader for the append buffer.</returns>
303 public Reader AsReader()
304 {
305 return new Reader(ref this);
306 }
307
308 /// <summary>
309 /// A reader for UnsafeAppendBuffer.
310 /// </summary>
311 [GenerateTestsForBurstCompatibility]
312 public unsafe struct Reader
313 {
314 /// <summary>
315 /// The internal buffer where the content is stored.
316 /// </summary>
317 /// <value>The internal buffer where the content is stored.</value>
318 public readonly byte* Ptr;
319
320 /// <summary>
321 /// The length in bytes of the append buffer's content.
322 /// </summary>
323 /// <value>The length in bytes of the append buffer's content.</value>
324 public readonly int Size;
325
326 /// <summary>
327 /// The location of the next read (expressed as a byte offset from the start).
328 /// </summary>
329 /// <value>The location of the next read (expressed as a byte offset from the start).</value>
330 public int Offset;
331
332 /// <summary>
333 /// Initializes and returns an instance of UnsafeAppendBuffer.Reader.
334 /// </summary>
335 /// <param name="buffer">A reference to the append buffer to read.</param>
336 public Reader(ref UnsafeAppendBuffer buffer)
337 {
338 Ptr = buffer.Ptr;
339 Size = buffer.Length;
340 Offset = 0;
341 }
342
343 /// <summary>
344 /// Initializes and returns an instance of UnsafeAppendBuffer.Reader that reads from a buffer.
345 /// </summary>
346 /// <remarks>The buffer will be read *as if* it is an UnsafeAppendBuffer whether it was originally allocated as one or not.</remarks>
347 /// <param name="ptr">The buffer to read as an UnsafeAppendBuffer.</param>
348 /// <param name="length">The length in bytes of the </param>
349 public Reader(void* ptr, int length)
350 {
351 Ptr = (byte*)ptr;
352 Size = length;
353 Offset = 0;
354 }
355
356 /// <summary>
357 /// Whether the offset has advanced past the last of the append buffer's content.
358 /// </summary>
359 /// <value>Whether the offset has advanced past the last of the append buffer's content.</value>
360 public bool EndOfBuffer => Offset == Size;
361
362 /// <summary>
363 /// Reads an element from the append buffer.
364 /// </summary>
365 /// <remarks>Advances the reader's offset by the size of T.</remarks>
366 /// <typeparam name="T">The type of element to read.</typeparam>
367 /// <param name="value">Output for the element read.</param>
368 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
369 public void ReadNext<T>(out T value) where T : unmanaged
370 {
371 var structSize = UnsafeUtility.SizeOf<T>();
372 CheckBounds(structSize);
373
374 void* addr = Ptr + Offset;
375 if (CollectionHelper.IsAligned((ulong)addr, UnsafeUtility.AlignOf<T>()))
376 UnsafeUtility.CopyPtrToStructure<T>(addr, out value);
377 else
378 fixed (void* pValue = &value) UnsafeUtility.MemCpy(pValue, addr, structSize);
379
380 Offset += structSize;
381 }
382
383 /// <summary>
384 /// Reads an element from the append buffer.
385 /// </summary>
386 /// <remarks>Advances the reader's offset by the size of T.</remarks>
387 /// <typeparam name="T">The type of element to read.</typeparam>
388 /// <returns>The element read.</returns>
389 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
390 public T ReadNext<T>() where T : unmanaged
391 {
392 var structSize = UnsafeUtility.SizeOf<T>();
393 CheckBounds(structSize);
394
395 T value;
396 void* addr = Ptr + Offset;
397 if (CollectionHelper.IsAligned((ulong)addr, UnsafeUtility.AlignOf<T>()))
398 value = UnsafeUtility.ReadArrayElement<T>(addr, 0);
399 else
400 UnsafeUtility.MemCpy(&value, addr, structSize);
401
402 Offset += structSize;
403 return value;
404 }
405
406 /// <summary>
407 /// Reads an element from the append buffer.
408 /// </summary>
409 /// <remarks>Advances the reader's offset by `structSize`.</remarks>
410 /// <param name="structSize">The size of the element to read.</param>
411 /// <returns>A pointer to where the read element resides in the append buffer.</returns>
412 public void* ReadNext(int structSize)
413 {
414 CheckBounds(structSize);
415
416 var value = (void*)((IntPtr)Ptr + Offset);
417 Offset += structSize;
418 return value;
419 }
420
421 /// <summary>
422 /// Reads an element from the append buffer.
423 /// </summary>
424 /// <remarks>Advances the reader's offset by the size of T.</remarks>
425 /// <typeparam name="T">The type of element to read.</typeparam>
426 /// <param name="value">Outputs a new array with length of 1. The read element is copied to the single index of this array.</param>
427 /// <param name="allocator">The allocator to use.</param>
428 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
429 public void ReadNext<T>(out NativeArray<T> value, AllocatorManager.AllocatorHandle allocator) where T : unmanaged
430 {
431 var length = ReadNext<int>();
432 value = CollectionHelper.CreateNativeArray<T>(length, allocator, NativeArrayOptions.UninitializedMemory);
433 var size = length * UnsafeUtility.SizeOf<T>();
434 if (size > 0)
435 {
436 var ptr = ReadNext(size);
437 UnsafeUtility.MemCpy(NativeArrayUnsafeUtility.GetUnsafePtr(value), ptr, size);
438 }
439 }
440
441 /// <summary>
442 /// Reads an array from the append buffer.
443 /// </summary>
444 /// <remarks>An array stored in the append buffer starts with an int specifying the number of values in the array.
445 /// The first element of an array immediately follows this int.
446 ///
447 /// Advances the reader's offset by the size of the array (plus an int).</remarks>
448 /// <typeparam name="T">The type of elements in the array to read.</typeparam>
449 /// <param name="length">Output which is the number of elements in the read array.</param>
450 /// <returns>A pointer to where the first element of the read array resides in the append buffer.</returns>
451 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
452 public void* ReadNextArray<T>(out int length) where T : unmanaged
453 {
454 length = ReadNext<int>();
455 return (length == 0) ? null : ReadNext(length * UnsafeUtility.SizeOf<T>());
456 }
457
458 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
459 void CheckBounds(int structSize)
460 {
461 if (Offset + structSize > Size)
462 {
463 throw new ArgumentException($"Requested value outside bounds of UnsafeAppendOnlyBuffer. Remaining bytes: {Size - Offset} Requested: {structSize}");
464 }
465 }
466 }
467
468 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
469 static void CheckAlignment(int alignment)
470 {
471 var zeroAlignment = alignment == 0;
472 var powTwoAlignment = ((alignment - 1) & alignment) == 0;
473 var validAlignment = (!zeroAlignment) && powTwoAlignment;
474
475 if (!validAlignment)
476 {
477 throw new ArgumentException($"Specified alignment must be non-zero positive power of two. Requested: {alignment}");
478 }
479 }
480 }
481}