ECS的组件(Component)是常用的一类,他的固定大小造就了ECS系统能够高速的运转,但是我们有时是需要动态可变数组的。今天我们来看看动态缓冲组件(Dynamic Buffer Components)
声明方式
[InternalBufferCapacity(16)]
public struct MyElement : IBufferElementData
{
public int Value;
}
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;
在ForEach中使用
Entities.ForEach((in DynamicBuffer<MyElement> myBuff) => {
for (int i = 0; i < myBuff.Length; i++)
{
// ... read myBuff[i]
}
}).Schedule();
如果要修改就改成ref。
Entities.ForEach((ref DynamicBuffer<MyElement> myBuff) => {
for (int i = 0; i < myBuff.Length; i++)
{
myBuff.Add(new MyElement(){ Value = 1});
}
}).Schedule();
结构更改使 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
通过 BufferFromEntity 随机查找缓冲区
如果一个实体的所有实体都Entities.ForEach需要相同的缓冲区,则可以将该缓冲区捕获为主线程上的局部变量:
var myBuff = EntityManager.GetBuffer<MyElement>(someEntity);
Entities.ForEach((in SomeComp someComp) => {
// ... use myBuff
}).Schedule();
并行记录
如果使用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();
使用动态缓冲区修改
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 });
获取块(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++)
{
// ...
}
}
重新解释缓冲区
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
该Reinterpret方法仅强制原始类型和新类型具有相同的大小。例如,您可以将 a 重新解释uint为 a,float因为这两种类型都是 32 位的。您有责任决定重新解释是否对您的目的有意义。
本文基本和官方文档一致,增加了一些个人的翻译和看法。
看不懂的可以直接看原文。
引用:
官方文档