天天看點

在ECS系統中使用IJobChunk作業使用IJobChunk擴充閱讀

洪流學堂,讓你快人幾步。你好,我是跟着大智學Unity的萌新,我叫小新,最近在跟着大智學習DOTS。

你可以在系統内部實作IJobChunk,用于逐塊周遊資料。當你在系統的

OnUpdate()

中安排IJobChunk作業時,該作業為每個符合entity查詢條件的chunk調用一次

Execute()

。然後,你可以周遊每個chunk中的entity上的資料。

使用IJobChunk

與Entities.ForEach相比,使用IJobChunk進行疊代需要更多的代碼,但是也更直接。

按塊進行疊代的另一個好處是,你可以使用

Archetype.Has<T>()

來檢查每個塊中是否存在可選元件,然後相應地處理塊中的所有entity。

實作IJobChunk作業的步驟如下:

  1. 建立一個

    EntityQuery

    來辨別要處理的entity。
  2. 定義job結構體,并包含

    ArchetypeChunkComponentType

    對象的字段,這些對象辨別job必須直接通路的元件的類型。另外,指定作業是隻讀還是寫入這些元件。
  3. 執行個體化作業并在系統

    OnUpdate()

    方法中安排作業。
  4. Execute()

    函數中,擷取

    NativeArray

    作業讀取或寫入的元件的執行個體,然後在目前塊上進行周遊以執行所需的工作。

後面會有執行個體示範如何使用

IJobChunk

使用EntityQuery查詢資料

EntityQuery定義原型必須包含的一組元件類型,系統才能處理其關聯的塊和實體。原型中可以有其他元件,但是它必須至少有EntityQuery定義的元件。你還可以排除包含特定類型元件的原型。

對于簡單查詢,可以使用

SystemBase.GetEntityQuery()

函數并按如下所示傳入元件類型:

public class RotationSpeedSystem : SystemBase
{
    private EntityQuery m_Query;

    protected override void OnCreate()
    {
        m_Query = GetEntityQuery(ComponentType.ReadOnly<Rotation>(),
            ComponentType.ReadOnly<RotationSpeed>());
        //...
    }

           

對于更複雜的情況,你可以使用

EntityQueryDesc

EntityQueryDesc

提供了靈活的查詢機制,以指定的元件類型:

  • All

    :此數組中的所有元件類型必須存在于原型中
  • Any

    :原型中必須至少存在此數組中的一種元件類型
  • None

    :原型中不能存在此數組中的任何元件類型

例如,以下查詢包括包含

RotationQuaternion

RotationSpeed

元件的原型,但不包括包含

Frozen

元件的任何原型:

protected override void OnCreate()
{
    var queryDescription = new EntityQueryDesc()
    {
        None = new ComponentType[]
        {
            typeof(Static)
        },
        All = new ComponentType[]
        {
            ComponentType.ReadWrite<Rotation>(),
            ComponentType.ReadOnly<RotationSpeed>()
        }
    };
    m_Query = GetEntityQuery(queryDescription);
}

           

查詢時可以使用

ComponentType.ReadOnly<T>

而不是更簡單的

typeof

表達式來指定系統不會寫入

RotationSpeed

你還可以組合多個查詢,傳入

EntityQueryDesc

對象數組而不是單個執行個體。ECS使用邏輯或運算來組合每個查詢。下面的示例選擇包含

RotationQuaternion

RotationSpeed

元件(或兩者同時都有)的所有原型:

protected override void OnCreate()
{
    var queryDescription0 = new EntityQueryDesc
    {
        All = new ComponentType[] {typeof(Rotation)}
    };

    var queryDescription1 = new EntityQueryDesc
    {
        All = new ComponentType[] {typeof(RotationSpeed)}
    };

    m_Query = GetEntityQuery(new EntityQueryDesc[] {queryDescription0, queryDescription1});
}
           

**注意:**請勿在

EntityQueryDesc

中包含可選元件。要處理可選元件,在

IJobChunk.Execute()

使用

chunk.Has<T>()

内部方法檢查目前ArchetypeChunk是否有可選元件。因為同一塊中的所有實體都具有相同的元件,是以你隻需要一個塊檢查一次即可,不用每個實體檢查一次。

為了提高效率并避免建立不必要地垃圾收集的引用類型,應在系統

OnCreate()

方法中建立

EntityQueries

,然後将結果存儲在執行個體變量中。(在上面示例中,

m_Query

變量就是這個用途)

定義IJobChunk結構體

IJobChunk結構體中定義了作業運作時需要的資料以及作業的

Execute()

方法。

要通路系統傳給

Execute()

方法的塊内的元件數組,必須為作業讀取或寫入的每種類型的元件建立一個

ArchetypeChunkComponentType<T>

對象。你可以使用這些對象擷取

NativeArray

執行個體,通過這些

NativeArray

可以通路實體的元件。包括作業的

Execute()

方法讀取或寫入的EntityQuery中引用的所有元件。你還可以用

ArchetypeChunkComponentType

擷取未包含在EntityQuery中的可選元件類型。

在通路目前塊之前,必須檢查確定目前塊有可選元件。例如,HelloCube IJobChunk示例定義了一個作業結構體,該結構定義了兩個元件的

ArchetypeChunkComponentType<T>

變量,

RotationQuaternion

RotationSpeed

[BurstCompile]
struct RotationSpeedJob : IJobChunk
{
    public float DeltaTime;
    public ComponentTypeHandle<Rotation> RotationTypeHandle;
    [ReadOnly] public ComponentTypeHandle<RotationSpeed> RotationSpeedTypeHandle;

