• Unity ECS小知识2 - 动态缓冲组件(Dynamic Buffer Components)


    ECS的组件(Component)是常用的一类,他的固定大小造就了ECS系统能够高速的运转,但是我们有时是需要动态可变数组的。今天我们来看看动态缓冲组件(Dynamic Buffer Components)

    声明方式

    [InternalBufferCapacity(16)]
    public struct MyElement : IBufferElementData
    {
        public int Value;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    InternalBufferCapacity,让我们定义一般需要16个元素,如果省略默认128字节元素。(看官方文档解释是128个字节,取决于结构体有几个字节,没理解错的话,如果没有声明InternalBufferCapacity,这里是128/4=32个Capactiy。

    这里区别于普通组件,继承的是IBufferElementData。

    结构是非托管的,因此受到与结构IBufferElementData相同的约束IComponentData。DynamicBuffer结构仅代表一个单独的动态缓冲区,而不是组件类型。

    指针类型

    每个缓冲区存储了2个有用的指针类型:Length和Capacity。

    Length

    Length是逻辑长度,从0开始的,你可以通过Add添加,那么Length会增加1。

    Capacity

    Capacity是缓冲区的实际重量,单独设置他会调整缓冲区大小,如果你的数据超出了缓冲区大小,那么所有数据会被复制到一个更大数组中,指针会设置到该数组。

    使用方法

    //创建一个动态缓冲区的实体。
    EntityManager.CreateEntity(typeof(MyElement));    
    
    //给实体e添加一个动态数组。
    EntityManager.AddComponent<MyElement>(e);    
    
    //移除实体e的动态数组MyElement。
    EntityManager.RemoveComponent<MyElement>(e);
    
    //匹配实体的动态数组MyElement的查询。
    EntityQuery query = GetEntityQuery(typeof(MyElement));
    
    //获取实体e的动态数组
    DynamicBuffer<MyElement> myBuff = EntityManager.GetBuffer<MyElement>(e);
    
    //读取和写入长度5的数据,如果超出抛出异常。
    int x = myBuff[5].Value;
    myBuff[5] = new MyElement { Value = x + 1 };
    
    //在当前Length处增加一个新的数值,并增加Length,如果超过了Capacity大小,则缓冲区大小调整为Capacity的两倍。
    myBuff.Add(new MyElement { Value = 100 });
    
    //设置了可用索引为10(0-9)
    myBuff.Length = 10; 
    
    //设置数组大小,如果小于Length则抛出安全检查异常。
    myBuff.Capacity = 20;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在ForEach中使用

    	Entities.ForEach((in DynamicBuffer<MyElement> myBuff) => {
        for (int i = 0; i < myBuff.Length; i++)
        {
            // ... read myBuff[i]
        }
    }).Schedule();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果要修改就改成ref。

    	Entities.ForEach((ref DynamicBuffer<MyElement> myBuff) => {
        for (int i = 0; i < myBuff.Length; i++)
        {
            myBuff.Add(new MyElement(){ Value = 1});
        }
    }).Schedule();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    结构更改使 DynamicBuffer 无效

    DynamicBuffer<MyElement> myBuff = EntityManager.GetBuffer<MyElement>(e);
    
    //这里创建了新的Entity,这个结构改变使之前获得的myBuff 无效。
    EntityManager.CreateEntity();
    
    //进行读写操作这里可能引发异常。
    var x = myBuff[0];   // Exception!
    
    // 应当在这里重新获得myBuff
    myBuff = EntityManager.GetBuffer<MyElement>(e);
    var y = myBuff[0];   // OK
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    通过 BufferFromEntity 随机查找缓冲区

    如果一个实体的所有实体都Entities.ForEach需要相同的缓冲区,则可以将该缓冲区捕获为主线程上的局部变量:

    var myBuff = EntityManager.GetBuffer<MyElement>(someEntity);  
    
    Entities.ForEach((in SomeComp someComp) => {    
        // ... use myBuff
    }).Schedule();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    并行记录

    如果使用ScheduleParallel,请注意不能并行写入缓冲区。但是,您可以使用 EntityCommandBuffer.ParallelWriter来并行记录更改。

    OnUpdate中在ForEach中访问多个缓冲区

    如果Entities.ForEach需要在其代码中查找一个或多个缓冲区,则需要一个BufferFromEntity结构,它提供按实体对缓冲区的随机查找。

    // 在SystemBase类中的OnUpdate中
    BufferFromEntity<MyElement> lookup = GetBufferFromEntity<MyElement>();
    
    Entities.ForEach((in SomeComp someComp) => {
        // EntityManager不能再作业中使用,我所我们使用, 所以我们可以用这种方式
        DynamicBuffer<MyElement> myBuff = lookup[someComp.OtherEntity];
    
        // ... use myBuff
    }).Schedule();
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    使用动态缓冲区修改

    
    EntityCommandBuffer ecb = new EntityCommandBuffer(Allocator.TempJob);
    
    //移除e的MyElement
    ecb.RemoveComponent<MyElement>(e);
    
    //记录向现有实体添加MyElement动态缓冲区的命令。
    //返回的DynamicBuffer的数据存储在EntityCommandBuffer中,
    //因此对返回缓冲区的更改也会被记录下来。
    DynamicBuffer<MyElement> myBuff = ecb.AddBuffer<MyElement>(e); 
    
    //实体将有一个MyElement缓冲区
    //长度20和这些记录的值。
    myBuff.Length = 20;
    myBuff[0] = new MyElement { Value = 5 };
    myBuff[3] = new MyElement { Value = -9 };
    
    // SetBuffer类似于AddBuffer,但是安全检查将在回放时抛出异常,因为实体还没有MyElement缓冲区。Add过才能Set。
    DynamicBuffer<MyElement> otherBuf = ecb.SetBuffer<MyElement>(otherEntity);
    
    //记录一个要追加到缓冲区的MyElement值。抛出异常
    //实体还没有MyElement缓冲区。
    ecb.AppendToBuffer<MyElement>(otherEntity, new MyElement { Value = 12 });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    获取块(Chunk)的所有缓冲区

    // ... 假设一个带有MyElement动态缓冲区的块
    
    // 从SystemBase获取一个表示动态缓冲区类型MyElement的BufferTypeHandle
    BufferTypeHandle<MyElement> myElementHandle = GetBufferTypeHandle<MyElement>();
    
    // 从块中获取 BufferAccessor .
    BufferAccessor<MyElement> buffers = chunk.GetBufferAccessor(myElementHandle);
    
    //遍历区块中每个实体的所有MyElement缓冲区。
    for (int i = 0; i < chunk.Count; i++)
    {
        DynamicBuffer<MyElement> buffer = buffers[i];
    
        //遍历缓冲区中的所有元素。
        for (int i = 0; i < buffer.Length; i++)
        {
            // ...
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    重新解释缓冲区

    ADynamicBuffer可以被“重新解释”,这样你就可以得到另一个DynamicBuffer, where T并且U具有相同的大小。这种重新解释对相同的内存进行了别名,因此更改i一个索引处的值会更改i另一个索引处的值:

    DynamicBuffer<MyElement> myBuff = EntityManager.GetBuffer<MyElement>(e);
    
    //只要每个MyElement结构体是四个字节就有效。
    DynamicBuffer<int> intBuffer = myBuff.Reinterpret<int>();
    
    intBuffer[2] = 6;  // 相同的效果: myBuff[2] = new MyElement { Value = 6 };
    
    // MyElement值与int值6具有相同的四个字节。
    MyElement myElement = myBuff[2];
    Debug.Log(myElement.Value);    // 6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    该Reinterpret方法仅强制原始类型和新类型具有相同的大小。例如,您可以将 a 重新解释uint为 a,float因为这两种类型都是 32 位的。您有责任决定重新解释是否对您的目的有意义。

    本文基本和官方文档一致,增加了一些个人的翻译和看法。
    看不懂的可以直接看原文。

    引用:
    官方文档

  • 相关阅读:
    【Python】9*9乘法口诀表(while、for两种循环)
    项目之旅(第三周)
    内网渗透之PTH&PTT&PTK(域控)
    基于Java+SpringBoot制作一个社区宠物登记小程序
    使用esxcli命令升级VMware ESXi补丁
    Mybatis中 list.size() = 1 但显示 All elements are null
    java酒店管理系统设计与实现计算机毕业设计MyBatis+系统+LW文档+源码+调试部署
    IntelliJ IDEA远程调试:使用IDEA Remote Debug进行高效调试的指南
    自动化测试工具Selenium & Appium
    AlexNet架构解析
  • 原文地址:https://blog.csdn.net/thinbug/article/details/125521625