Dynamic buffer components | Entities | 0.10.0-preview.6
docs.unity3d.com
    Show / Hide Table of Contents

    Dynamic buffer components

    Use dynamic buffer components to associate array-like data with an entity. Dynamic buffers are ECS components that can hold a variable number of elements, and automatically resize as necessary.

    To create a dynamic buffer, first declare a struct that implements IBufferElementData and defines the elements stored in the buffer. For example, you can use the following struct for a buffer component that stores integers:

    
    public struct IntBufferElement : IBufferElementData
    {
        public int Value;
    }
    
    

    To associate a dynamic buffer with an entity, add an IBufferElementData component directly to the entity rather than adding the dynamic buffer container itself.

    ECS manages the container. For most purposes, you can use a declared IBufferElementData type to treat a dynamic buffer the same as any other ECS component. For example, you can use the IBufferElementData type in entity queries as well as when you add or remove the buffer component. However, you must use different functions to access a buffer component and those functions provide the DynamicBuffer instance, which gives an array-like interface to the buffer data.

    To specify an “internal capacity" for a dynamic buffer component, use the InternalBufferCapacity attribute. The internal capacity defines the number of elements the dynamic buffer stores in the ArchetypeChunk along with the other components of an entity. If you increase the size of a buffer beyond the internal capacity, the buffer allocates a heap memory block outside the current chunk and moves all existing elements. ECS manages this external buffer memory automatically, and frees the memory when the buffer component is removed.

    Note: If the data in a buffer is not dynamic, you can use a blob asset instead of a dynamic buffer. Blob assets can store structured data, including arrays. Multiple entities can share blob assets.

    Declaring buffer element types

    To declare a buffer, declare a struct that defines the type of element that you want to put into the buffer. The struct must implement IBufferElementData, like so:

    
    // InternalBufferCapacity specifies how many elements a buffer can have before
    // the buffer storage is moved outside the chunk.
    [InternalBufferCapacity(8)]
    public struct MyBufferElement : IBufferElementData
    {
        // Actual value each buffer element will store.
        public int Value;
    
        // The following implicit conversions are optional, but can be convenient.
        public static implicit operator int(MyBufferElement e)
        {
            return e.Value;
        }
    
        public static implicit operator MyBufferElement(int e)
        {
            return new MyBufferElement { Value = e };
        }
    }
    
    

    Adding buffer types to entities

    To add a buffer to an entity, add the IBufferElementData struct that defines the data type of the buffer element, and then add that type directly to an entity or to an archetype:

    Using EntityManager.AddBuffer()

    For more information, see the documentation on EntityManager.AddBuffer().

    
    EntityManager.AddBuffer<MyBufferElement>(entity);
    
    

    Using an archetype

    
    Entity e = EntityManager.CreateEntity(typeof(MyBufferElement));
    
    

    Using the [GenerateAuthoringComponent] attribute

    You can use [GenerateAuthoringComponent]to generate authoring components for simple IBufferElementData implementations that contain only one field. Setting this attribute allows you add an ECS IBufferElementData component to a GameObject so that you can set the buffer elements in the Editor.

    For example, if you declare the following type, you can add it directly to a GameObject in the Editor:

    [GenerateAuthoringComponent]
    public struct IntBufferElement: IBufferElementData
    {
        public int Value;
    }
    

    In the background, Unity generates a class named IntBufferElementAuthoring (which inherits from MonoBehaviour), which exposes a public field of List<int> type. When the GameObject containing this generated authoring component is converted into an entity, the list is converted into DynamicBuffer<IntBufferElement>, and then added to the converted entity.

    Note the following restrictions:

    • Only one component in a single C# file can have a generated authoring component, and the C# file must not have another MonoBehaviour in it.
    • IBufferElementData authoring components cannot be automatically generated for types that contain more than one field.
    • IBufferElementData authoring components cannot be automatically generated for types that have an explicit layout.

    Using an EntityCommandBuffer

    You can add or set a buffer component when you add commands to an entity command buffer.

    Use AddBuffer to create a new buffer for the entity, which changes the entity's archetype. Use SetBuffer to wipe out the existing buffer (which must exist) and create a new, empty buffer in its place. Both functions return a DynamicBuffer instance that you can use to populate the new buffer. You can add elements to the buffer immediately, but they are not otherwise accessible until the buffer is added to the entity when the command buffer is executed.

    The following job creates a new entity using a command buffer and then adds a dynamic buffer component using EntityCommandBuffer.AddBuffer. The job also adds a number of elements to the dynamic buffer.

    
    #pragma warning disable 618
            struct DataSpawnJob : IJobForEachWithEntity<DataToSpawn>
            {
                // A command buffer marshals structural changes to the data
                public EntityCommandBuffer.Concurrent CommandBuffer;
    
                //The DataToSpawn component tells us how many entities with buffers to create
                public void Execute(Entity spawnEntity, int index, [ReadOnly] ref DataToSpawn data)
                {
                    for (int e = 0; e < data.EntityCount; e++)
                    {
                        //Create a new entity for the command buffer
                        Entity newEntity = CommandBuffer.CreateEntity(index);
    
                        //Create the dynamic buffer and add it to the new entity
                        DynamicBuffer<MyBufferElement> buffer =
                            CommandBuffer.AddBuffer<MyBufferElement>(index, newEntity);
    
                        //Reinterpret to plain int buffer
                        DynamicBuffer<int> intBuffer = buffer.Reinterpret<int>();
    
                        //Optionally, populate the dynamic buffer
                        for (int j = 0; j < data.ElementCount; j++)
                        {
                            intBuffer.Add(j);
                        }
                    }
    
                    //Destroy the DataToSpawn entity since it has done its job
                    CommandBuffer.DestroyEntity(index, spawnEntity);
                }
            }
    #pragma warning restore 618
    
    

    Note: You are not required to add data to the dynamic buffer immediately. However, you won't have access to the buffer again until after the entity command buffer you are using is executed.

    Accessing buffers

    You can use EntityManager, systems, and jobs to access the DynamicBuffer instance in much the same way as you access other component types of entities.

    EntityManager

    You can use an instance of the EntityManager to access a dynamic buffer:

    
    DynamicBuffer<MyBufferElement> dynamicBuffer
        = EntityManager.GetBuffer<MyBufferElement>(entity);
    
    

    Looking up buffers of another entity

    When you need to look up the buffer data belonging to another entity in a job, you can pass a BufferFromEntity variable to the job.

    
    BufferFromEntity<MyBufferElement> lookup = GetBufferFromEntity<MyBufferElement>();
    var buffer = lookup[entity];
    buffer.Add(17);
    buffer.RemoveAt(0);
    
    

    SystemBase Entities.ForEach

    You can access dynamic buffers associated with the entities you process with Entities.ForEach by passing the buffer as one of your lambda function parameters. The following example adds all the values stored in the buffers of type, MyBufferElement:

    
    public class DynamicBufferSystem : SystemBase
    {
        protected override void OnUpdate()
        {
            var sum = 0;
    
            Entities.ForEach((DynamicBuffer<MyBufferElement> buffer) =>
            {
                foreach (var integer in buffer.Reinterpret<int>())
                {
                    sum += integer;
                }
            }).Run();
    
            Debug.Log("Sum of all buffers: " + sum);
        }
    }
    
    

    Note that we can write directly to the captured sum variable in this example because we execute the code with Run(). If we scheduled the function to run in a job, we could only write to a native container such as NativeArray, even though the result is a single value.

    IJobChunk

    To access an individual buffer in an IJobChunk job, pass the buffer data type to the job and use that to get a BufferAccessor. A buffer accessor is an array-like structure that provides access to all of the dynamic buffers in the current chunk.

    Like the previous example, the following example adds up the contents of all dynamic buffers that contain elements of type, MyBufferElement. IJobChunk jobs can also run in parallel on each chunk, so in the example, it first stores the intermediate sum for each buffer in a native array and then uses a second job to calculate the final sum. In this case, the intermediate array holds one result for each chunk, rather than one result for each entity.

    
    public class DynamicBufferJobSystem : SystemBase
    {
        private EntityQuery query;
    
        protected override void OnCreate()
        {
            //Create a query to find all entities with a dynamic buffer
            // containing MyBufferElement
            EntityQueryDesc queryDescription = new EntityQueryDesc();
            queryDescription.All = new[] {ComponentType.ReadOnly<MyBufferElement>()};
            query = GetEntityQuery(queryDescription);
        }
    
        public struct BuffersInChunks : IJobChunk
        {
            //The data type and safety object
            public ArchetypeChunkBufferType<MyBufferElement> BufferType;
    
            //An array to hold the output, intermediate sums
            public NativeArray<int> sums;
    
            public void Execute(ArchetypeChunk chunk,
                int chunkIndex,
                int firstEntityIndex)
            {
                //A buffer accessor is a list of all the buffers in the chunk
                BufferAccessor<MyBufferElement> buffers
                    = chunk.GetBufferAccessor(BufferType);
    
                for (int c = 0; c < chunk.Count; c++)
                {
                    //An individual dynamic buffer for a specific entity
                    DynamicBuffer<MyBufferElement> buffer = buffers[c];
                    foreach (MyBufferElement element in buffer)
                    {
                        sums[chunkIndex] += element.Value;
                    }
                }
            }
        }
    
        //Sums the intermediate results into the final total
        public struct SumResult : IJob
        {
            [DeallocateOnJobCompletion] public NativeArray<int> sums;
            public NativeArray<int> result;
            public void Execute()
            {
                foreach (int integer in sums)
                {
                    result[0] += integer;
                }
            }
        }
    
        protected override void OnUpdate()
        {
            //Create a native array to hold the intermediate sums
            int chunksInQuery = query.CalculateChunkCount();
            NativeArray<int> intermediateSums
                = new NativeArray<int>(chunksInQuery, Allocator.TempJob);
    
            //Schedule the first job to add all the buffer elements
            BuffersInChunks bufferJob = new BuffersInChunks();
            bufferJob.BufferType = GetArchetypeChunkBufferType<MyBufferElement>();
            bufferJob.sums = intermediateSums;
            this.Dependency = bufferJob.ScheduleParallel(query, this.Dependency);
    
            //Schedule the second job, which depends on the first
            SumResult finalSumJob = new SumResult();
            finalSumJob.sums = intermediateSums;
            NativeArray<int> finalSum = new NativeArray<int>(1, Allocator.Temp);
            finalSumJob.result = finalSum;
            this.Dependency = finalSumJob.Schedule(this.Dependency);
    
            this.CompleteDependency();
            Debug.Log("Sum of all buffers: " + finalSum[0]);
            finalSum.Dispose();
        }
    }
    
    

    Reinterpreting buffers

    Buffers can be reinterpreted as a type of the same size. The intention is to allow controlled type-punning and to get rid of the wrapper element types when they get in the way. To reinterpret, call Reinterpret<T>:

    
    DynamicBuffer<int> intBuffer
        = EntityManager.GetBuffer<MyBufferElement>(entity).Reinterpret<int>();
    
    

    The reinterpreted buffer instance retains the safety handle of the original buffer, and is safe to use. Reinterpreted buffers reference original data, so modifications to one reinterpreted buffer are immediately reflected in others.

    Note: The reinterpret function only enforces that the types involved have the same length. For example, you can alias a uint and float buffer without raising an error because both types are 32-bits long. You must make sure that the reinterpretation makes sense logically.

    Buffer reference invalidation

    Every structural change invalidates all references to dynamic buffers. Structural changes generally cause entities to move from one chunk to another. Small dynamic buffers can reference memory within a chunk (as opposed to from main memory) and therefore, they need to be reacquired after a structural change.

    
    var entity1 = EntityManager.CreateEntity();
    var entity2 = EntityManager.CreateEntity();
    
    DynamicBuffer<MyBufferElement> buffer1
        = EntityManager.AddBuffer<MyBufferElement>(entity1);
    // This line causes a structural change and invalidates
    // the previously acquired dynamic buffer
    DynamicBuffer<MyBufferElement> buffer2
        = EntityManager.AddBuffer<MyBufferElement>(entity1);
    // This line will cause an error:
    buffer1.Add(17);
    
    
    Back to top
    Copyright © 2023 Unity Technologies — Terms of use
    • Legal
    • Privacy Policy
    • Cookies
    • Do Not Sell or Share My Personal Information
    • Your Privacy Choices (Cookie Settings)
    "Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
    Generated by DocFX on 18 October 2023