A game about forced loneliness, made by TACStudios
at master 1764 lines 80 kB view raw
1#pragma warning disable 0649 2 3using System; 4using System.Diagnostics; 5using System.Runtime.InteropServices; 6using AOT; 7using Unity.Burst; 8using Unity.Collections.LowLevel.Unsafe; 9using Unity.Mathematics; 10using UnityEngine.Assertions; 11using Unity.Jobs.LowLevel.Unsafe; 12using System.Runtime.CompilerServices; 13using System.Threading; 14 15namespace Unity.Collections 16{ 17 [GenerateTestsForBurstCompatibility] 18 struct Spinner 19 { 20 int m_Lock; 21 22 /// <summary> 23 /// Continually spin until the lock can be acquired. 24 /// </summary> 25 [MethodImpl(MethodImplOptions.AggressiveInlining)] 26 internal void Acquire() 27 { 28 for (; ; ) 29 { 30 // Optimistically assume the lock is free on the first try. 31 if (Interlocked.CompareExchange(ref m_Lock, 1, 0) == 0) 32 { 33 return; 34 } 35 36 // Wait for lock to be released without generate cache misses. 37 while (Volatile.Read(ref m_Lock) == 1) 38 { 39 continue; 40 } 41 42 // Future improvement: the 'continue` instruction above could be swapped for a 'pause' intrinsic 43 // instruction when the CPU supports it, to further reduce contention by reducing load-store unit 44 // utilization. However, this would need to be optional because if you don't use hyper-threading 45 // and you don't care about power efficiency, using the 'pause' instruction will slow down lock 46 // acquisition in the contended scenario. 47 } 48 } 49 50 /// <summary> 51 /// Try to acquire the lock and immediately return without spinning. 52 /// </summary> 53 /// <returns><see langword="true"/> if the lock was acquired, <see langword="false"/> otherwise.</returns> 54 [MethodImpl(MethodImplOptions.AggressiveInlining)] 55 internal bool TryAcquire() 56 { 57 // First do a memory load (read) to check if lock is free in order to prevent uncessary cache missed. 58 return Volatile.Read(ref m_Lock) == 0 && 59 Interlocked.CompareExchange(ref m_Lock, 1, 0) == 0; 60 } 61 62 /// <summary> 63 /// Try to acquire the lock, and spin only if <paramref name="spin"/> is <see langword="true"/>. 64 /// </summary> 65 /// <param name="spin">Set to true to spin the lock.</param> 66 /// <returns><see langword="true"/> if the lock was acquired, <see langword="false" otherwise./></returns> 67 [MethodImpl(MethodImplOptions.AggressiveInlining)] 68 internal bool TryAcquire(bool spin) 69 { 70 if (spin) 71 { 72 Acquire(); 73 return true; 74 } 75 76 return TryAcquire(); 77 } 78 79 /// <summary> 80 /// Release the lock 81 /// </summary> 82 [MethodImpl(MethodImplOptions.AggressiveInlining)] 83 internal void Release() 84 { 85 Volatile.Write(ref m_Lock, 0); 86 } 87 } 88 89 /// <summary> 90 /// Manages custom memory allocators. 91 /// </summary> 92 public static class AllocatorManager 93 { 94 internal static Block AllocateBlock<T>(ref this T t, int sizeOf, int alignOf, int items) where T : unmanaged, IAllocator 95 { 96 CheckValid(t.Handle); 97 Block block = default; 98 block.Range.Pointer = IntPtr.Zero; 99 block.Range.Items = items; 100 block.Range.Allocator = t.Handle; 101 block.BytesPerItem = sizeOf; 102 // Make the alignment multiple of cacheline size 103 block.Alignment = math.max(JobsUtility.CacheLineSize, alignOf); 104 105 var error = t.Try(ref block); 106 CheckFailedToAllocate(error); 107 return block; 108 } 109 110 internal static Block AllocateBlock<T,U>(ref this T t, U u, int items) where T : unmanaged, IAllocator where U : unmanaged 111 { 112 return AllocateBlock(ref t, UnsafeUtility.SizeOf<U>(), UnsafeUtility.AlignOf<U>(), items); 113 } 114 115 /// <summary> 116 /// Allocates memory directly from an allocator. 117 /// </summary> 118 /// <typeparam name="T">The type of allocator.</typeparam> 119 /// /// <param name="t">The allocator of type T used to allocator memory.</param> 120 /// <param name="sizeOf">The number of bytes to allocate to item.</param> 121 /// <param name="alignOf">The alignment in bytes.</param> 122 /// <param name="items">The number of items. Defaults to 1.</param> 123 /// <returns>A pointer to the allocated memory.</returns> 124 public static unsafe void* Allocate<T>(ref this T t, int sizeOf, int alignOf, int items = 1) where T : unmanaged, IAllocator 125 { 126 return (void*)AllocateBlock(ref t, sizeOf, alignOf, items).Range.Pointer; 127 } 128 129 internal static unsafe U* Allocate<T,U>(ref this T t, U u, int items) where T : unmanaged, IAllocator where U : unmanaged 130 { 131 return (U*)Allocate(ref t, UnsafeUtility.SizeOf<U>(), UnsafeUtility.AlignOf<U>(), items); 132 } 133 134 internal static unsafe void* AllocateStruct<T, U>(ref this T t, U u, int items) where T : unmanaged, IAllocator where U : unmanaged 135 { 136 return (void*)Allocate(ref t, UnsafeUtility.SizeOf<U>(), UnsafeUtility.AlignOf<U>(), items); 137 } 138 139 internal static unsafe void FreeBlock<T>(ref this T t, ref Block block) where T : unmanaged, IAllocator 140 { 141 CheckValid(t.Handle); 142 block.Range.Items = 0; 143 var error = t.Try(ref block); 144 CheckFailedToFree(error); 145 } 146 147 internal static unsafe void Free<T>(ref this T t, void* pointer, int sizeOf, int alignOf, int items) where T : unmanaged, IAllocator 148 { 149 if (pointer == null) 150 return; 151 Block block = default; 152 block.AllocatedItems = items; 153 block.Range.Pointer = (IntPtr)pointer; 154 block.BytesPerItem = sizeOf; 155 block.Alignment = alignOf; 156 t.FreeBlock(ref block); 157 } 158 159 internal static unsafe void Free<T,U>(ref this T t, U* pointer, int items) where T : unmanaged, IAllocator where U : unmanaged 160 { 161 Free(ref t, pointer, UnsafeUtility.SizeOf<U>(), UnsafeUtility.AlignOf<U>(), items); 162 } 163 164 /// <summary> 165 /// Allocates memory from an allocator. 166 /// </summary> 167 /// <param name="handle">A handle to the allocator.</param> 168 /// <param name="itemSizeInBytes">The number of bytes to allocate.</param> 169 /// <param name="alignmentInBytes">The alignment in bytes (must be a power of two).</param> 170 /// <param name="items">The number of values to allocate space for. Defaults to 1.</param> 171 /// <returns>A pointer to the allocated memory.</returns> 172 public unsafe static void* Allocate(AllocatorHandle handle, int itemSizeInBytes, int alignmentInBytes, int items = 1) 173 { 174 return handle.Allocate(itemSizeInBytes, alignmentInBytes, items); 175 } 176 177 /// <summary> 178 /// Allocates enough memory for an unmanaged value of a given type. 179 /// </summary> 180 /// <typeparam name="T">The type of value to allocate for.</typeparam> 181 /// <param name="handle">A handle to the allocator.</param> 182 /// <param name="items">The number of values to allocate for space for. Defaults to 1.</param> 183 /// <returns>A pointer to the allocated memory.</returns> 184 public unsafe static T* Allocate<T>(AllocatorHandle handle, int items = 1) where T : unmanaged 185 { 186 return handle.Allocate(default(T), items); 187 } 188 189 /// <summary> 190 /// Frees an allocation. 191 /// </summary> 192 /// <remarks>For some allocators, the size of the allocation must be known to properly deallocate. 193 /// Other allocators only need the pointer when deallocating and so will ignore `itemSizeInBytes`, `alignmentInBytes` and `items`.</remarks> 194 /// <param name="handle">A handle to the allocator.</param> 195 /// <param name="pointer">A pointer to the allocated memory.</param> 196 /// <param name="itemSizeInBytes">The size in bytes of the allocation.</param> 197 /// <param name="alignmentInBytes">The alignment in bytes (must be a power of two).</param> 198 /// <param name="items">The number of values that the memory was allocated for.</param> 199 public unsafe static void Free(AllocatorHandle handle, void* pointer, int itemSizeInBytes, int alignmentInBytes, int items = 1) 200 { 201 handle.Free(pointer, itemSizeInBytes, alignmentInBytes, items); 202 } 203 204 /// <summary> 205 /// Frees an allocation. 206 /// </summary> 207 /// <param name="handle">A handle to the allocator.</param> 208 /// <param name="pointer">A pointer to the allocated memory.</param> 209 public unsafe static void Free(AllocatorHandle handle, void* pointer) 210 { 211 handle.Free((byte*)pointer, 1); 212 } 213 214 /// <summary> 215 /// Frees an allocation. 216 /// </summary> 217 /// <remarks>For some allocators, the size of the allocation must be known to properly deallocate. 218 /// Other allocators only need the pointer when deallocating and so will ignore `T` and `items`.</remarks> 219 /// <typeparam name="T">The type of value that the memory was allocated for.</typeparam> 220 /// <param name="handle">A handle to the allocator.</param> 221 /// <param name="pointer">A pointer to the allocated memory.</param> 222 /// <param name="items">The number of values that the memory was allocated for.</param> 223 public unsafe static void Free<T>(AllocatorHandle handle, T* pointer, int items = 1) where T : unmanaged 224 { 225 handle.Free(pointer, items); 226 } 227 228 /// <summary> 229 /// Corresponds to Allocator.Invalid. 230 /// </summary> 231 /// <value>Corresponds to Allocator.Invalid.</value> 232 public static readonly AllocatorHandle Invalid = new AllocatorHandle { Index = 0 }; 233 234 /// <summary> 235 /// Corresponds to Allocator.None. 236 /// </summary> 237 /// <value>Corresponds to Allocator.None.</value> 238 public static readonly AllocatorHandle None = new AllocatorHandle { Index = 1 }; 239 240 /// <summary> 241 /// Corresponds to Allocator.Temp. 242 /// </summary> 243 /// <value>Corresponds to Allocator.Temp.</value> 244 public static readonly AllocatorHandle Temp = new AllocatorHandle { Index = 2 }; 245 246 /// <summary> 247 /// Corresponds to Allocator.TempJob. 248 /// </summary> 249 /// <value>Corresponds to Allocator.TempJob.</value> 250 public static readonly AllocatorHandle TempJob = new AllocatorHandle { Index = 3 }; 251 252 /// <summary> 253 /// Corresponds to Allocator.Persistent. 254 /// </summary> 255 /// <value>Corresponds to Allocator.Persistent.</value> 256 public static readonly AllocatorHandle Persistent = new AllocatorHandle { Index = 4 }; 257 258 /// <summary> 259 /// Corresponds to Allocator.AudioKernel. 260 /// </summary> 261 /// <value>Corresponds to Allocator.AudioKernel.</value> 262 /// <remarks>Do not use. Reserved for internal use.</remarks> 263 public static readonly AllocatorHandle AudioKernel = new AllocatorHandle { Index = 5 }; 264 265 /// <summary> 266 /// Used for calling an allocator function. 267 /// </summary> 268 public delegate int TryFunction(IntPtr allocatorState, ref Block block); 269 270 /// <summary> 271 /// Convert an Allocator to an AllocatorHandle, keeping the Version. 272 /// </summary> 273 /// <param name="a">The Allocator to convert.</param> 274 /// <returns>The AllocatorHandle of an allocator.</returns> 275 public static AllocatorHandle ConvertToAllocatorHandle(Allocator a) 276 { 277 ushort index = (ushort)((uint)a & 0xFFFF); 278 ushort version = (ushort)((uint)a >> 16); 279 return new AllocatorHandle { Index = index, Version = version }; 280 } 281 282 /// <summary> 283 /// Represents the allocator function used within an allocator. 284 /// </summary> 285 [StructLayout(LayoutKind.Sequential)] 286 public struct AllocatorHandle : IAllocator, IEquatable<AllocatorHandle>, IComparable<AllocatorHandle> 287 { 288 internal ref TableEntry TableEntry => ref SharedStatics.TableEntry.Ref.Data.ElementAt(Index); 289 internal bool IsInstalled => ((SharedStatics.IsInstalled.Ref.Data.ElementAt(Index>>6) >> (Index&63)) & 1) != 0; 290 291 internal void IncrementVersion() 292 { 293#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG 294 if (IsInstalled && IsCurrent) 295 { 296 // When allocator version is larger than 0x7FFF, allocator.ToAllocator 297 // returns a negative value which causes problem when comparing to Allocator.None. 298 // So only lower 15 bits of version is valid. 299 Version = OfficialVersion = (ushort)(++OfficialVersion & 0x7FFF); 300 } 301#endif 302 } 303 304 internal void Rewind() 305 { 306#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG 307 InvalidateDependents(); 308 IncrementVersion(); 309#endif 310 } 311 312 internal void Install(TableEntry tableEntry) 313 { 314#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG 315 // if this allocator has never been visited before, then the unsafelists for its child allocators 316 // and child safety handles are uninitialized, which means their allocator is Allocator.Invalid. 317 // rectify that here. 318 if (ChildAllocators.Allocator.Value != (int)Allocator.Persistent) 319 { 320 ChildAllocators = new UnsafeList<AllocatorHandle>(0, Allocator.Persistent); 321#if ENABLE_UNITY_COLLECTIONS_CHECKS 322 ChildSafetyHandles = new UnsafeList<AtomicSafetyHandle>(0, Allocator.Persistent); 323#endif 324 } 325#endif 326 Rewind(); 327 TableEntry = tableEntry; 328 } 329 330#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG 331 internal ref ushort OfficialVersion => ref SharedStatics.Version.Ref.Data.ElementAt(Index); 332 internal ref UnsafeList<AllocatorHandle> ChildAllocators => ref SharedStatics.ChildAllocators.Ref.Data.ElementAt(Index); 333 internal ref AllocatorHandle Parent => ref SharedStatics.Parent.Ref.Data.ElementAt(Index); 334 internal ref int IndexInParent => ref SharedStatics.IndexInParent.Ref.Data.ElementAt(Index); 335 336 internal bool IsCurrent => (Version == 0) || (Version == OfficialVersion); 337 internal bool IsValid => (Index < FirstUserIndex) || (IsInstalled && IsCurrent); 338 339#if ENABLE_UNITY_COLLECTIONS_CHECKS 340 internal ref Spinner ChildSpinLock => ref SharedStatics.ChildSpinLock.Ref.Data.ElementAt(Index); 341 internal ref UnsafeList<AtomicSafetyHandle> ChildSafetyHandles => ref SharedStatics.ChildSafetyHandles.Ref.Data.ElementAt(Index); 342 343 /// <summary> 344 /// <para>Determines if the handle is still valid, because we intend to release it if it is.</para> 345 /// </summary> 346 /// <param name="handle">Safety handle.</param> 347 internal static unsafe bool CheckExists(AtomicSafetyHandle handle) 348 { 349 bool res = false; 350 int* versionNode = (int*) (void*) handle.versionNode; 351 res = (handle.version == (*versionNode & AtomicSafetyHandle.ReadWriteDisposeCheck)); 352 return res; 353 } 354 355 internal static unsafe bool AreTheSame(AtomicSafetyHandle a, AtomicSafetyHandle b) 356 { 357 if(a.version != b.version) 358 return false; 359 if(a.versionNode != b.versionNode) 360 return false; 361 return true; 362 } 363 364 /// <summary> 365 /// For internal use only. 366 /// </summary> 367 /// <value>For internal use only.</value> 368 public const int InvalidChildSafetyHandleIndex = -1; 369 370 internal int AddSafetyHandle(AtomicSafetyHandle handle) 371 { 372 if(!NeedsUseAfterFreeTracking()) 373 return InvalidChildSafetyHandleIndex; 374 ChildSpinLock.Acquire(); 375 var result = ChildSafetyHandles.Length; 376 ChildSafetyHandles.Add(handle); 377 ChildSpinLock.Release(); 378 return result; 379 } 380 381 internal bool TryRemoveSafetyHandle(AtomicSafetyHandle handle, int safetyHandleIndex) 382 { 383 if(!NeedsUseAfterFreeTracking()) 384 return false; 385 if(safetyHandleIndex == InvalidChildSafetyHandleIndex) 386 return false; 387 388 ChildSpinLock.Acquire(); 389 safetyHandleIndex = math.min(safetyHandleIndex, ChildSafetyHandles.Length - 1); 390 while(safetyHandleIndex >= 0) 391 { 392 unsafe 393 { 394 var safetyHandle = ChildSafetyHandles.Ptr + safetyHandleIndex; 395 if(AreTheSame(*safetyHandle, handle)) 396 { 397 ChildSafetyHandles.RemoveAtSwapBack(safetyHandleIndex); 398 ChildSpinLock.Release(); 399 return true; 400 } 401 } 402 --safetyHandleIndex; 403 } 404 ChildSpinLock.Release(); 405 return false; 406 } 407#endif 408 409 internal bool NeedsUseAfterFreeTracking() 410 { 411 if (IsValid == false) 412 return false; 413 414 if (ChildAllocators.Allocator.Value != (int)Allocator.Persistent) 415 return false; 416 417 return true; 418 } 419 420 internal static bool AreTheSame(AllocatorHandle a, AllocatorHandle b) 421 { 422 if (a.Index != b.Index) 423 return false; 424 if (a.Version != b.Version) 425 return false; 426 return true; 427 } 428 429 /// <summary> 430 /// For internal use only. 431 /// </summary> 432 /// <value>For internal use only.</value> 433 public const int InvalidChildAllocatorIndex = -1; 434 435 internal int AddChildAllocator(AllocatorHandle handle) 436 { 437 if(!NeedsUseAfterFreeTracking()) 438 return InvalidChildAllocatorIndex; 439 var result = ChildAllocators.Length; 440 ChildAllocators.Add(handle); 441 handle.Parent = this; 442 handle.IndexInParent = result; 443 return result; 444 } 445 446 internal bool TryRemoveChildAllocator(AllocatorHandle handle, int childAllocatorIndex) 447 { 448 if(!NeedsUseAfterFreeTracking()) 449 return false; 450 if(childAllocatorIndex == InvalidChildAllocatorIndex) 451 return false; 452 childAllocatorIndex = math.min(childAllocatorIndex, ChildAllocators.Length - 1); 453 while(childAllocatorIndex >= 0) 454 { 455 unsafe 456 { 457 var allocatorHandle = ChildAllocators.Ptr + childAllocatorIndex; 458 if(AreTheSame(*allocatorHandle, handle)) 459 { 460 ChildAllocators.RemoveAtSwapBack(childAllocatorIndex); 461 return true; 462 } 463 } 464 --childAllocatorIndex; 465 } 466 return false; 467 } 468 469 // when you rewind an allocator, it invalidates and unregisters all of its child allocators - allocators that use as 470 // backing memory, memory that was allocated from this (parent) allocator. the rewind operation was itself unmanaged, 471 // until we added a managed global table of delegates alongside the unmanaged global table of function pointers. once 472 // this table was added, the "unregister" extension function became managed, because it manipulates a managed array of 473 // delegates. 474 475 // a workaround (UnmanagedUnregister) was found that makes it possible for rewind to become unmanaged again: only in 476 // the case that we rewind an allocator and invalidate all of its child allocators, we then unregister the child 477 // allocators without touching the managed array of delegates as well. 478 479 // this can "leak" delegates - it's possible for this to cause us to hold onto a GC reference to a delegate until 480 // the end of the program, long after the delegate is no longer needed. but, there are only 65,536 such slots to 481 // burn, and delegates are small data structures, and the leak ends when a delegate slot is reused, and most importantly, 482 // when we've rewound an allocator while child allocators remain registered, we are likely before long to encounter 483 // a use-before-free crash or a safety handle violation, both of which are likely to terminate the session before 484 // anything can leak. 485 486 internal void InvalidateDependents() 487 { 488 if (!NeedsUseAfterFreeTracking()) 489 return; 490#if ENABLE_UNITY_COLLECTIONS_CHECKS 491 ChildSpinLock.Acquire(); 492 for (var i = 0; i < ChildSafetyHandles.Length; ++i) 493 { 494 unsafe 495 { 496 AtomicSafetyHandle* handle = ChildSafetyHandles.Ptr + i; 497 if(CheckExists(*handle)) 498 AtomicSafetyHandle.Release(*handle); 499 } 500 } 501 ChildSafetyHandles.Clear(); 502 ChildSafetyHandles.TrimExcess(); 503 ChildSpinLock.Release(); 504#endif 505 if (Parent.IsValid) 506 Parent.TryRemoveChildAllocator(this, IndexInParent); 507 Parent = default; 508 IndexInParent = InvalidChildAllocatorIndex; 509 for (var i = 0; i < ChildAllocators.Length; ++i) 510 { 511 unsafe 512 { 513 AllocatorHandle* handle = (AllocatorHandle*)ChildAllocators.Ptr + i; 514 if (handle->IsValid) 515 handle->UnmanagedUnregister(); // see above comment 516 } 517 } 518 ChildAllocators.Clear(); 519 ChildAllocators.TrimExcess(); 520 } 521 522#endif 523 524 /// <summary> 525 /// Implicitly convert an Allocator to an AllocatorHandle with its Version being reset to 0. 526 /// </summary> 527 /// <param name="a">The Allocator to convert.</param> 528 /// <returns>The AllocatorHandle of an allocator.</returns> 529 public static implicit operator AllocatorHandle(Allocator a) => new AllocatorHandle 530 { 531 Index = (ushort)((uint)a & 0xFFFF), 532 Version = 0 533 }; 534 535 /// <summary> 536 /// This allocator's index into the global table of allocator functions. 537 /// </summary> 538 /// <value>This allocator's index into the global table of allocator functions.</value> 539 public ushort Index; 540 541 /// <summary> 542 /// This allocator's version number. 543 /// </summary> 544 /// <remarks>An allocator function is uniquely identified by its *combination* of <see cref="Index"/> and <see cref="Version"/> together: each 545 /// index has a version number that starts at 0; the version number is incremented each time the allocator is invalidated. Only the 546 /// lower 15 bits of Version is in use because when allocator version is larger than 0x7FFF, allocator.ToAllocator returns a negative value 547 /// which causes problem when comparing to Allocator.None. 548 /// </remarks> 549 /// <value>This allocator's version number.</value> 550 public ushort Version; 551 552 /// <summary> 553 /// The <see cref="Index"/> cast to int. 554 /// </summary> 555 /// <value>The <see cref="Index"/> cast to int.</value> 556 public int Value => Index; 557 558 /// <summary> 559 /// Allocates a block from this allocator. 560 /// </summary> 561 /// <typeparam name="T">The type of value to allocate for.</typeparam> 562 /// <param name="block">Outputs the allocated block.</param> 563 /// <param name="items">The number of values to allocate for.</param> 564 /// <returns>0 if successful. Otherwise, returns the error code from the allocator function.</returns> 565 public int TryAllocateBlock<T>(out Block block, int items) where T : unmanaged 566 { 567 block = new Block 568 { 569 Range = new Range { Items = items, Allocator = this }, 570 BytesPerItem = UnsafeUtility.SizeOf<T>(), 571 Alignment = 1 << math.min(3, math.tzcnt(UnsafeUtility.SizeOf<T>())) 572 }; 573 var returnCode = Try(ref block); 574 return returnCode; 575 } 576 577 /// <summary> 578 /// Allocates a block with this allocator function. 579 /// </summary> 580 /// <typeparam name="T">The type of value to allocate for.</typeparam> 581 /// <param name="items">The number of values to allocate for.</param> 582 /// <returns>The allocated block.</returns> 583 /// <exception cref="ArgumentException">Thrown if the allocator is not valid or if the allocation failed.</exception> 584 public Block AllocateBlock<T>(int items) where T : unmanaged 585 { 586 CheckValid(this); 587 var error = TryAllocateBlock<T>(out Block block, items); 588 CheckAllocatedSuccessfully(error); 589 return block; 590 } 591 592 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 593 static void CheckAllocatedSuccessfully(int error) 594 { 595 if (error != 0) 596 throw new ArgumentException($"Error {error}: Failed to Allocate"); 597 } 598 599 /// <summary> 600 /// For internal use only. 601 /// </summary> 602 /// <value>For internal use only.</value> 603 public TryFunction Function => default; 604 605 /// <summary> 606 /// Tries to allocate the block with this allocator. 607 /// </summary> 608 /// <param name="block">The block to allocate.</param> 609 /// <returns>0 if successful. Otherwise, returns an error code.</returns> 610 public int Try(ref Block block) 611 { 612 block.Range.Allocator = this; 613 var error = AllocatorManager.Try(ref block); 614 return error; 615 } 616 617 /// <summary> 618 /// This handle. 619 /// </summary> 620 /// <value>This handle.</value> 621 public AllocatorHandle Handle { get { return this; } set { this = value; } } 622 623 /// <summary> 624 /// Retrieve the Allocator associated with this allocator handle. 625 /// </summary> 626 /// <value>The Allocator retrieved.</value> 627 public Allocator ToAllocator 628 { 629 get 630 { 631 uint lo = Index; 632 uint hi = Version; 633 uint value = (hi << 16) | lo; 634 return (Allocator)value; 635 } 636 } 637 638 /// <summary> 639 /// Check whether this allocator is a custom allocator. 640 /// </summary> 641 /// <remarks>The AllocatorHandle is a custom allocator if its Index is larger or equal to `FirstUserIndex`.</remarks> 642 /// <value>True if this AllocatorHandle is a custom allocator.</value> 643 public bool IsCustomAllocator { get { return this.Index >= FirstUserIndex; } } 644 645 /// <summary> 646 /// Check whether this allocator will automatically dispose allocations. 647 /// </summary> 648 /// <value>True if allocations made by this AllocatorHandle are not automatically disposed.</value> 649 public bool IsAutoDispose { get { return ((SharedStatics.IsAutoDispose.Ref.Data.ElementAt(Index >> 6) >> (Index & 63)) & 1) != 0; } } 650 651 /// <summary> 652 /// Dispose the allocator. 653 /// </summary> 654 public void Dispose() 655 { 656 Rewind(); 657#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG 658 ChildAllocators.Dispose(); 659#if ENABLE_UNITY_COLLECTIONS_CHECKS 660 ChildSafetyHandles.Dispose(); 661#endif 662#endif 663 TableEntry = default; 664 } 665 666 /// <summary> 667 /// <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> instances are equal if they refer to the same instance at the same version. 668 /// </summary> 669 /// <param name="obj">Object containing an <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/>.</param> 670 /// <returns>Returns true if both handles are for the same allocator instance at the same version, otherwise false.</returns> 671 public override bool Equals(object obj) 672 { 673 if (obj is AllocatorHandle) 674 return Value == ((AllocatorHandle) obj).Value; 675 676 if (obj is Allocator) 677 return ToAllocator == ((Allocator)obj); 678 679 return false; 680 } 681 682 /// <summary> 683 /// <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> instances are equal if they refer to the same instance at the same version. 684 /// </summary> 685 /// <param name="other"><seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> to compare against.</param> 686 /// <returns>Returns true if both handles are for the same allocator instance at the same version, otherwise false.</returns> 687 [MethodImpl(MethodImplOptions.AggressiveInlining)] 688 public bool Equals(AllocatorHandle other) 689 { 690 return Value == other.Value; 691 } 692 693 /// <summary> 694 /// <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> instances are equal if they refer to the same instance at the same version. 695 /// </summary> 696 /// <param name="other"><seealso cref="Unity.Collections.Allocator"/> to compare against.</param> 697 /// <returns>Returns true if both handles are for the same allocator instance at the same version, otherwise false.</returns> 698 [MethodImpl(MethodImplOptions.AggressiveInlining)] 699 public bool Equals(Allocator other) 700 { 701 return ToAllocator == other; 702 } 703 704 /// <summary> 705 /// A hash used for comparisons. 706 /// </summary> 707 /// <returns>A unique hash code.</returns> 708 [MethodImpl(MethodImplOptions.AggressiveInlining)] 709 public override int GetHashCode() 710 { 711 return Value; 712 } 713 714 /// <summary> 715 /// Evaluates if one <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> is equal to the other. 716 /// </summary> 717 /// <param name="lhs">The left-hand side</param> 718 /// <param name="rhs">The right-hand side</param> 719 /// <returns>True if the left-hand side's <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> is equal to the right-hand side's.</returns> 720 [MethodImpl(MethodImplOptions.AggressiveInlining)] 721 public static bool operator ==(AllocatorHandle lhs, AllocatorHandle rhs) 722 { 723 return lhs.Value == rhs.Value; 724 } 725 726 /// <summary> 727 /// Evaluates if one <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> is not equal to the other. 728 /// </summary> 729 /// <param name="lhs">The left-hand side</param> 730 /// <param name="rhs">The right-hand side</param> 731 /// <returns>True if the left-hand side's <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> is not equal to the right-hand side's.</returns> 732 [MethodImpl(MethodImplOptions.AggressiveInlining)] 733 public static bool operator !=(AllocatorHandle lhs, AllocatorHandle rhs) 734 { 735 return lhs.Value != rhs.Value; 736 } 737 738 /// <summary> 739 /// Evaluates if one <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> is less than the other. 740 /// </summary> 741 /// <param name="lhs">The left-hand side</param> 742 /// <param name="rhs">The right-hand side</param> 743 /// <returns>True if the left-hand side's <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> is less than the right-hand side's.</returns> 744 [MethodImpl(MethodImplOptions.AggressiveInlining)] 745 public static bool operator <(AllocatorHandle lhs, AllocatorHandle rhs) 746 { 747 return lhs.Value < rhs.Value; 748 } 749 750 /// <summary> 751 /// Evaluates if one <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> is greater than the other. 752 /// </summary> 753 /// <param name="lhs">The left-hand side</param> 754 /// <param name="rhs">The right-hand side</param> 755 /// <returns>True if the left-hand side's <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> is greater than the right-hand side's.</returns> 756 [MethodImpl(MethodImplOptions.AggressiveInlining)] 757 public static bool operator >(AllocatorHandle lhs, AllocatorHandle rhs) 758 { 759 return lhs.Value > rhs.Value; 760 } 761 762 /// <summary> 763 /// Evaluates if one <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> is less than or equal to the other. 764 /// </summary> 765 /// <param name="lhs">The left-hand side</param> 766 /// <param name="rhs">The right-hand side</param> 767 /// <returns>True if the left-hand side's <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> is less than or equal to the right-hand side's.</returns> 768 [MethodImpl(MethodImplOptions.AggressiveInlining)] 769 public static bool operator <=(AllocatorHandle lhs, AllocatorHandle rhs) 770 { 771 return lhs.Value <= rhs.Value; 772 } 773 774 /// <summary> 775 /// Evaluates if one <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> is greater than or equal to the other. 776 /// </summary> 777 /// <param name="lhs">The left-hand side</param> 778 /// <param name="rhs">The right-hand side</param> 779 /// <returns>True if the left-hand side's <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> is greater than or equal to the right-hand side's.</returns> 780 [MethodImpl(MethodImplOptions.AggressiveInlining)] 781 public static bool operator >=(AllocatorHandle lhs, AllocatorHandle rhs) 782 { 783 return lhs.Value >= rhs.Value; 784 } 785 786 /// <summary> 787 /// Compare this <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> against a given one 788 /// </summary> 789 /// <param name="other">The other <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> to compare to</param> 790 /// <returns>Difference between <seealso cref="Unity.Collections.AllocatorManager.AllocatorHandle"/> values</returns> 791 [MethodImpl(MethodImplOptions.AggressiveInlining)] 792 public int CompareTo(AllocatorHandle other) 793 { 794 return Value - other.Value; 795 } 796 } 797 798 /// <summary> 799 /// For internal use only. 800 /// </summary> 801 [StructLayout(LayoutKind.Sequential)] 802 public struct BlockHandle 803 { 804 /// <summary> 805 /// Represents the handle. 806 /// </summary> 807 /// <value>Represents the handle.</value> 808 public ushort Value; 809 } 810 811 /// <summary> 812 /// A range of allocated memory. 813 /// </summary> 814 /// <remarks>The name is perhaps misleading: only in combination with a <see cref="Block"/> does 815 /// a `Range` have sufficient information to represent the number of bytes in an allocation. The reason `Range` is its own type that's separate from `Block` 816 /// stems from some efficiency concerns in the implementation details. In most cases, a `Range` is only used in conjunction with an associated `Block`. 817 /// </remarks> 818 [StructLayout(LayoutKind.Sequential)] 819 public struct Range : IDisposable 820 { 821 /// <summary> 822 /// Pointer to the start of this range. 823 /// </summary> 824 /// <value>Pointer to the start of this range.</value> 825 public IntPtr Pointer; // 0 826 827 /// <summary> 828 /// Number of items allocated in this range. 829 /// </summary> 830 /// <remarks>The actual allocation may be larger. See <see cref="Block.AllocatedItems"/>.</remarks> 831 /// <value>Number of items allocated in this range. </value> 832 public int Items; // 8 833 834 /// <summary> 835 /// The allocator function used for this range. 836 /// </summary> 837 /// <value>The allocator function used for this range.</value> 838 public AllocatorHandle Allocator; // 12 839 840 /// <summary> 841 /// Deallocates the memory represented by this range. 842 /// </summary> 843 /// <remarks> 844 /// Same as disposing the <see cref="Block"/> which contains this range. 845 /// 846 /// Cannot be used with allocators which need the allocation size to deallocate. 847 /// </remarks> 848 public void Dispose() 849 { 850 Block block = new Block { Range = this }; 851 block.Dispose(); 852 this = block.Range; 853 } 854 } 855 856 /// <summary> 857 /// Represents an individual allocation within an allocator. 858 /// </summary> 859 /// <remarks>A block consists of a <see cref="Range"/> plus metadata about the type of elements for which the block was allocated.</remarks> 860 [StructLayout(LayoutKind.Sequential)] 861 public struct Block : IDisposable 862 { 863 /// <summary> 864 /// The range of memory encompassed by this block. 865 /// </summary> 866 /// <value>The range of memory encompassed by this block.</value> 867 public Range Range; 868 869 /// <summary> 870 /// Number of bytes per item. 871 /// </summary> 872 /// <value>Number of bytes per item.</value> 873 public int BytesPerItem; 874 875 /// <summary> 876 /// Number of items allocated for. 877 /// </summary> 878 /// <value>Number of items allocated for.</value> 879 public int AllocatedItems; 880 881 /// <summary> 882 /// Log2 of the byte alignment. 883 /// </summary> 884 /// <remarks>The alignment must always be power of 2. Storing the alignment as its log2 helps enforces this.</remarks> 885 /// <value>Log2 of the byte alignment.</value> 886 public byte Log2Alignment; 887 888 /// <summary> 889 /// This field only exists to pad the `Block` struct. Ignore it. 890 /// </summary> 891 /// <value>This field only exists to pad the `Block` struct. Ignore it.</value> 892 public byte Padding0; 893 894 /// <summary> 895 /// This field only exists to pad the `Block` struct. Ignore it. 896 /// </summary> 897 /// <value>This field only exists to pad the `Block` struct. Ignore it.</value> 898 public ushort Padding1; 899 900 /// <summary> 901 /// This field only exists to pad the `Block` struct. Ignore it. 902 /// </summary> 903 /// <value>This field only exists to pad the `Block` struct. Ignore it.</value> 904 public uint Padding2; 905 906 /// <summary> 907 /// Number of bytes requested for this block. 908 /// </summary> 909 /// <remarks>The actual allocation size may be larger due to alignment.</remarks> 910 /// <value>Number of bytes requested for this block.</value> 911 public long Bytes => (long) BytesPerItem * Range.Items; 912 913 /// <summary> 914 /// Number of bytes allocated for this block. 915 /// </summary> 916 /// <remarks>The requested allocation size may be smaller. Any excess is due to alignment</remarks> 917 /// <value>Number of bytes allocated for this block.</value> 918 public long AllocatedBytes => (long) BytesPerItem * AllocatedItems; 919 920 /// <summary> 921 /// The alignment. 922 /// </summary> 923 /// <remarks>Must be power of 2 that's greater than or equal to 0. 924 /// 925 /// Set alignment *before* the allocation is made. Setting it after has no effect on the allocation.</remarks> 926 /// <param name="value">A new alignment. If not a power of 2, it will be rounded up to the next largest power of 2.</param> 927 /// <value>The alignment.</value> 928 public int Alignment 929 { 930 get => 1 << Log2Alignment; 931 set => Log2Alignment = (byte)(32 - math.lzcnt(math.max(1, value) - 1)); 932 } 933 934 /// <summary> 935 /// Deallocates this block. 936 /// </summary> 937 /// <remarks>Same as <see cref="TryAllocate"/>.</remarks> 938 public void Dispose() 939 { 940 TryFree(); 941 } 942 943 /// <summary> 944 /// Attempts to allocate this block. 945 /// </summary> 946 /// <returns>0 if successful. Otherwise, returns the error code from the allocator function.</returns> 947 public int TryAllocate() 948 { 949 Range.Pointer = IntPtr.Zero; 950 return Try(ref this); 951 } 952 953 /// <summary> 954 /// Attempts to free this block. 955 /// </summary> 956 /// <returns>0 if successful. Otherwise, returns the error code from the allocator function.</returns> 957 public int TryFree() 958 { 959 Range.Items = 0; 960 return Try(ref this); 961 } 962 963 /// <summary> 964 /// Allocates this block. 965 /// </summary> 966 /// <exception cref="ArgumentException">Thrown if safety checks are enabled and the allocation fails.</exception> 967 public void Allocate() 968 { 969 var error = TryAllocate(); 970 CheckFailedToAllocate(error); 971 } 972 973 /// <summary> 974 /// Frees the block. 975 /// </summary> 976 /// <exception cref="ArgumentException">Thrown if safety checks are enabled and the deallocation fails.</exception> 977 public void Free() 978 { 979 var error = TryFree(); 980 CheckFailedToFree(error); 981 } 982 983 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 984 void CheckFailedToAllocate(int error) 985 { 986 if (error != 0) 987 throw new ArgumentException($"Error {error}: Failed to Allocate {this}"); 988 } 989 990 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 991 void CheckFailedToFree(int error) 992 { 993 if (error != 0) 994 throw new ArgumentException($"Error {error}: Failed to Free {this}"); 995 } 996 } 997 998 /// <summary> 999 /// An allocator function pointer. 1000 /// </summary> 1001 public interface IAllocator : IDisposable 1002 { 1003 /// <summary> 1004 /// The allocator function. It can allocate, deallocate, or reallocate. 1005 /// </summary> 1006 TryFunction Function { get; } 1007 1008 /// <summary> 1009 /// Invoke the allocator function. 1010 /// </summary> 1011 /// <param name="block">The block to allocate, deallocate, or reallocate. See <see cref="AllocatorManager.Try"/></param> 1012 /// <returns>0 if successful. Otherwise, returns the error code from the allocator function.</returns> 1013 int Try(ref Block block); 1014 1015 /// <summary> 1016 /// This allocator. 1017 /// </summary> 1018 /// <value>This allocator.</value> 1019 AllocatorHandle Handle { get; set; } 1020 1021 /// <summary> 1022 /// Cast the Allocator index into Allocator 1023 /// </summary> 1024 Allocator ToAllocator { get; } 1025 1026 /// <summary> 1027 /// Check whether an allocator is a custom allocator 1028 /// </summary> 1029 bool IsCustomAllocator { get; } 1030 1031 /// <summary> 1032 /// Check whether an allocator will automatically dispose allocations. 1033 /// </summary> 1034 /// <remarks>Allocations made by allocator are not automatically disposed by default.</remarks> 1035 bool IsAutoDispose { get { return false; } } 1036 } 1037 1038 /// <summary> 1039 /// Memory allocation Success status 1040 /// </summary> 1041 public const int kErrorNone = 0; 1042 1043 /// <summary> 1044 /// Memory allocation Buffer Overflow status 1045 /// </summary> 1046 public const int kErrorBufferOverflow = -1; 1047 1048 [BurstDiscard] 1049 private static void CheckDelegate(ref bool useDelegate) 1050 { 1051 //@TODO: This should use BurstCompiler.IsEnabled once that is available as an efficient API. 1052 useDelegate = true; 1053 } 1054 1055 private static bool UseDelegate() 1056 { 1057 bool result = false; 1058 CheckDelegate(ref result); 1059 return result; 1060 } 1061 1062 private static int allocate_block(ref Block block) 1063 { 1064 TableEntry tableEntry = default; 1065 tableEntry = block.Range.Allocator.TableEntry; 1066 var function = new FunctionPointer<TryFunction>(tableEntry.function); 1067 // this is a path for bursted caller, for non-Burst C#, it generates garbage each time we call Invoke 1068 return function.Invoke(tableEntry.state, ref block); 1069 } 1070 1071 [BurstDiscard] 1072 private static void forward_mono_allocate_block(ref Block block, ref int error) 1073 { 1074 TableEntry tableEntry = default; 1075 tableEntry = block.Range.Allocator.TableEntry; 1076 1077 var index = block.Range.Allocator.Handle.Index; 1078 if (index >= MaxNumCustomAllocators) 1079 { 1080 throw new ArgumentException("Allocator index into TryFunction delegate table exceeds maximum."); 1081 } 1082 ref TryFunction function = ref Managed.TryFunctionDelegates[block.Range.Allocator.Handle.Index]; 1083 error = function(tableEntry.state, ref block); 1084 } 1085 1086 internal static Allocator LegacyOf(AllocatorHandle handle) 1087 { 1088 if (handle.Value >= FirstUserIndex) 1089 return Allocator.Persistent; 1090 return (Allocator) handle.Value; 1091 } 1092 1093 static unsafe int TryLegacy(ref Block block) 1094 { 1095 if (block.Range.Pointer == IntPtr.Zero) // Allocate 1096 { 1097 block.Range.Pointer = (IntPtr)Memory.Unmanaged.Allocate(block.Bytes, block.Alignment, LegacyOf(block.Range.Allocator)); 1098 block.AllocatedItems = block.Range.Items; 1099 return (block.Range.Pointer == IntPtr.Zero) ? -1 : 0; 1100 } 1101 if (block.Bytes == 0) // Free 1102 { 1103 if (LegacyOf(block.Range.Allocator) != Allocator.None) 1104 { 1105 Memory.Unmanaged.Free((void*)block.Range.Pointer, LegacyOf(block.Range.Allocator)); 1106 } 1107 block.Range.Pointer = IntPtr.Zero; 1108 block.AllocatedItems = 0; 1109 return 0; 1110 } 1111 // Reallocate (keep existing pointer and change size if possible. otherwise, allocate new thing and copy) 1112 return -1; 1113 } 1114 1115 /// <summary> 1116 /// Invokes the allocator function of a block. 1117 /// </summary> 1118 /// <remarks>The allocator function is looked up from a global table. 1119 /// 1120 /// - If the block range's Pointer is null, it will allocate. 1121 /// - If the block range's Pointer is not null, it will reallocate. 1122 /// - If the block range's Items is 0, it will deallocate. 1123 /// </remarks> 1124 /// <param name="block">The block to allocate, deallocate, or reallocate.</param> 1125 /// <returns>0 if successful. Otherwise, returns the error code from the block's allocator function.</returns> 1126 public static unsafe int Try(ref Block block) 1127 { 1128 if (block.Range.Allocator.Value < FirstUserIndex) 1129 return TryLegacy(ref block); 1130 TableEntry tableEntry = default; 1131 tableEntry = block.Range.Allocator.TableEntry; 1132 var function = new FunctionPointer<TryFunction>(tableEntry.function); 1133#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG 1134 // if the allocator being passed in has a version of 0, that means "whatever the current version is." 1135 // so we patch it here, with whatever the current version is... 1136 if (block.Range.Allocator.Version == 0) 1137 block.Range.Allocator.Version = block.Range.Allocator.OfficialVersion; 1138#endif 1139 1140 if (UseDelegate()) 1141 { 1142 int error = kErrorNone; 1143 forward_mono_allocate_block(ref block, ref error); 1144 return error; 1145 } 1146 return allocate_block(ref block); 1147 } 1148 1149 /// <summary> 1150 /// A stack allocator with no storage of its own. Uses the storage of its parent. 1151 /// </summary> 1152 [BurstCompile] 1153 internal struct StackAllocator : IAllocator, IDisposable 1154 { 1155 public AllocatorHandle Handle { get { return m_handle; } set { m_handle = value; } } 1156 public Allocator ToAllocator { get { return m_handle.ToAllocator; } } 1157 public bool IsCustomAllocator { get { return m_handle.IsCustomAllocator; } } 1158 1159 internal AllocatorHandle m_handle; 1160 1161 internal Block m_storage; 1162 internal long m_top; 1163 1164 public void Initialize(Block storage) 1165 { 1166 m_storage = storage; 1167 m_top = 0; 1168#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG 1169 m_storage.Range.Allocator.AddChildAllocator(Handle); 1170#endif 1171 } 1172 1173 public unsafe int Try(ref Block block) 1174 { 1175 if (block.Range.Pointer == IntPtr.Zero) // Allocate 1176 { 1177 if (m_top + block.Bytes > m_storage.Bytes) 1178 { 1179 return -1; 1180 } 1181 1182 block.Range.Pointer = (IntPtr)((byte*)m_storage.Range.Pointer + m_top); 1183 block.AllocatedItems = block.Range.Items; 1184 m_top += block.Bytes; 1185 return 0; 1186 } 1187 1188 if (block.Bytes == 0) // Free 1189 { 1190 if ((byte*)block.Range.Pointer - (byte*)m_storage.Range.Pointer == (long)(m_top - block.AllocatedBytes)) 1191 { 1192 m_top -= block.AllocatedBytes; 1193 var blockSizeInBytes = block.AllocatedItems * block.BytesPerItem; 1194 block.Range.Pointer = IntPtr.Zero; 1195 block.AllocatedItems = 0; 1196 return 0; 1197 } 1198 1199 return -1; 1200 } 1201 1202 // Reallocate (keep existing pointer and change size if possible. otherwise, allocate new thing and copy) 1203 return -1; 1204 } 1205 1206 [BurstCompile] 1207 [MonoPInvokeCallback(typeof(TryFunction))] 1208 public static unsafe int Try(IntPtr allocatorState, ref Block block) 1209 { 1210 return ((StackAllocator*)allocatorState)->Try(ref block); 1211 } 1212 1213 public TryFunction Function => Try; 1214 1215 public void Dispose() 1216 { 1217 m_handle.Rewind(); 1218 } 1219 } 1220 1221 /// <summary> 1222 /// Slab allocator with no backing storage. 1223 /// </summary> 1224 [BurstCompile] 1225 internal struct SlabAllocator : IAllocator, IDisposable 1226 { 1227 public AllocatorHandle Handle { get { return m_handle; } set { m_handle = value; } } 1228 1229 public Allocator ToAllocator { get { return m_handle.ToAllocator; } } 1230 1231 public bool IsCustomAllocator { get { return m_handle.IsCustomAllocator; } } 1232 1233 internal AllocatorHandle m_handle; 1234 1235 internal Block Storage; 1236 internal int Log2SlabSizeInBytes; 1237 internal FixedList4096Bytes<int> Occupied; 1238 internal long budgetInBytes; 1239 internal long allocatedBytes; 1240 1241 public long BudgetInBytes => budgetInBytes; 1242 1243 public long AllocatedBytes => allocatedBytes; 1244 1245 internal int SlabSizeInBytes 1246 { 1247 get => 1 << Log2SlabSizeInBytes; 1248 set => Log2SlabSizeInBytes = (byte)(32 - math.lzcnt(math.max(1, value) - 1)); 1249 } 1250 1251 internal int Slabs => (int)(Storage.Bytes >> Log2SlabSizeInBytes); 1252 1253 internal void Initialize(Block storage, int slabSizeInBytes, long budget) 1254 { 1255#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG 1256 storage.Range.Allocator.AddChildAllocator(Handle); 1257#endif 1258 Assert.IsTrue((slabSizeInBytes & (slabSizeInBytes - 1)) == 0); 1259 Storage = storage; 1260 Log2SlabSizeInBytes = 0; 1261 Occupied = default; 1262 budgetInBytes = budget; 1263 allocatedBytes = 0; 1264 SlabSizeInBytes = slabSizeInBytes; 1265 Occupied.Length = (Slabs + 31) / 32; 1266 } 1267 1268 public int Try(ref Block block) 1269 { 1270 if (block.Range.Pointer == IntPtr.Zero) // Allocate 1271 { 1272 if (block.Bytes + allocatedBytes > budgetInBytes) 1273 return -2; //over allocator budget 1274 if (block.Bytes > SlabSizeInBytes) 1275 return -1; 1276 for (var wordIndex = 0; wordIndex < Occupied.Length; ++wordIndex) 1277 { 1278 var word = Occupied[wordIndex]; 1279 if (word == -1) 1280 continue; 1281 for (var bitIndex = 0; bitIndex < 32; ++bitIndex) 1282 if ((word & (1 << bitIndex)) == 0) 1283 { 1284 Occupied[wordIndex] |= 1 << bitIndex; 1285 block.Range.Pointer = Storage.Range.Pointer + 1286 (int)(SlabSizeInBytes * (wordIndex * 32U + bitIndex)); 1287 block.AllocatedItems = SlabSizeInBytes / block.BytesPerItem; 1288 allocatedBytes += block.Bytes; 1289 return 0; 1290 } 1291 } 1292 1293 return -1; 1294 } 1295 1296 if (block.Bytes == 0) // Free 1297 { 1298 var slabIndex = ((ulong)block.Range.Pointer - (ulong)Storage.Range.Pointer) >> 1299 Log2SlabSizeInBytes; 1300 int wordIndex = (int)(slabIndex >> 5); 1301 int bitIndex = (int)(slabIndex & 31); 1302 Occupied[wordIndex] &= ~(1 << bitIndex); 1303 block.Range.Pointer = IntPtr.Zero; 1304 var blockSizeInBytes = block.AllocatedItems * block.BytesPerItem; 1305 allocatedBytes -= blockSizeInBytes; 1306 block.AllocatedItems = 0; 1307 return 0; 1308 } 1309 1310 // Reallocate (keep existing pointer and change size if possible. otherwise, allocate new thing and copy) 1311 return -1; 1312 } 1313 1314 [BurstCompile] 1315 [MonoPInvokeCallback(typeof(TryFunction))] 1316 public static unsafe int Try(IntPtr allocatorState, ref Block block) 1317 { 1318 return ((SlabAllocator*)allocatorState)->Try(ref block); 1319 } 1320 1321 public TryFunction Function => Try; 1322 1323 public void Dispose() 1324 { 1325 m_handle.Rewind(); 1326 } 1327 } 1328 1329 internal struct TableEntry 1330 { 1331 internal IntPtr function; 1332 internal IntPtr state; 1333 } 1334 1335 internal struct Array16<T> where T : unmanaged 1336 { 1337 internal T f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15; 1338 } 1339 internal struct Array256<T> where T : unmanaged 1340 { 1341 internal Array16<T> f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15; 1342 } 1343 internal struct Array4096<T> where T : unmanaged 1344 { 1345 internal Array256<T> f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15; 1346 } 1347 internal struct Array32768<T> : IIndexable<T> where T : unmanaged 1348 { 1349 internal Array4096<T> f0, f1, f2, f3, f4, f5, f6, f7; 1350 public int Length { get { return 32768; } set {} } 1351 public ref T ElementAt(int index) 1352 { 1353 unsafe { fixed(Array4096<T>* p = &f0) { return ref UnsafeUtility.AsRef<T>((T*)p + index); } } 1354 } 1355 } 1356 1357 /// <summary> 1358 /// Contains arrays of the allocator function pointers. 1359 /// </summary> 1360 internal sealed class SharedStatics 1361 { 1362 internal sealed class IsInstalled { internal static readonly SharedStatic<Long1024> Ref = SharedStatic<Long1024>.GetOrCreate<IsInstalled>(); } 1363 internal sealed class TableEntry { internal static readonly SharedStatic<Array32768<AllocatorManager.TableEntry>> Ref = SharedStatic<Array32768<AllocatorManager.TableEntry>>.GetOrCreate<TableEntry>(); } 1364 internal sealed class IsAutoDispose { internal static readonly SharedStatic<Long1024> Ref = SharedStatic<Long1024>.GetOrCreate<IsAutoDispose>(); } 1365#if ENABLE_UNITY_COLLECTIONS_CHECKS 1366 internal sealed class ChildSpinLock { internal static readonly SharedStatic<Array32768<Spinner>> Ref = SharedStatic<Array32768<Spinner>>.GetOrCreate<ChildSpinLock>(); } 1367 internal sealed class ChildSafetyHandles { internal static readonly SharedStatic<Array32768<UnsafeList<AtomicSafetyHandle>>> Ref = SharedStatic<Array32768<UnsafeList<AtomicSafetyHandle>>>.GetOrCreate<ChildSafetyHandles>(); } 1368#endif 1369#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG 1370 internal sealed class Version { internal static readonly SharedStatic<Array32768<ushort>> Ref = SharedStatic<Array32768<ushort>>.GetOrCreate<Version>(); } 1371 internal sealed class ChildAllocators { internal static readonly SharedStatic<Array32768<UnsafeList<AllocatorHandle>>> Ref = SharedStatic<Array32768<UnsafeList<AllocatorHandle>>>.GetOrCreate<ChildAllocators>(); } 1372 internal sealed class Parent { internal static readonly SharedStatic<Array32768<AllocatorHandle>> Ref = SharedStatic<Array32768<AllocatorHandle>>.GetOrCreate<Parent>(); } 1373 internal sealed class IndexInParent { internal static readonly SharedStatic<Array32768<int>> Ref = SharedStatic<Array32768<int>>.GetOrCreate<IndexInParent>(); } 1374#endif 1375 } 1376 1377 internal static class Managed 1378 { 1379 /// <summary> 1380 /// Global delegate table to hold TryFunction delegates for managed callers 1381 /// </summary> 1382 internal static TryFunction[] TryFunctionDelegates = new TryFunction[MaxNumCustomAllocators]; 1383 1384 /// <summary> 1385 /// Register TryFunction delegates for managed caller to avoid garbage collections 1386 /// </summary> 1387 /// <param name="index">Index into the TryFunction delegates table.</param> 1388 /// <param name="function">TryFunction delegate to be registered.</param> 1389 [ExcludeFromBurstCompatTesting("Uses managed delegate")] 1390 public static void RegisterDelegate(int index, TryFunction function) 1391 { 1392 if(index >= MaxNumCustomAllocators) 1393 { 1394 throw new ArgumentException("index to be registered in TryFunction delegate table exceeds maximum."); 1395 } 1396 // Register TryFunction delegates for managed caller to avoid garbage collections 1397 Managed.TryFunctionDelegates[index] = function; 1398 } 1399 1400 /// <summary> 1401 /// Unregister TryFunction delegate 1402 /// </summary> 1403 /// <param name="int">Index into the TryFunction delegates table.</param> 1404 [ExcludeFromBurstCompatTesting("Uses managed delegate")] 1405 public static void UnregisterDelegate(int index) 1406 { 1407 if (index >= MaxNumCustomAllocators) 1408 { 1409 throw new ArgumentException("index to be unregistered in TryFunction delegate table exceeds maximum."); 1410 } 1411 Managed.TryFunctionDelegates[index] = default; 1412 } 1413 } 1414 1415 /// <summary> 1416 /// For internal use only. 1417 /// </summary> 1418 public static void Initialize() 1419 { 1420 } 1421 1422 /// <summary> 1423 /// Saves an allocator's function pointers at a particular index in the global function table. 1424 /// </summary> 1425 /// <param name="handle">The global function table index at which to install the allocator function.</param> 1426 /// <param name="allocatorState">IntPtr to allocator's custom state.</param> 1427 /// <param name="functionPointer">The allocator function to install in the global function table.</param> 1428 /// <param name="function">The allocator function to install in the global function table.</param> 1429 /// <param name="IsAutoDispose">Flag indicating if the allocator will automatically dispose allocations.</param> 1430 internal static void Install(AllocatorHandle handle, 1431 IntPtr allocatorState, 1432 FunctionPointer<TryFunction> functionPointer, 1433 TryFunction function, 1434 bool IsAutoDispose = false) 1435 { 1436 if(functionPointer.Value == IntPtr.Zero) 1437 handle.Unregister(); 1438 else 1439 { 1440 int error = ConcurrentMask.TryAllocate(ref SharedStatics.IsInstalled.Ref.Data, handle.Value, 1); 1441 if (ConcurrentMask.Succeeded(error)) 1442 { 1443 handle.Install(new TableEntry { state = allocatorState, function = functionPointer.Value }); 1444 Managed.RegisterDelegate(handle.Index, function); 1445 1446 // If the allocator will automatically dispose allocations. 1447 if (IsAutoDispose) 1448 { 1449 ConcurrentMask.TryAllocate(ref SharedStatics.IsAutoDispose.Ref.Data, handle.Value, 1); 1450 } 1451 } 1452 } 1453 } 1454 1455 /// <summary> 1456 /// Saves an allocator's function pointers at a particular index in the global function table. 1457 /// </summary> 1458 /// <param name="handle">The global function table index at which to install the allocator function.</param> 1459 /// <param name="allocatorState">IntPtr to allocator's custom state.</param> 1460 /// <param name="function">The allocator function to install in the global function table.</param> 1461 internal static void Install(AllocatorHandle handle, IntPtr allocatorState, TryFunction function) 1462 { 1463 var functionPointer = (function == null) 1464 ? new FunctionPointer<TryFunction>(IntPtr.Zero) 1465 : BurstCompiler.CompileFunctionPointer(function); 1466 Install(handle, allocatorState, functionPointer, function); 1467 } 1468 1469 /// <summary> 1470 /// Saves an allocator's function pointers in a free slot of the global function table. Thread safe. 1471 /// </summary> 1472 /// <param name="allocatorState">IntPtr to allocator's custom state.</param> 1473 /// <param name="functionPointer">Function pointer to create or save in the function table.</param> 1474 /// <param name="IsAutoDispose">Flag indicating if the allocator will automatically dispose allocations.</param> 1475 /// <param name="isGlobal">Flag indicating if the allocator is a global allocator.</param> 1476 /// <param name="globalIndex">Index into the global function table of the allocator to be created.</param> 1477 /// <returns>Returns a handle to the newly registered allocator function.</returns> 1478 internal static AllocatorHandle Register(IntPtr allocatorState, 1479 FunctionPointer<TryFunction> functionPointer, 1480 bool IsAutoDispose = false, 1481 bool isGlobal = false, 1482 int globalIndex = 0) 1483 { 1484 int error; 1485 int offset; 1486 if (isGlobal) 1487 { 1488 if (globalIndex < GlobalAllocatorBaseIndex) 1489 { 1490 throw new ArgumentException($"Error: {globalIndex} is less than GlobalAllocatorBaseIndex"); 1491 } 1492 error = ConcurrentMask.TryAllocate(ref SharedStatics.IsInstalled.Ref.Data, globalIndex, 1); 1493 offset = globalIndex; 1494 } 1495 else 1496 { 1497 error = ConcurrentMask.TryAllocate(ref SharedStatics.IsInstalled.Ref.Data, out offset, (FirstUserIndex + 63) >> 6, (int)(GlobalAllocatorBaseIndex - 1), 1); 1498 } 1499 var tableEntry = new TableEntry { state = allocatorState, function = functionPointer.Value }; 1500 AllocatorHandle handle = default; 1501 if(ConcurrentMask.Succeeded(error)) 1502 { 1503 handle.Index = (ushort)offset; 1504 handle.Install(tableEntry); 1505 1506 // If the allocator will automatically dispose allocations. 1507 if (IsAutoDispose) 1508 { 1509 ConcurrentMask.TryAllocate(ref SharedStatics.IsAutoDispose.Ref.Data, offset, 1); 1510 } 1511 1512#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG 1513 handle.Version = handle.OfficialVersion; 1514#endif 1515 } 1516 return handle; 1517 } 1518 1519 static class AllocatorCache<T> where T : unmanaged, IAllocator 1520 { 1521 public static FunctionPointer<TryFunction> TryFunction; 1522 public static TryFunction CachedFunction; 1523 } 1524 1525 /// <summary> 1526 /// Saves an allocator's function pointers in a free slot of the global function table. Thread safe. 1527 /// </summary> 1528 /// <typeparam name="T">The type of allocator to register.</typeparam> 1529 /// <param name="t">Reference to the allocator.</param> 1530 /// <param name="IsAutoDispose">Flag indicating if the allocator will automatically dispose allocations.</param> 1531 /// <param name="isGlobal">Flag indicating if the allocator is a global allocator.</param> 1532 /// <param name="globalIndex">Index into the global function table of the allocator to be created.</param> 1533 [ExcludeFromBurstCompatTesting("Uses managed delegate")] 1534 public static unsafe void Register<T>(ref this T t, bool IsAutoDispose = false, bool isGlobal = false, int globalIndex = 0) where T : unmanaged, IAllocator 1535 { 1536 FunctionPointer<TryFunction> functionPointer; 1537 var func = t.Function; 1538 if (func == null) 1539 functionPointer = new FunctionPointer<TryFunction>(IntPtr.Zero); 1540 else 1541 { 1542 if (func != AllocatorCache<T>.CachedFunction) 1543 { 1544 AllocatorCache<T>.TryFunction = BurstCompiler.CompileFunctionPointer(func); 1545 AllocatorCache<T>.CachedFunction = func; 1546 } 1547 functionPointer = AllocatorCache<T>.TryFunction; 1548 } 1549 t.Handle = Register((IntPtr)UnsafeUtility.AddressOf(ref t), functionPointer, IsAutoDispose, isGlobal, globalIndex); 1550 1551 Managed.RegisterDelegate(t.Handle.Index, t.Function); 1552 1553#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG 1554 if (!t.Handle.IsValid) 1555 throw new InvalidOperationException("Allocator registration succeeded, but failed to produce valid handle."); 1556#endif 1557 } 1558 1559 /// <summary> 1560 /// Removes an allocator's function pointers from the global function table, without managed code 1561 /// </summary> 1562 /// <typeparam name="T">The type of allocator to unregister.</typeparam> 1563 /// <param name="t">Reference to the allocator.</param> 1564 public static void UnmanagedUnregister<T>(ref this T t) where T : unmanaged, IAllocator 1565 { 1566 if(t.Handle.IsInstalled) 1567 { 1568 t.Handle.Install(default); 1569 ConcurrentMask.TryFree(ref SharedStatics.IsInstalled.Ref.Data, t.Handle.Value, 1); 1570 ConcurrentMask.TryFree(ref SharedStatics.IsAutoDispose.Ref.Data, t.Handle.Value, 1); 1571 } 1572 } 1573 1574 /// <summary> 1575 /// Removes an allocator's function pointers from the global function table. 1576 /// </summary> 1577 /// <typeparam name="T">The type of allocator to unregister.</typeparam> 1578 /// <param name="t">Reference to the allocator.</param> 1579 [ExcludeFromBurstCompatTesting("Uses managed delegate")] 1580 public static void Unregister<T>(ref this T t) where T : unmanaged, IAllocator 1581 { 1582 if(t.Handle.IsInstalled) 1583 { 1584 t.Handle.Dispose(); 1585 ConcurrentMask.TryFree(ref SharedStatics.IsInstalled.Ref.Data, t.Handle.Value, 1); 1586 ConcurrentMask.TryFree(ref SharedStatics.IsAutoDispose.Ref.Data, t.Handle.Value, 1); 1587 Managed.UnregisterDelegate(t.Handle.Index); 1588 } 1589 } 1590 1591 /// <summary> 1592 /// Create a custom allocator by allocating a backing storage to store the allocator and then register it 1593 /// </summary> 1594 /// <typeparam name="T">The type of allocator to create.</typeparam> 1595 /// <param name="backingAllocator">Allocator used to allocate backing storage.</param> 1596 /// <param name="isGlobal">Flag indicating if the allocator is a global allocator.</param> 1597 /// <param name="globalIndex">Index into the global function table of the allocator to be created.</param> 1598 /// <returns>Returns reference to the newly created allocator.</returns> 1599 [ExcludeFromBurstCompatTesting("Register uses managed delegate")] 1600 internal static ref T CreateAllocator<T>(AllocatorHandle backingAllocator, bool isGlobal = false, int globalIndex = 0) 1601 where T : unmanaged, IAllocator 1602 { 1603 unsafe 1604 { 1605 var allocatorPtr = (T*)Memory.Unmanaged.Allocate(UnsafeUtility.SizeOf<T>(), 16, backingAllocator); 1606 *allocatorPtr = default; 1607 ref T allocator = ref UnsafeUtility.AsRef<T>(allocatorPtr); 1608 Register(ref allocator, allocatorPtr->IsAutoDispose, isGlobal, globalIndex); 1609 return ref allocator; 1610 } 1611 } 1612 1613 /// <summary> 1614 /// Destroy a custom allocator by unregistering the allocator and freeing its backing storage 1615 /// </summary> 1616 /// <typeparam name="T">The type of allocator to destroy.</typeparam> 1617 /// <param name="t">Reference to the allocator.</param> 1618 /// <param name="backingAllocator">Allocator used in allocating the backing storage.</param> 1619 [ExcludeFromBurstCompatTesting("Registration uses managed delegates")] 1620 internal static void DestroyAllocator<T>(ref this T t, AllocatorHandle backingAllocator) 1621 where T : unmanaged, IAllocator 1622 { 1623 Unregister(ref t); 1624 1625 unsafe 1626 { 1627 var allocatorPtr = UnsafeUtility.AddressOf<T>(ref t); 1628 Memory.Unmanaged.Free(allocatorPtr, backingAllocator); 1629 } 1630 } 1631 1632 /// <summary> 1633 /// For internal use only. 1634 /// </summary> 1635 public static void Shutdown() 1636 { 1637 } 1638 1639 /// <summary> 1640 /// Index in the global function table of the first user-defined allocator. 1641 /// </summary> 1642 /// <remarks>The indexes from 0 up to `FirstUserIndex` are reserved and so should not be used for your own allocators.</remarks> 1643 /// <value>Index in the global function table of the first user-defined allocator.</value> 1644 public const ushort FirstUserIndex = 64; 1645 1646 /// <summary> 1647 /// Maximum number of user-defined allocators. 1648 /// </summary> 1649 public const ushort MaxNumCustomAllocators = 32768; 1650 1651 /// <summary> 1652 /// Number of global scratchpad allocators reserved in the global function table. 1653 /// </summary> 1654 /// <remarks>Number of global scratchpad allocators reserved in the global function table. Make sure it is larger than or equals to the max number of jobs that can run at the same time.</remarks> 1655#if UNITY_2022_2_14F1_OR_NEWER 1656 internal static readonly ushort NumGlobalScratchAllocators = (ushort) (JobsUtility.ThreadIndexCount); 1657#else 1658 internal const ushort NumGlobalScratchAllocators = JobsUtility.MaxJobThreadCount + 1; 1659#endif 1660 1661 /// <summary> 1662 /// Max number of global allocators reserved in the global function table. 1663 /// </summary> 1664 /// <remarks>Max number of global allocators reserved in the global function table. Make sure it is larger than or equals to NumGlobalScratchAllocators.</remarks> 1665#if UNITY_2022_2_14F1_OR_NEWER 1666 internal static readonly ushort MaxNumGlobalAllocators = (ushort)(JobsUtility.ThreadIndexCount); 1667#else 1668 internal const ushort MaxNumGlobalAllocators = JobsUtility.MaxJobThreadCount + 1; 1669#endif 1670 1671 /// <summary> 1672 /// Base index in the global function table for global allocators. 1673 /// </summary> 1674 /// <remarks>The indexes from `GlobalAllocatorBaseIndex` up to `MaxNumCustomAllocators` are reserved which 1675 /// should not be used for your own allocators.</remarks> 1676 /// <value>Base index in the global function table for global allocators.</value> 1677 internal static readonly uint GlobalAllocatorBaseIndex = (uint)(MaxNumCustomAllocators - MaxNumGlobalAllocators); 1678 1679 /// <summary> 1680 /// Index in the global function table of the first global scratchpad allocator. 1681 /// </summary> 1682 /// <remarks>The indexes from `GlobalAllocatorBaseIndex` up to `NumGlobalScratchAllocators` are reserved for global scratchpad allocators.</remarks> 1683 /// <value>Index in the global function table of the first global scratchpad allocator.</value> 1684 internal static readonly uint FirstGlobalScratchpadAllocatorIndex = GlobalAllocatorBaseIndex; 1685 1686 internal static bool IsCustomAllocator(AllocatorHandle allocator) 1687 { 1688 return allocator.Index >= FirstUserIndex; 1689 } 1690 1691 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 1692 internal static void CheckFailedToAllocate(int error) 1693 { 1694 if (error != 0) 1695 throw new ArgumentException("failed to allocate"); 1696 } 1697 1698 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 1699 internal static void CheckFailedToFree(int error) 1700 { 1701 if (error != 0) 1702 throw new ArgumentException("failed to free"); 1703 } 1704 1705 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")] 1706 internal static void CheckValid(AllocatorHandle handle) 1707 { 1708#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG 1709 if (handle.IsValid == false) 1710 throw new ArgumentException("allocator handle is not valid."); 1711#endif 1712 } 1713 } 1714 1715 /// <summary> 1716 /// Provides a wrapper for custom allocator. 1717 /// </summary> 1718 /// <typeparam name="T">The type of the allocator.</typeparam> 1719 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(AllocatorManager.AllocatorHandle) })] 1720 public unsafe struct AllocatorHelper<T> : IDisposable 1721 where T : unmanaged, AllocatorManager.IAllocator 1722 { 1723 /// <summary> 1724 /// Pointer to a custom allocator. 1725 /// </summary> 1726 readonly T* m_allocator; 1727 1728 /// <summary> 1729 /// Allocator used to allocate backing storage of T. 1730 /// </summary> 1731 AllocatorManager.AllocatorHandle m_backingAllocator; 1732 1733 /// <summary> 1734 /// Get the custom allocator. 1735 /// </summary> 1736 public ref T Allocator => ref UnsafeUtility.AsRef<T>(m_allocator); 1737 1738 /// <summary> 1739 /// Allocate the custom allocator from backingAllocator and register it. 1740 /// </summary> 1741 /// <param name="backingAllocator">Allocator used to allocate backing storage.</param> 1742 /// <param name="isGlobal">Flag indicating if the allocator is a global allocator.</param> 1743 /// <param name="globalIndex">Index into the global function table of the allocator to be created.</param> 1744 [ExcludeFromBurstCompatTesting("CreateAllocator is unburstable")] 1745 public AllocatorHelper(AllocatorManager.AllocatorHandle backingAllocator, bool isGlobal = false, int globalIndex = 0) 1746 { 1747 ref var allocator = ref AllocatorManager.CreateAllocator<T>(backingAllocator, isGlobal, globalIndex); 1748 m_allocator = (T*)UnsafeUtility.AddressOf<T>(ref allocator); 1749 m_backingAllocator = backingAllocator; 1750 } 1751 1752 /// <summary> 1753 /// Dispose the custom allocator backing memory and unregister it. 1754 /// </summary> 1755 [ExcludeFromBurstCompatTesting("DestroyAllocator is unburstable")] 1756 public void Dispose() 1757 { 1758 ref var allocator = ref UnsafeUtility.AsRef<T>(m_allocator); 1759 AllocatorManager.DestroyAllocator(ref allocator, m_backingAllocator); 1760 } 1761 } 1762} 1763 1764#pragma warning restore 0649