zoukankan      html  css  js  c++  java
  • 动态缓冲组件

    动态缓冲组件

    使用动态缓冲区组件将类数组数据与实体相关联。动态缓冲区是 ECS 组件,可以容纳可变数量的元素,并根据需要自动调整大小。

    要创建动态缓冲区,首先声明一个实现IBufferElementData并定义存储在缓冲区中的元素的结构例如,您可以将以下结构用于存储整数的缓冲区组件:

    
    public struct IntBufferElement : IBufferElementData
    {
        public int Value;
    }
    
    

    要将动态缓冲区与实体关联,请将IBufferElementData组件直接添加到实体,而不是添加动态缓冲区容器本身。

    ECS 管理容器。大多数情况下,您可以使用声明的IBufferElementData类型将动态缓冲区视为与任何其他 ECS 组件相同。例如,您可以IBufferElementData实体查询中以及在添加或删除缓冲区组件时使用该类型但是,您必须使用不同的函数来访问缓冲区组件,并且这些函数提供了DynamicBuffer实例,它为缓冲区数据提供了类似数组的接口。

    要为动态缓冲区组件指定“内部容量”,请使用InternalBufferCapacity 属性。内部容量定义动态缓冲区与实体的其他组件一起存储在ArchetypeChunk 中的元素数量。如果增加缓冲区的大小超出内部容量,buffer会在当前chunk之外分配一个堆内存块并移动所有存在的元素,ECS自动管理这个外部buffer memory,当buffer组件被移除时释放内存。

    笔记

    如果缓冲区中的数据不是动态的,则可以使用blob 资产而不是动态缓冲区。Blob 资产可以存储结构化数据,包括数组。多个实体可以共享 Blob 资产。

    声明缓冲区元素类型

    要声明缓冲区,请声明一个结构体,该结构体定义要放入缓冲区的元素类型。该结构必须实现IBufferElementData,如下所示:

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

    向实体添加缓冲区类型

    要将缓冲区添加到实体,请添加IBufferElementData定义缓冲区元素数据类型结构,然后将该类型直接添加到实体或原型

    使用 EntityManager.AddBuffer()

    有关更多信息,请参阅有关EntityManager.AddBuffer()的文档

    
    EntityManager.AddBuffer<MyBufferElement>(entity);
    
    

    使用原型

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

    使用[GenerateAuthoringComponent]属性

    您可以使用[GenerateAuthoringComponent]为仅包含一个字段的简单 IBufferElementData 实现生成创作组件。设置此属性允许您将 ECS IBufferElementData 组件添加到游戏对象,以便您可以在编辑器中设置缓冲区元素。

    例如,如果您声明以下类型,则可以将其直接添加到编辑器中的 GameObject 中:

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

    在后台,Unity 生成一个名为IntBufferElementAuthoring(继承自MonoBehaviour的类,该类公开了一个公共字段List<int>类型。当包含此生成的创作组件的 GameObject 转换为实体时,列表将转换为DynamicBuffer<IntBufferElement>,然后添加到转换后的实体中。

    请注意以下限制:

    • 单个 C# 文件中只有一个组件可以有一个生成的创作组件,并且 C# 文件中不能有另一个 MonoBehaviour。
    • IBufferElementData 无法为包含多个字段的类型自动生成创作组件。
    • IBufferElementData 无法为具有显式布局的类型自动生成创作组件。

    使用EntityCommandBuffer

    您可以在向实体命令缓冲区添加命令时添加或设置缓冲区组件。

    使用AddBuffer为实体创建一个新缓冲区,这会更改实体的原型。使用SetBuffer清除现有缓冲区(必须存在)并在其位置创建一个新的空缓冲区。这两个函数都返回一个DynamicBuffer实例,您可以使用它来填充新缓冲区。您可以立即将元素添加到缓冲区,但在执行命令缓冲区时将缓冲区添加到实体之前,它们无法以其他方式访问。

    以下作业使用命令缓冲区创建一个新实体,然后使用EntityCommandBuffer.AddBuffer添加动态缓冲区组件该作业还向动态缓冲区添加了许多元素。

    
    using Unity.Entities;
    using Unity.Jobs;
    
    public partial class CreateEntitiesWithBuffers : SystemBase
    {
        // A command buffer system executes command buffers in its own OnUpdate
        public EntityCommandBufferSystem CommandBufferSystem;
    
        protected override void OnCreate()
        {
            // Get the command buffer system
            CommandBufferSystem
                = World.DefaultGameObjectInjectionWorld.GetExistingSystem<EndSimulationEntityCommandBufferSystem>();
        }
    
        protected override void OnUpdate()
        {
            // The command buffer to record commands,
            // which are executed by the command buffer system later in the frame
            EntityCommandBuffer.ParallelWriter commandBuffer
                = CommandBufferSystem.CreateCommandBuffer().AsParallelWriter();
            //The DataToSpawn component tells us how many entities with buffers to create
            Entities.ForEach((Entity spawnEntity, int entityInQueryIndex, in DataToSpawn data) =>
            {
                for (int e = 0; e < data.EntityCount; e++)
                {
                    //Create a new entity for the command buffer
                    Entity newEntity = commandBuffer.CreateEntity(entityInQueryIndex);
    
                    //Create the dynamic buffer and add it to the new entity
                    DynamicBuffer<MyBufferElement> buffer =
                        commandBuffer.AddBuffer<MyBufferElement>(entityInQueryIndex, 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(entityInQueryIndex, spawnEntity);
            }).ScheduleParallel();
    
            CommandBufferSystem.AddJobHandleForProducer(this.Dependency);
        }
    }
    
    笔记

    您不需要立即将数据添加到动态缓冲区。但是,在执行您正在使用的实体命令缓冲区之前,您将无法再次访问该缓冲区。

    访问缓冲区

    您可以使用EntityManagersystems和 jobs 以与访问其他组件类型的实体大致相同的方式访问DynamicBuffer实例。

    实体管理器

    您可以使用EntityManager 的实例来访问动态缓冲区:

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

    查找另一个实体的缓冲区

    当您需要在作业中查找属于另一个实体的缓冲区数据时,您可以将BufferFromEntity变量传递给作业。

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

    SystemBase Entities.ForEach

    通过将缓冲区作为 lambda 函数参数之一传递,您可以访问与使用 Entities.ForEach 处理的实体关联的动态缓冲区。以下示例添加存储在类型为,的缓冲区中的所有值MyBufferElement

    
    public partial class DynamicBufferSystem : SystemBase
    {
        protected override void OnUpdate()
        {
            var sum = 0;
    
            Entities.ForEach((DynamicBuffer<MyBufferElement> buffer) =>
            {
                for(int i = 0; i < buffer.Length; i++)
                {
                    sum += buffer[i].Value;
                }
            }).Run();
    
            Debug.Log("Sum of all buffers: " + sum);
        }
    }
    
    

    请注意,sum在此示例中我们可以直接写入捕获的变量,因为我们使用Run()如果我们安排函数在作业中运行,我们只能写入本地容器,例如 NativeArray,即使结果是单个值。

    作业块

    要访问IJobChunk作业中的单个缓冲区,请将缓冲区数据类型传递给作业并使用它来获取BufferAccessor缓冲区访问器是一个类似数组的结构,它提供对当前块中所有动态缓冲区的访问。

    与前面的示例一样,以下示例将包含类型为 的元素的所有动态缓冲区的内容相加MyBufferElementIJobChunk作业也可以在每个块上并行运行,因此在示例中,它首先将每个缓冲区的中间总和存储在本机数组中,然后使用第二个作业来计算最终总和。在这种情况下,中间数组为每个块保存一个结果,而不是为每个实体保存一个结果。

    
    public partial 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 : IJobEntityBatch
        {
            //The data type and safety object
            public BufferTypeHandle<MyBufferElement> BufferTypeHandle;
    
            //An array to hold the output, intermediate sums
            public NativeArray<int> sums;
    
            public void Execute(ArchetypeChunk batchInChunk, int batchIndex)
            {
                //A buffer accessor is a list of all the buffers in the chunk
                BufferAccessor<MyBufferElement> buffers
                    = batchInChunk.GetBufferAccessor(BufferTypeHandle);
    
                for (int c = 0; c < batchInChunk.Count; c++)
                {
                    //An individual dynamic buffer for a specific entity
                    DynamicBuffer<MyBufferElement> buffer = buffers[c];
                    for(int i = 0; i < buffer.Length; i++)
                    {
                        sums[batchIndex] += buffer[i].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()
            {
                for(int i  = 0; i < sums.Length; i++)
                {
                    result[0] += sums[i];
                }
            }
        }
    
        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.BufferTypeHandle = GetBufferTypeHandle<MyBufferElement>();
            bufferJob.sums = intermediateSums;
            this.Dependency = bufferJob.ScheduleParallel(query, 1, 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();
        }
    }
    
    

    重新解释缓冲区

    缓冲区可以重新解释为相同大小的类型。目的是允许受控类型双关并在它们妨碍时摆脱包装元素类型。要重新解释,请调用Reinterpret<T>

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

    重新解释的缓冲区实例保留了原始缓冲区的安全句柄,可以安全使用。重新解释的缓冲区引用原始数据,因此对一个重新解释的缓冲区的修改会立即反映在其他缓冲区中。

    注意: reinterpret 函数仅强制所涉及的类型具有相同的长度。例如,您可以在不引发错误的情况下为auintfloatbuffer 设置别名因为这两种类型都是 32 位长。您必须确保重新解释在逻辑上有意义。

    缓冲区引用失效

    每次结构更改都会使对动态缓冲区的所有引用无效。结构变化通常会导致实体从一个块移动到另一个块。小的动态缓冲区可以引用块内的内存(与主内存相反),因此,它们需要在结构更改后重新获取。

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

     

  • 相关阅读:
    NOI Online 2020 提高组游记
    【HDU5840】This world need more Zhu
    CSP-S 2019 AFO记
    防错笔记
    关于Blog
    题解 【UER #6】逃跑
    动态规划杂题选记
    有趣计数题选做
    题解 [POI2012] Leveling Ground
    xioa han 带画家!
  • 原文地址:https://www.cnblogs.com/alps/p/15328238.html
Copyright © 2011-2022 走看看