天天看點

Unity ECS小知識2 - 動态緩沖元件(Dynamic Buffer Components)

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 位的。您有責任決定重新解釋是否對您的目的有意義。

本文基本和官方文檔一緻,增加了一些個人的翻譯和看法。

看不懂的可以直接看原文。

引用:

官方文檔