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