#region allocator-custom-example using 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; // This is the example code used in // Packages/com.unity.collections/Documentation~/allocator/allocator-custom.md // Example custom allocator. The allocator is able to allocate memory from Allocator.Persistant, // if successful, initialize the allocated memory with a user configured value and increment an // allocation count. The allocator is able to deallocate the memory, if successful, decrement // the allocation count. // 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(); } #region allocator-custom-try // Value to initialize the allocated memory byte m_initialValue; // Allocation count int m_allocationCount; // Implement the Try method required by IAllocator interface public unsafe int Try(ref AllocatorManager.Block block) { // Error status int error = 0; // Allocate if (block.Range.Pointer == IntPtr.Zero) { // Allocate 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; // return if error occurs if (error != 0) return error; // if allocation succeeds, intialize 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; } // Deallocate 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; // return if error occurs 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(CompileSynchronously = true)] [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 // Property to get the initial value public byte InitialValue => m_initialValue; // Property to get the allocation count public int AllocationCount => m_allocationCount; // Initialize the allocator 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 allocator internal struct ExampleCustomAllocatorStruct { // Use AllocatorHelper to help creating the example custom alloctor AllocatorHelper 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(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 // Constructor of user structure public ExampleCustomAllocatorStruct(byte initialValue) { this = default; CreateCustomAllocator(Allocator.Persistent, initialValue); } // Dispose the user structure public void Dispose() { DisposeCustomAllocator(); } #region allocator-custom-use // Sample code to use the custom allocator to allocate containers public void UseCustomAllocator(out NativeArray nativeArray, out NativeList nativeList) { // Use custom allocator to allocate a native array and check initial value. nativeArray = CollectionHelper.CreateNativeArray(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(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 // Get allocation count from the custom allocator public int AllocationCount => customAllocator.AllocationCount; public void UseCustomAllocatorHandle(out NativeArray nativeArray, out NativeList nativeList) { // Use custom allocator to allocate a native array and check initial value. nativeArray = CollectionHelper.CreateNativeArray(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(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 nativeArray, out NativeList 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(() => { nativeArray[0] = 0xEF; }); // Object disposed exception throws because nativeList is already disposed Assert.Throws(() => { 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 nativeArray, out NativeList 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(() => { nativeArray[0] = 0xEF; }); // Object disposed exception throws because nativeList is already disposed Assert.Throws(() => { 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(); // create 128 native array with another threads for (var i = 0; i < 128; i++) { var task = Task.Run(() => { var nativeArray = CollectionHelper.CreateNativeArray(100, ref exampleStruct.customAllocator, NativeArrayOptions.UninitializedMemory); CollectionHelper.Dispose(nativeArray); }); taskList.Add(task); } Task.WaitAll(taskList.ToArray()); exampleStruct.Dispose(); } } #endregion // allocator-custom-user-struct