docs.unity3d.com
Search Results for

    Show / Hide Table of Contents

    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();
        }
    }
    
    In This Article
    Back to top
    Copyright © 2024 Unity Technologies — Trademarks and terms of use
    • Legal
    • Privacy Policy
    • Cookie Policy
    • Do Not Sell or Share My Personal Information
    • Your Privacy Choices (Cookie Settings)