A game about forced loneliness, made by TACStudios
at master 481 lines 20 kB view raw
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}