    public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
    {
        // ...
    }
}
           

系統為

OnUpdate()

函數中的這些變量指派,ECS在運作作業時會在

Execute()

方法内使用這些變量。

這個作業還使用Unity的delta時間為3D對象的旋轉設定動畫。該示例使用struct字段将delta值傳遞給

Execute()

方法。

編寫Execute方法

IJobChunk

Execute()

方法的簽名為:

public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
           

chunk

參數是記憶體塊的句柄,包含此作業的疊代時必須處理的實體群組件。因為塊隻能包含一個原型,是以塊中的所有實體都具有相同的元件。

使用

chunk

參數擷取元件的NativeArray執行個體:

var chunkRotations = chunk.GetNativeArray(RotationTypeHandle);
var chunkRotationSpeeds = chunk.GetNativeArray(RotationSpeedTypeHandle);

           

這些數組是對齊的,同個實體在所有數組中具有相同的索引。可以使用正常的for循環來周遊元件數組。使用

chunk.Count

得到存儲在目前塊的實體的數量:

var chunkRotations = chunk.GetNativeArray(RotationTypeHandle);
var chunkRotationSpeeds = chunk.GetNativeArray(RotationSpeedTypeHandle);
for (var i = 0; i < chunk.Count; i++)
{
    var rotation = chunkRotations[i];
    var rotationSpeed = chunkRotationSpeeds[i];

    // Rotate something about its up vector at the speed given by RotationSpeed.
    chunkRotations[i] = new Rotation
    {
        Value = math.mul(math.normalize(rotation.Value),
            quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * DeltaTime))
    };
}

           

如果在EntityQueryDesc中有

Any

過濾器,或者可選的元件沒有寫在查詢中,則可以在使用之前用

ArchetypeChunk.Has<T>()

函數測試目前塊是否包含這些元件:

