123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353 |
- #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<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);
- }
-
- #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<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
-
- // 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();
- }
- }
- #endregion // allocator-custom-user-struct
-
|