Use a custom allocator
Once you've defined a custom allocator, you can add it to your structure or class.
Declare and create a custom allocator
The first step is to declare and create the custom allocator. You must do the following:
- Allocate memory to hold the custom allocator
- Register the allocator by adding an entry in a global allocator table
- Initialize the allocator if necessary.
The wrapper AllocatorHelper
helps the process in creating a custom allocator. Examples are given below as how to declare and create a custom allocator defined in the Example custom allocator.
// Example user structure that contains the custom allocator
internal struct ExampleCustomAllocatorStruct
{
// Use AllocatorHelper to help creating the example custom alloctor
AllocatorHelper<ExampleCustomAllocator> customAllocatorHelper;
// Custom allocator property for accessibility
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);
}
}
Use a custom allocator to allocate memory
For Native-
collection types, allocation from a custom allocator is similar to a classic allocator, except you must use CollectionHelper.CreateNativeArray
to create a NativeArray
from a custom allocator and CollectionHelper.Dispose
to deallocate a NativeArray
from a custom allocator.
For Unsafe-
collection types, you must use AllocatorManager.Allocate
to allocate memory from a custom allocator and AllocatorManager.Free
to deallocate the memory.
When you use a custom allocator to create a Native-
collection type, its safety handle is added to the list of child safety handles of the custom allocator. When you rewind the allocator handle of a custom allocator, it invalidates and unregisters all its child allocators, and invalidates all its child safety handles. For Native-
collection types, the disposal safety checks throw an exception if the allocator handle has rewound.
The following example method UseCustomAllocator
shows how to use a custom allocator to create and allocate native containers:
// 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);
}
}
Dispose a custom allocator
To dispose a custom allocator, the following must happen:
- The custom allocator must rewind its allocator handle which invalidates and unregisters all the allocator handle's child allocators, and invalidates all its child safety handles.
- You must unregister the allocator
- You must dispose the memory used to store the allocator.
Example method DisposeCustomAllocator
in the user structure shows how to dispose a custom allocator.
// Dispose the custom allocator
void DisposeCustomAllocator()
{
// Dispose the custom allocator
customAllocator.Dispose();
// Unregister the custom allocator and dispose it
customAllocatorHelper.Dispose();
}
Full example of a custom allocator
The following is a full example of how to use a custom allocator:
// Example user structure that contains the custom allocator
internal struct ExampleCustomAllocatorStruct
{
// Use AllocatorHelper to help creating the example custom alloctor
AllocatorHelper<ExampleCustomAllocator> customAllocatorHelper;
// Custom allocator property for accessibility
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);
}
// Dispose the custom allocator
void DisposeCustomAllocator()
{
// Dispose the custom allocator
customAllocator.Dispose();
// Unregister the custom allocator and dispose it
customAllocatorHelper.Dispose();
}
// Constructor of user structure
public ExampleCustomAllocatorStruct(byte initialValue)
{
this = default;
CreateCustomAllocator(Allocator.Persistent, initialValue);
}
// Dispose the user structure
public void Dispose()
{
DisposeCustomAllocator();
}
// 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);
}
}
// Get allocation count from the custom allocator
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]);
// Free the byte buffer.
AllocatorManager.Free(customAllocator.ToAllocator, bytePtr, 10);
}
}
}
internal class ExampleCustomAllocatorStructUsage
{
// Initial value for the custom allocator.
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);
// Dispose the user structure
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);
// 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);
// Dispose the user structure
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();
}
}