Define a custom allocator
Define a custom alloctor for specific memory allocation needs.
Read time 17 minutesLast updated 6 days ago
You can use a custom allocator for specific memory allocation needs. To create a custom allocator, it must contain an allocator handle of type and implement the interface, . After you create a custom allocator, you need to register it in a global allocator table in .
AllocatorManager.AllocatorHandleAllocatorManager.IAllocatorAllocatorManagerAdd the AllocatorManager.AllocatorHandle type to the custom allocator
A custom allocator must contain an allocator handle of type . An allocator handle includes the following:
AllocatorManager.AllocatorHandle- : A 2 byte unsigned version number. Only the lower 15 bits are valid.
Version - : A 2 byte unsigned index of the global allocator table obtained during registration.
Index - Method to add a safety handle to the list of child safety handles of the allocator handle.
- Method to add a child allocator to the list of child allocators of the allocator handle.
- A rewind method to invalidate and unregister all the child allocators, invalidate all the child safety handles of the allocator handle, and increment the allocator handle' and
Version.OfficialVersion
Implement AllocatorManager.IAllocator interface
To define a custom allocator, you must implement the interface.
AllocatorManager.IAllocatorBecause implements , your custom allocator must implement the method.
AllocatorManager.IAllocatorIDisposableDisposeThe following is an example of how to set up the interface and its required properties except the and method:
IAllocatorTryAllocatorFunction// A custom allocator must implement AllocatorManager.IAllocator interface[BurstCompile(CompileSynchronously = true)]internal struct ExampleCustomAllocator : AllocatorManager.IAllocator{ // A custom allocator must contain AllocatorManager.AllocatorHandle AllocatorManager.AllocatorHandle m_handle; // Implement the Function property required by IAllocator interface public AllocatorManager.TryFunction Function => AllocatorFunction; // Implement the Handle property required by IAllocator interface public AllocatorManager.AllocatorHandle Handle { get { return m_handle; } set { m_handle = value; } } // Implement the ToAllocator property required by IAllocator interface public Allocator ToAllocator { get { return m_handle.ToAllocator; } } // Implement the IsCustomAllocator property required by IAllocator interface public bool IsCustomAllocator { get { return m_handle.IsCustomAllocator; } } // Implement the IsAutoDispose property required by IAllocator interface // Allocations made by this example allocator are not automatically disposed. // This implementation can be skipped because the default implementation of // this property is false. public bool IsAutoDispose { get { return false; } } // Implement the Dispose method required by IDisposable interface because // AllocatorManager.IAllocator implements IDisposable public void Dispose() { // Make sure no memory leaks Assert.AreEqual(0, m_allocationCount); m_handle.Dispose(); }}
The method tells a custom allocator how to allocate or deallocate memory. The following is an example of the method where a custom allocator allocates memory from , initializes the allocated memory with a user configured value, and increments an allocation count. The custom allocator also decrements the allocation count when deallocating the allocated memory.
TryTryAllocator.Persistentbyte m_initialValue; int m_allocationCount; public unsafe int Try(ref AllocatorManager.Block block) { int error = 0; if (block.Range.Pointer == IntPtr.Zero) { // Allocate memory from Allocator.Persistent and restore the original allocator AllocatorManager.AllocatorHandle tempAllocator = block.Range.Allocator; block.Range.Allocator = Allocator.Persistent; error = AllocatorManager.Try(ref block); block.Range.Allocator = tempAllocator; if (error != 0) return error; // If allocation succeeds, initialize the memory with the initial value and increment the allocation count if (block.Range.Pointer != IntPtr.Zero) { UnsafeUtility.MemSet((void*)block.Range.Pointer, m_initialValue, block.Bytes); Interlocked.Increment(ref m_allocationCount); } return 0; } else { // Deallocate memory from Allocator.Persistant and restore the original allocator AllocatorManager.AllocatorHandle tempAllocator = block.Range.Allocator; block.Range.Allocator = Allocator.Persistent; error = AllocatorManager.Try(ref block); block.Range.Allocator = tempAllocator; if (error != 0) return error; // If deallocation succeeds, decrement the allocation count if (block.Range.Pointer == IntPtr.Zero) { Interlocked.Decrement(ref m_allocationCount); } return 0; } } #endregion // allocator-custom-try #region allocator-custom-allocator-function // Implement the allocator function of delegate AllocatorManager.TryFunction that is // required when register the allocator on the global allocator table [BurstCompile] [MonoPInvokeCallback(typeof(AllocatorManager.TryFunction))] public static unsafe int AllocatorFunction(IntPtr customAllocatorPtr, ref AllocatorManager.Block block) { return ((ExampleCustomAllocator*)customAllocatorPtr)->Try(ref block); } #endregion // allocator-custom-allocator-function public byte InitialValue => m_initialValue; public int AllocationCount => m_allocationCount; public void Initialize(byte initialValue) { m_initialValue = initialValue; m_allocationCount = 0; }}#endregion // allocator-custom-example#region allocator-custom-user-struct// Example user structure that contains the custom allocatorinternal struct ExampleCustomAllocatorStruct{ AllocatorHelper<ExampleCustomAllocator> customAllocatorHelper; public ref ExampleCustomAllocator customAllocator => ref customAllocatorHelper.Allocator; // Create the example custom allocator void CreateCustomAllocator(AllocatorManager.AllocatorHandle backgroundAllocator, byte initialValue) { // Allocate the custom allocator from backgroundAllocator and register the allocator customAllocatorHelper = new AllocatorHelper<ExampleCustomAllocator>(backgroundAllocator); // Set the initial value to initialize the memory customAllocator.Initialize(initialValue); } #region allocator-custom-dispose // Dispose the custom allocator void DisposeCustomAllocator() { // Dispose the custom allocator customAllocator.Dispose(); // Unregister the custom allocator and dispose it customAllocatorHelper.Dispose(); } #endregion // allocator-custom-dispose public ExampleCustomAllocatorStruct(byte initialValue) { this = default; CreateCustomAllocator(Allocator.Persistent, initialValue); } public void Dispose() { DisposeCustomAllocator(); } #region allocator-custom-use // Sample code to use the custom allocator to allocate containers public void UseCustomAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList) { // Use custom allocator to allocate a native array and check initial value. nativeArray = CollectionHelper.CreateNativeArray<int, ExampleCustomAllocator>(100, ref customAllocator, NativeArrayOptions.UninitializedMemory); Assert.AreEqual(customAllocator.InitialValue, (byte)nativeArray[0] & 0xFF); nativeArray[0] = 0xFE; // Use custom allocator to allocate a native list and check initial value. nativeList = new NativeList<int>(customAllocator.Handle); for (int i = 0; i < 50; i++) { nativeList.Add(i); } unsafe { // Use custom allocator to allocate a byte buffer. byte* bytePtr = (byte*)AllocatorManager.Allocate(ref customAllocator, sizeof(byte), sizeof(byte), 10); Assert.AreEqual(customAllocator.InitialValue, bytePtr[0]); // Free the byte buffer. AllocatorManager.Free(customAllocator.ToAllocator, bytePtr, 10); } } #endregion // allocator-custom-use public int AllocationCount => customAllocator.AllocationCount; public void UseCustomAllocatorHandle(out NativeArray<int> nativeArray, out NativeList<int> nativeList) { // Use custom allocator to allocate a native array and check initial value. nativeArray = CollectionHelper.CreateNativeArray<int>(100, customAllocator.ToAllocator, NativeArrayOptions.UninitializedMemory); Assert.AreEqual(customAllocator.InitialValue, (byte)nativeArray[0] & 0xFF); nativeArray[0] = 0xFE; // Use custom allocator to allocate a native list and check initial value. nativeList = new NativeList<int>(customAllocator.Handle); for (int i = 0; i < 50; i++) { nativeList.Add(i); } unsafe { // Use custom allocator to allocate a byte buffer. byte* bytePtr = (byte*)AllocatorManager.Allocate(ref customAllocator, sizeof(byte), sizeof(byte), 10); Assert.AreEqual(customAllocator.InitialValue, bytePtr[0]); AllocatorManager.Free(customAllocator.ToAllocator, bytePtr, 10); } }}internal class ExampleCustomAllocatorStructUsage{ const int IntialValue = 0xAB; // Test code. [Test] public void UseCustomAllocator_Works() { ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); // Allocate native array and native list from the custom allocator exampleStruct.UseCustomAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList); // Able to access the native array and native list Assert.AreEqual(nativeArray[0], 0xFE); Assert.AreEqual(nativeList[10], 10); // Need to use CollectionHelper.DisposeNativeArray to dispose the native array from a custom allocator CollectionHelper.Dispose(nativeArray) ; // Dispose the native list nativeList.Dispose();#if ENABLE_UNITY_COLLECTIONS_CHECKS // Object disposed exception throws because nativeArray is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeArray[0] = 0xEF; }); // Object disposed exception throws because nativeList is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeList[10] = 0x10; });#endif // Check allocation count after dispose the native array and native list Assert.AreEqual(0, exampleStruct.AllocationCount); exampleStruct.Dispose(); } [Test] public void UseCustomAllocatorHandle_Works() { ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); // Allocate native array and native list from the custom allocator handle exampleStruct.UseCustomAllocatorHandle(out NativeArray<int> nativeArray, out NativeList<int> nativeList); // Able to access the native array and native list Assert.AreEqual(nativeArray[0], 0xFE); Assert.AreEqual(nativeList[10], 10); // Need to use CollectionHelper.DisposeNativeArray to dispose the native array from a custom allocator CollectionHelper.Dispose(nativeArray); nativeList.Dispose();#if ENABLE_UNITY_COLLECTIONS_CHECKS // Object disposed exception throws because nativeArray is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeArray[0] = 0xEF; }); // Object disposed exception throws because nativeList is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeList[10] = 0x10; });#endif Assert.AreEqual(0, exampleStruct.AllocationCount); exampleStruct.Dispose(); } [Test] public void CustomAllocatorHandle_MultiThreadWorks() { ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); var taskList = new List<Task>(); // create 128 native array with another threads for (var i = 0; i < 128; i++) { var task = Task.Run(() => { var nativeArray = CollectionHelper.CreateNativeArray<int, ExampleCustomAllocator>(100, ref exampleStruct.customAllocator, NativeArrayOptions.UninitializedMemory); CollectionHelper.Dispose(nativeArray); }); taskList.Add(task); } Task.WaitAll(taskList.ToArray()); exampleStruct.Dispose(); }}#endregion // allocator-custom-user-struct
Example method below shows an allocator function of the custom allocator.
AllocatorFunction// Implement the allocator function of delegate AllocatorManager.TryFunction that is // required when register the allocator on the global allocator table [BurstCompile] [MonoPInvokeCallback(typeof(AllocatorManager.TryFunction))] public static unsafe int AllocatorFunction(IntPtr customAllocatorPtr, ref AllocatorManager.Block block) { return ((ExampleCustomAllocator*)customAllocatorPtr)->Try(ref block); } #endregion // allocator-custom-allocator-function public byte InitialValue => m_initialValue; public int AllocationCount => m_allocationCount; public void Initialize(byte initialValue) { m_initialValue = initialValue; m_allocationCount = 0; }}#endregion // allocator-custom-example#region allocator-custom-user-struct// Example user structure that contains the custom allocatorinternal struct ExampleCustomAllocatorStruct{ AllocatorHelper<ExampleCustomAllocator> customAllocatorHelper; public ref ExampleCustomAllocator customAllocator => ref customAllocatorHelper.Allocator; // Create the example custom allocator void CreateCustomAllocator(AllocatorManager.AllocatorHandle backgroundAllocator, byte initialValue) { // Allocate the custom allocator from backgroundAllocator and register the allocator customAllocatorHelper = new AllocatorHelper<ExampleCustomAllocator>(backgroundAllocator); // Set the initial value to initialize the memory customAllocator.Initialize(initialValue); } #region allocator-custom-dispose // Dispose the custom allocator void DisposeCustomAllocator() { // Dispose the custom allocator customAllocator.Dispose(); // Unregister the custom allocator and dispose it customAllocatorHelper.Dispose(); } #endregion // allocator-custom-dispose public ExampleCustomAllocatorStruct(byte initialValue) { this = default; CreateCustomAllocator(Allocator.Persistent, initialValue); } public void Dispose() { DisposeCustomAllocator(); } #region allocator-custom-use // Sample code to use the custom allocator to allocate containers public void UseCustomAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList) { // Use custom allocator to allocate a native array and check initial value. nativeArray = CollectionHelper.CreateNativeArray<int, ExampleCustomAllocator>(100, ref customAllocator, NativeArrayOptions.UninitializedMemory); Assert.AreEqual(customAllocator.InitialValue, (byte)nativeArray[0] & 0xFF); nativeArray[0] = 0xFE; // Use custom allocator to allocate a native list and check initial value. nativeList = new NativeList<int>(customAllocator.Handle); for (int i = 0; i < 50; i++) { nativeList.Add(i); } unsafe { // Use custom allocator to allocate a byte buffer. byte* bytePtr = (byte*)AllocatorManager.Allocate(ref customAllocator, sizeof(byte), sizeof(byte), 10); Assert.AreEqual(customAllocator.InitialValue, bytePtr[0]); // Free the byte buffer. AllocatorManager.Free(customAllocator.ToAllocator, bytePtr, 10); } } #endregion // allocator-custom-use public int AllocationCount => customAllocator.AllocationCount; public void UseCustomAllocatorHandle(out NativeArray<int> nativeArray, out NativeList<int> nativeList) { // Use custom allocator to allocate a native array and check initial value. nativeArray = CollectionHelper.CreateNativeArray<int>(100, customAllocator.ToAllocator, NativeArrayOptions.UninitializedMemory); Assert.AreEqual(customAllocator.InitialValue, (byte)nativeArray[0] & 0xFF); nativeArray[0] = 0xFE; // Use custom allocator to allocate a native list and check initial value. nativeList = new NativeList<int>(customAllocator.Handle); for (int i = 0; i < 50; i++) { nativeList.Add(i); } unsafe { // Use custom allocator to allocate a byte buffer. byte* bytePtr = (byte*)AllocatorManager.Allocate(ref customAllocator, sizeof(byte), sizeof(byte), 10); Assert.AreEqual(customAllocator.InitialValue, bytePtr[0]); AllocatorManager.Free(customAllocator.ToAllocator, bytePtr, 10); } }}internal class ExampleCustomAllocatorStructUsage{ const int IntialValue = 0xAB; // Test code. [Test] public void UseCustomAllocator_Works() { ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); // Allocate native array and native list from the custom allocator exampleStruct.UseCustomAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList); // Able to access the native array and native list Assert.AreEqual(nativeArray[0], 0xFE); Assert.AreEqual(nativeList[10], 10); // Need to use CollectionHelper.DisposeNativeArray to dispose the native array from a custom allocator CollectionHelper.Dispose(nativeArray) ; // Dispose the native list nativeList.Dispose();#if ENABLE_UNITY_COLLECTIONS_CHECKS // Object disposed exception throws because nativeArray is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeArray[0] = 0xEF; }); // Object disposed exception throws because nativeList is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeList[10] = 0x10; });#endif // Check allocation count after dispose the native array and native list Assert.AreEqual(0, exampleStruct.AllocationCount); exampleStruct.Dispose(); } [Test] public void UseCustomAllocatorHandle_Works() { ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); // Allocate native array and native list from the custom allocator handle exampleStruct.UseCustomAllocatorHandle(out NativeArray<int> nativeArray, out NativeList<int> nativeList); // Able to access the native array and native list Assert.AreEqual(nativeArray[0], 0xFE); Assert.AreEqual(nativeList[10], 10); // Need to use CollectionHelper.DisposeNativeArray to dispose the native array from a custom allocator CollectionHelper.Dispose(nativeArray); nativeList.Dispose();#if ENABLE_UNITY_COLLECTIONS_CHECKS // Object disposed exception throws because nativeArray is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeArray[0] = 0xEF; }); // Object disposed exception throws because nativeList is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeList[10] = 0x10; });#endif Assert.AreEqual(0, exampleStruct.AllocationCount); exampleStruct.Dispose(); } [Test] public void CustomAllocatorHandle_MultiThreadWorks() { ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); var taskList = new List<Task>(); // create 128 native array with another threads for (var i = 0; i < 128; i++) { var task = Task.Run(() => { var nativeArray = CollectionHelper.CreateNativeArray<int, ExampleCustomAllocator>(100, ref exampleStruct.customAllocator, NativeArrayOptions.UninitializedMemory); CollectionHelper.Dispose(nativeArray); }); taskList.Add(task); } Task.WaitAll(taskList.ToArray()); exampleStruct.Dispose(); }}#endregion // allocator-custom-user-struct
Global allocator table
The global allocator table in stores all the necessary information for custom allocators to work. When you instantiate a custom allocator, you must register the allocator in the global allocator table. The table stores the following information:
AllocatorManager- A pointer to the custom allocator instance.
- A pointer to the allocator function of the custom allocator instance.
- The current official version of the custom allocator instance, stored in the lower 15 bits of a 2 byte unsigned integer value.
- A list of child safety handles of native containers that are created using the custom allocator instance.
- A list of child allocators that are allocated using the custom allocator instance.
- A bit flag indicating whether the custom allocator is able to dispose individual allocations.
Custom allocator example
The following is an example of a custom allocator that has an and initializes the allocated memory with a user-configured value and increments the allocation count. It also uses to register the allocator on the global allocator table:
AllocatorManager.AllocatorHandleAllocatorManager.TryFunctionusing System;using AOT;using System.Collections.Generic;using System.Threading.Tasks;using NUnit.Framework;using Unity.Collections;using Unity.Collections.LowLevel.Unsafe;using Unity.Burst;using System.Threading;// Example custom allocator. The allocator can allocate memory from Allocator.Persistant, and// if successful, initializes the allocated memory with a user configured value and increments an// allocation count. The allocator can also deallocate memory and, if successful, decrements// the allocation count.[BurstCompile(CompileSynchronously = true)]internal struct ExampleCustomAllocator : AllocatorManager.IAllocator{ AllocatorManager.AllocatorHandle m_handle; public AllocatorManager.TryFunction Function => AllocatorFunction; public AllocatorManager.AllocatorHandle Handle { get { return m_handle; } set { m_handle = value; } } public Allocator ToAllocator { get { return m_handle.ToAllocator; } } public bool IsCustomAllocator { get { return m_handle.IsCustomAllocator; } } public bool IsAutoDispose { get { return false; } } public void Dispose() { // Make sure no memory leaks Assert.AreEqual(0, m_allocationCount); m_handle.Dispose(); } #region allocator-custom-try byte m_initialValue; int m_allocationCount; public unsafe int Try(ref AllocatorManager.Block block) { int error = 0; if (block.Range.Pointer == IntPtr.Zero) { // Allocate memory from Allocator.Persistent and restore the original allocator AllocatorManager.AllocatorHandle tempAllocator = block.Range.Allocator; block.Range.Allocator = Allocator.Persistent; error = AllocatorManager.Try(ref block); block.Range.Allocator = tempAllocator; if (error != 0) return error; // If allocation succeeds, initialize the memory with the initial value and increment the allocation count if (block.Range.Pointer != IntPtr.Zero) { UnsafeUtility.MemSet((void*)block.Range.Pointer, m_initialValue, block.Bytes); Interlocked.Increment(ref m_allocationCount); } return 0; } else { // Deallocate memory from Allocator.Persistant and restore the original allocator AllocatorManager.AllocatorHandle tempAllocator = block.Range.Allocator; block.Range.Allocator = Allocator.Persistent; error = AllocatorManager.Try(ref block); block.Range.Allocator = tempAllocator; if (error != 0) return error; // If deallocation succeeds, decrement the allocation count if (block.Range.Pointer == IntPtr.Zero) { Interlocked.Decrement(ref m_allocationCount); } return 0; } } #endregion // allocator-custom-try #region allocator-custom-allocator-function // Implement the allocator function of delegate AllocatorManager.TryFunction that is // required when register the allocator on the global allocator table [BurstCompile] [MonoPInvokeCallback(typeof(AllocatorManager.TryFunction))] public static unsafe int AllocatorFunction(IntPtr customAllocatorPtr, ref AllocatorManager.Block block) { return ((ExampleCustomAllocator*)customAllocatorPtr)->Try(ref block); } #endregion // allocator-custom-allocator-function public byte InitialValue => m_initialValue; public int AllocationCount => m_allocationCount; public void Initialize(byte initialValue) { m_initialValue = initialValue; m_allocationCount = 0; }}#endregion // allocator-custom-example#region allocator-custom-user-struct// Example user structure that contains the custom allocatorinternal struct ExampleCustomAllocatorStruct{ AllocatorHelper<ExampleCustomAllocator> customAllocatorHelper; public ref ExampleCustomAllocator customAllocator => ref customAllocatorHelper.Allocator; // Create the example custom allocator void CreateCustomAllocator(AllocatorManager.AllocatorHandle backgroundAllocator, byte initialValue) { // Allocate the custom allocator from backgroundAllocator and register the allocator customAllocatorHelper = new AllocatorHelper<ExampleCustomAllocator>(backgroundAllocator); // Set the initial value to initialize the memory customAllocator.Initialize(initialValue); } #region allocator-custom-dispose // Dispose the custom allocator void DisposeCustomAllocator() { // Dispose the custom allocator customAllocator.Dispose(); // Unregister the custom allocator and dispose it customAllocatorHelper.Dispose(); } #endregion // allocator-custom-dispose public ExampleCustomAllocatorStruct(byte initialValue) { this = default; CreateCustomAllocator(Allocator.Persistent, initialValue); } public void Dispose() { DisposeCustomAllocator(); } #region allocator-custom-use // Sample code to use the custom allocator to allocate containers public void UseCustomAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList) { // Use custom allocator to allocate a native array and check initial value. nativeArray = CollectionHelper.CreateNativeArray<int, ExampleCustomAllocator>(100, ref customAllocator, NativeArrayOptions.UninitializedMemory); Assert.AreEqual(customAllocator.InitialValue, (byte)nativeArray[0] & 0xFF); nativeArray[0] = 0xFE; // Use custom allocator to allocate a native list and check initial value. nativeList = new NativeList<int>(customAllocator.Handle); for (int i = 0; i < 50; i++) { nativeList.Add(i); } unsafe { // Use custom allocator to allocate a byte buffer. byte* bytePtr = (byte*)AllocatorManager.Allocate(ref customAllocator, sizeof(byte), sizeof(byte), 10); Assert.AreEqual(customAllocator.InitialValue, bytePtr[0]); // Free the byte buffer. AllocatorManager.Free(customAllocator.ToAllocator, bytePtr, 10); } } #endregion // allocator-custom-use public int AllocationCount => customAllocator.AllocationCount; public void UseCustomAllocatorHandle(out NativeArray<int> nativeArray, out NativeList<int> nativeList) { // Use custom allocator to allocate a native array and check initial value. nativeArray = CollectionHelper.CreateNativeArray<int>(100, customAllocator.ToAllocator, NativeArrayOptions.UninitializedMemory); Assert.AreEqual(customAllocator.InitialValue, (byte)nativeArray[0] & 0xFF); nativeArray[0] = 0xFE; // Use custom allocator to allocate a native list and check initial value. nativeList = new NativeList<int>(customAllocator.Handle); for (int i = 0; i < 50; i++) { nativeList.Add(i); } unsafe { // Use custom allocator to allocate a byte buffer. byte* bytePtr = (byte*)AllocatorManager.Allocate(ref customAllocator, sizeof(byte), sizeof(byte), 10); Assert.AreEqual(customAllocator.InitialValue, bytePtr[0]); AllocatorManager.Free(customAllocator.ToAllocator, bytePtr, 10); } }}internal class ExampleCustomAllocatorStructUsage{ const int IntialValue = 0xAB; // Test code. [Test] public void UseCustomAllocator_Works() { ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); // Allocate native array and native list from the custom allocator exampleStruct.UseCustomAllocator(out NativeArray<int> nativeArray, out NativeList<int> nativeList); // Able to access the native array and native list Assert.AreEqual(nativeArray[0], 0xFE); Assert.AreEqual(nativeList[10], 10); // Need to use CollectionHelper.DisposeNativeArray to dispose the native array from a custom allocator CollectionHelper.Dispose(nativeArray) ; // Dispose the native list nativeList.Dispose();#if ENABLE_UNITY_COLLECTIONS_CHECKS // Object disposed exception throws because nativeArray is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeArray[0] = 0xEF; }); // Object disposed exception throws because nativeList is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeList[10] = 0x10; });#endif // Check allocation count after dispose the native array and native list Assert.AreEqual(0, exampleStruct.AllocationCount); exampleStruct.Dispose(); } [Test] public void UseCustomAllocatorHandle_Works() { ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); // Allocate native array and native list from the custom allocator handle exampleStruct.UseCustomAllocatorHandle(out NativeArray<int> nativeArray, out NativeList<int> nativeList); // Able to access the native array and native list Assert.AreEqual(nativeArray[0], 0xFE); Assert.AreEqual(nativeList[10], 10); // Need to use CollectionHelper.DisposeNativeArray to dispose the native array from a custom allocator CollectionHelper.Dispose(nativeArray); nativeList.Dispose();#if ENABLE_UNITY_COLLECTIONS_CHECKS // Object disposed exception throws because nativeArray is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeArray[0] = 0xEF; }); // Object disposed exception throws because nativeList is already disposed Assert.Throws<ObjectDisposedException>(() => { nativeList[10] = 0x10; });#endif Assert.AreEqual(0, exampleStruct.AllocationCount); exampleStruct.Dispose(); } [Test] public void CustomAllocatorHandle_MultiThreadWorks() { ExampleCustomAllocatorStruct exampleStruct = new ExampleCustomAllocatorStruct(IntialValue); var taskList = new List<Task>(); // create 128 native array with another threads for (var i = 0; i < 128; i++) { var task = Task.Run(() => { var nativeArray = CollectionHelper.CreateNativeArray<int, ExampleCustomAllocator>(100, ref exampleStruct.customAllocator, NativeArrayOptions.UninitializedMemory); CollectionHelper.Dispose(nativeArray); }); taskList.Add(task); } Task.WaitAll(taskList.ToArray()); exampleStruct.Dispose(); }}#endregion // allocator-custom-user-struct