docs.unity3d.com
Search Results for

    Show / Hide Table of Contents

    Custom allocator overview

    You can use a custom allocator for specific memory allocation needs. To create a custom allocator, it must contain an allocator handle of type AllocatorManager.AllocatorHandle and implement the interface, AllocatorManager.IAllocator. After you create a custom allocator, you need to register it in a global allocator table in AllocatorManager.

    Add the AllocatorManager.AllocatorHandle type to the custom allocator

    A custom allocator must contain an allocator handle of type AllocatorManager.AllocatorHandle. An allocator handle includes the following:

    • Version: A 2 byte unsigned version number. Only the lower 15 bits are valid.
    • Index: A 2 byte unsigned index of the global allocator table obtained during registration.
    • 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' Version and OfficialVersion.

    Implement AllocatorManager.IAllocator interface

    To define a custom allocator, you must implement the interface AllocatorManager.IAllocator which includes:

    • Function: A property that gets the allocator function of delegate TryFunction. The allocator function can allocate, deallocate, and reallocate memory.
    • Try: A method that the allocator function invokes to allocate, deallocate, or reallocate memory.
    • Handle: A property that gets and sets the allocator handle which is of type AllocatorManager.AllocatorHandle.
    • ToAllocator: A property that casts the allocator handle index to the enum Allocator.
    • IsCustomAllocator: A property that checks whether the allocator is a custom allocator. An allocator is a custom allocator if its handle Index is larger or equal to AllocatorManager.FirstUserIndex.
    • IsAutoDispose: A property that checks whether the allocator is able to dispose individual allocations. True if disposing an individual allocation is a no-op.

    Because AllocatorManager.IAllocator implements IDisposable, your custom allocator must implement the Dispose method.

    The following is an example of how to set up the IAllocator interface and its required properties except the Try and AllocatorFunction method:

    // 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 Try method tells a custom allocator how to allocate or deallocate memory. The following is an example of theTry method where a custom allocator allocates memory from Allocator.Persistant, 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.

    // 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);
                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)
            {
                m_allocationCount--;
            }
    
            return 0;
        }
    }
    
    

    Example method AllocatorFunction below shows an allocator function of the custom allocator.

    // 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);
    }
    
    

    Global allocator table

    The global allocator table in AllocatorManager 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:

    • 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, 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 AllocatorManager.AllocatorHandle and initializes the allocated memory with a user configured value and increments the allocation count. It also uses AllocatorManager.TryFunction to register the allocator on the global allocator table:

    using System;
    using AOT;
    using NUnit.Framework;
    using Unity.Collections;
    using Unity.Collections.LowLevel.Unsafe;
    using Unity.Burst;
    
    // 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();
        }
    
        // 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);
                    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)
                {
                    m_allocationCount--;
                }
    
                return 0;
            }
        }
    
    
        // 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);
        }
    
    
        // 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;
        }
    }
    
    

    Further information

    • Use a custom allocator
    In This Article
    • Add the AllocatorManager.AllocatorHandle type to the custom allocator
    • Implement AllocatorManager.IAllocator interface
    • Global allocator table
    • Custom allocator example
    • Further information
    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)