if (chunk.Has<OptionalComp>(OptionalCompType))
{//...}
           

**注意:**如果使用并發的entity command buffer,将

chunkIndex

參數作為

sortKey

參數傳遞給指令緩沖區函數。

跳過沒有變化的實體的塊

如果僅在元件值發生更改時才需要更新實體,可以将該元件類型添加到EntityQuery的更改篩選器中。例如,如果你的系統讀取兩個元件,并且僅在前兩個元件中的一個已更改時才需要更新第三個元件,則可以按以下方式使用EntityQuery:

private EntityQuery m_Query;

protected override void OnCreate()
{
    m_Query = GetEntityQuery(
        ComponentType.ReadWrite<Output>(),
        ComponentType.ReadOnly<InputA>(),
        ComponentType.ReadOnly<InputB>());
    m_Query.SetChangedVersionFilter(
        new ComponentType[]
        {
            ComponentType.ReadWrite<InputA>(),
            ComponentType.ReadWrite<InputB>()
        });
}

           

EntityQuery更改過濾器最多支援兩個元件。如果你想進行更多檢查或不使用EntityQuery,則可以手動進行檢查。要進行這個檢查,可以使用

ArchetypeChunk.DidChange()

函數将元件的塊的更改版本與系統的

LastSystemVersion

進行比較。如果此函數傳回false,則可以完全跳過目前塊,因為自從上次系統運作以來,該類型的元件均未更改。

你必須使用一個struct字段将

LastSystemVersion

從系統傳遞到作業中,如下所示:

[BurstCompile]
struct UpdateJob : IJobChunk
{
    public ComponentTypeHandle<InputA> InputATypeHandle;
    public ComponentTypeHandle<InputB> InputBTypeHandle;
    [ReadOnly] public ComponentTypeHandle<Output> OutputTypeHandle;
    public uint LastSystemVersion;

    public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
    {
        var inputAChanged = chunk.DidChange(InputATypeHandle, LastSystemVersion);
        var inputBChanged = chunk.DidChange(InputBTypeHandle, LastSystemVersion);

        // If neither component changed, skip the current chunk
        if (!(inputAChanged || inputBChanged))
            return;

        var inputAs = chunk.GetNativeArray(InputATypeHandle);
        var inputBs = chunk.GetNativeArray(InputBTypeHandle);
        var outputs = chunk.GetNativeArray(OutputTypeHandle);

        for (var i = 0; i < outputs.Length; i++)
        {
            outputs[i] = new Output { Value = inputAs[i].Value + inputBs[i].Value };
        }
    }
}

           

與所有作業結構體字段一樣,在計劃作業之前,必須先指派:

protected override void OnUpdate()
{
    var job = new UpdateJob();

    job.LastSystemVersion = this.LastSystemVersion;

    job.InputATypeHandle = GetComponentTypeHandle<InputA>(true);
    job.InputBTypeHandle = GetComponentTypeHandle<InputB>(true);
    job.OutputTypeHandle = GetComponentTypeHandle<Output>(false);

    this.Dependency = job.ScheduleParallel(m_Query, this.Dependency);
}

           

**注意:**為了提高效率,更改版本适用于整個塊,而不是單個實體。如果另一個具有寫入該類型元件功能的作業通路了塊,ECS就會增加該元件的更改版本,并且

DidChange()

函數會傳回true。即使聲明對元件進行寫如通路的作業實際上并未更改元件的值,ECS也會增加更改版本。

執行個體化并安排作業

要運作IJobChunk作業,必須建立作業結構體的執行個體,給結構體字段指派,然後安排作業。當你在SystemBase的

OnUpdate()

方法中執行時,系統會将安排作業每幀運作一次。

protected override void OnUpdate()
{
    var job = new RotationSpeedJob()
    {
        RotationTypeHandle = GetComponentTypeHandle<Rotation>(false),
        RotationSpeedTypeHandle = GetComponentTypeHandle<RotationSpeed>(true),
        DeltaTime = Time.DeltaTime
    };
    this.Dependency =  job.ScheduleParallel(m_Query, this.Dependency);
}
           

調用

GetArchetypeChunkComponentType<T>()

函數設定元件類型變量時,確定将隻需要讀取不需要寫入的元件的

isReadOnly

參數設定為true,這些參數可能會對ECS架構安排作業的效率産生重大影響。這些通路模式的設定,需要在結構體定義和EntityQuery中比對。

不要在系統類變量中緩存

GetArchetypeChunkComponentType<T>()

傳回值。你必須在每次系統運作時調用這個函數,并将更新後的值傳給作業。

擴充閱讀

  • ECS中的Entity實體
  • ECS之Component元件
  • ECS之System系統
  • 在ECS系統中使用Entities.ForEach
  • 在ECS系統中使用Job.WithCode
【擴充學習】在洪流學堂公衆号回複

DOTS

可以閱讀本系列所有文章,更有視訊教程等着你!

呼~ 今天小新絮絮叨叨的真是夠夠的了。沒講清楚的地方歡迎評論,咱們一起探索。

我是大智(vx:zhz11235),你的技術探路者,下次見!

别走!點贊,收藏哦!

好,你可以走了。

繼續閱讀