ECS:訪問Entity數據


ECS systems的主要任務就是讀取一系列的components數據,計算以后將結果寫入其他的components。那么如何高效地讀寫數據就是很重要的,而有效的方法就是利用cpu的多核特性來並行地讀取和處理數據。
 
ECS提供了多種完成這個任務的方式,每一種都有自己的規則和約束:
 
API
說明
JobComponentSystem Entities.ForEach
最簡單的方式
IJobForEach
相當於Entities.ForEach,但需要寫更多代碼
IJobForEachWithEntity
比IJobForEach稍微復雜點,提供了訪問entity handle和entity array index
IJobChunk
基於chunk的訪問
ComponentSystem.ForEach
main thread工作模式
Manual iteration
手動遍歷entities或chunks
EntityQuery:用來根據條件查詢entities,上述的各種訪問entities的方式,基本都顯示或隱式地依賴EntityQuery。WithStoreEntityQueryInField(ref EntityQuery)可用來訪問隱式EntityQuery對象。

JobComponentSystem lambda 函數

兩種方式的lambda表達式:
    JobComponentSystem.Entities.ForEach(lambda):針對每個entity的job
    JobComponentSystem.Job.WithCode(lambda):針對某一段邏輯代碼的job
 
函數
說明
WithAll<T>
全包含
WithAny<T, U>
包含任意
WithNone<T>
不包含
WithChangeFilter<T>
特定component變更時包含,和上一次update相比
目標component要么在參數列表中,要么在WithAny里面
一次最多同時支持2個components類型的變更
基於chunk級別的變更
如果有代碼以write的方式訪問chunk中的component,也會被標記為changed,不管是否實際改變任何數據
WithSharedComponentFilter
含有特定值的shared component
調用時傳入shared component object
WithStoreEntityQueryInField
可以將EntityQuery對象提前顯式地存在一個變量里面
在第JCS創建時賦值
第一次執行之前就可以使用了
可用來查詢entities的數量
重要:不能將ForEach參數列表里面的components類型,重復放在WithAll、WithAny、WithNone里面。
 
Entities.ForEach lambda函數參數列表:
(1)最多8個參數
(2)參數順序規則:
    a. 按值傳遞的參數(copy-by-value的方式拷貝)
    b. 可寫,ref參數
    c. 只讀,in參數
(3)所有的components參數要么是ref要么是in
(4)特殊固定名字參數:
    a. Entity entity
    b. int entityInQueryIndex
    c. int nativeThreadIndex,通過Run()函數手動執行lambda函數時,始終為0
 
Capturing variables 變量修飾:
 
函數名
說明
WithReadOnly(myvar)
只讀修飾
WithDeallocateOnJobCompletion(myvar)
釋放native container
WithNativeDisableParallelForRestriction(myvar)
保證多個線程訪問同一個可寫naive container。
並行訪問只有在每個線程都訪問自己獨有的數據(或native container中的某一段)時才是安全的。多個線程同時訪問同一個額數據會導致race condition。
WithNativeDisableContainerSafetyRestriction(myvar)
禁用訪問native container的安全限制。
WithNativeDisableUnsafePtrRestrictionAttribute(myvar)
允許使用unsafe指針。
Job選項:
JobHandle Scedule(JobHandle):以job方式執行lambda
    Entities.ForEach:在job threads上並行執行,每個job遍歷query到多chunks上的所有的entities。
    Job.WithCode:在一個job thread上執行一個lambda函數的實例。
void Run():主線程同步執行lambda函數,不以job的方式運行
    Entities.ForEach:lambda在每個query出來的chunks的entity上都執行一次。
    Job.WithCode:lambda函數執行一次。
WithBurst(FloatMode, FloatPrecision, bool):Burst編譯器參數
    floatMode:Default Deterministic Fase Strict
    floatPrecision:High Low Medium Standard
    synchronousCompilation:
WithoutBurst():關閉Burst編譯。當lambda表達式里面有不支持Burst的代碼時使用。
WithStructuralChanges():在主線程以關閉Burst的方式運行,這時候可以做一些structural changes的操作。建議使用EntityCommandBuffer來代替這種用法。
WithName(string):給job起名字,方便調試的時候用。
 
不能在job中對entity做結構性變更,比如創建entity、添加或移除component、銷毀entity等。正確的方式是使用ECB來緩存並延遲處理這些變更操作。

使用IJobForEach訪問Entity

IJobForEach以chunk為單位處理entities,同一個chunk中的所有entities當做一個批次來處理。當entities分布在不同的chunk中是,不同chunk的處理是並行的。這樣以chunk為單位的處理方式效率非常高,因為單個線程只處理自己的chunk中的數據,不會有多個線程同時處理一個chunk中的數據。
但是,如果沒一個entity的處理都非常耗時,就有可能會帶來性能問題,這種情況下可以使用IJobParallelFor來進行手動方式的遍歷。
 
定義:
    [ExcludeComponent(typeof(Frozen))]
    [RequireComponentTag(typeof(Gravity))]
    [BurstCompile]
    struct RotationSpeedJob : IJobForEach<RotationQuaternion, RotationSpeed>
    {}
模板參數表示需要操作的components。
屬性標簽:
 
標簽
說明
[ExcludeComponent(typeof(T))]
不包含
[RequireComponentTag(typeof(T))]
需要包含
[BurstCompile]
Burst編譯加速
執行函數Execute():
對於每個有效的entity,都會執行Execute()函數。
    public void Execute(ref RotationQuaternion rotationQuaternion, [ReadOnly] ref RotationSpeed rotSpeed){}
參數列表必須和類型的模板參數列表匹配,同時可以使用以下修飾符:
 
標簽
說明
[ReadOnly]
只讀
[WriteOnly]
只寫
[ChangedFilter]
chunks中的component變更時處理chunks中所有的entities,否則就完全不處理
IJobForEachWithEntity:
提供額外的參數:Entity對象和index。
可以使用提供得entity對象結合ECB,來操作entity或添加刪除components。

使用ComponentSystem訪問Entity

首選可以利用CPU多核特性的JobComponentSystem,而ComponentSystem一般只會用在以下幾種情況:
(1)調試或測試性開發;
(2)當需要調用只能在main thread上調用的一些API時使用;
(3)代碼的復雜甚至低於創建和調度一個job時使用;
(4)當需要在遍歷entities時直接執行一些結構性修改時,可以直接在ForEach中同步執行。
注意:在主線程的結構性修改會同步等待所有的jobs執行完成,所以會造成卡頓。這種情況下可以使用一個post-update command buffer,提前收集所有的修改操作,然后在某個時間點一次執行。
注意:ForEach lambda最多支持6個類型的components參數。

使用IJobChunk訪問Entity

JCS會針對每一個chunk執行一遍Execute()函數,然后在函數中逐個entity處理。
比IJobForEach代碼更復雜,需要更多代碼。但是要更加的明確和直接地訪問數據。
使用chun還有一個方便之處就是,可以在代碼中檢查optionl component是否存在,然后細化處理。
(1)創建EntityQuery
    使用JobComponentSystem.GetEntityQurey()來創建EnityQuery;
    一個system定義一次然后存成變量。
(2)定義IJobChunk
    ArchetyChunkComponentType<T>:每個需要訪問的component都需要定一個該類型對象,用來獲得T的列表數組。在OnUpdate()中每幀賦值,然后在Job.Execute()中使用。
    注意:ArchetyChunkComponentType<T>不能緩存,必須每幀在使用之前更新。
(3)Eexcute()執行函數    
    使用chunk.Has<T>來判斷是否包含某個可選的組件,每個chunk在一次執行中只需要check一次;
    基於版本號是否變更來決定是否執行job;
(4)實例化job、給字段賦值、並調度
public class TestSystem : JobComponentSystem
{
    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>()
            });
    }
 
 
    [BurstCompile]
    struct UpdateJob : IJobChunk
    {
        public ArchetypeChunkComponentType<InputA> InputAType;
        public ArchetypeChunkComponentType<InputB> InputBType;
        [ReadOnly] public ArchetypeChunkComponentType<Output> OutputType;
        public uint LastSystemVersion;
        public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
        {
            var inputAChanged = chunk.DidChange(InputAType, LastSystemVersion);
            var inputBChanged = chunk.DidChange(InputBType, LastSystemVersion);
            // If neither component changed, skip the current chunk
            if (!(inputAChanged || inputBChanged))
                return;
            var inputAs = chunk.GetNativeArray(InputAType);
            var inputBs = chunk.GetNativeArray(InputBType);
            var outputs = chunk.GetNativeArray(OutputType);
            for (var i = 0; i < outputs.Length; i++)
            {
                outputs[i] = new Output{ Value = inputAs[i].Value + inputBs[i].Value };
            }
        }
    }
 
    protected override JobHandle OnUpdate(JobHandle inputDependencies)
    {
        var job = new UpdateJob();
        job.LastSystemVersion = this.LastSystemVersion;
        job.InputAType = GetArchetypeChunkComponentType<InputA>(true);
        job.InputBType = GetArchetypeChunkComponentType<InputB>(true);
        job.OutputType = GetArchetypeChunkComponentType<Output>(false);
        return job.Schedule(m_Query, inputDependencies);
    }
}

手動迭代遍歷Entity

在JobComponentSystem中使用IJobParallelFor:
public class RotationSpeedSystem : JobComponentSystem
{
   [BurstCompile]
   struct RotationSpeedJob : IJobParallelFor
   {
       [DeallocateOnJobCompletion] public NativeArray<ArchetypeChunk> Chunks;
       public ArchetypeChunkComponentType<RotationQuaternion> RotationType;
       [ReadOnly] public ArchetypeChunkComponentType<RotationSpeed> RotationSpeedType;
       public float DeltaTime;
       public void Execute(int chunkIndex)
       {
           var chunk = Chunks[chunkIndex];
           var chunkRotation = chunk.GetNativeArray(RotationType);
           var chunkSpeed = chunk.GetNativeArray(RotationSpeedType);
           var instanceCount = chunk.Count;
           for (int i = 0; i < instanceCount; i++)
           {
               var rotation = chunkRotation[i];
               var speed = chunkSpeed[i];
               rotation.Value = math.mul(math.normalize(rotation.Value), quaternion.AxisAngle(math.up(), speed.RadiansPerSecond * DeltaTime));
               chunkRotation[i] = rotation;
           }
       }
   }
   EntityQuery m_Query;   
   protected override void OnCreate()
   {
       var queryDesc = new EntityQueryDesc
       {
           All = new ComponentType[]{ typeof(RotationQuaternion), ComponentType.ReadOnly<RotationSpeed>() }
       };
       m_Query = GetEntityQuery(queryDesc);
   }
   protected override JobHandle OnUpdate(JobHandle inputDeps)
   {
       var rotationType = GetArchetypeChunkComponentType<RotationQuaternion>();
       var rotationSpeedType = GetArchetypeChunkComponentType<RotationSpeed>(true);
       var chunks = m_Query.CreateArchetypeChunkArray(Allocator.TempJob);
       var rotationsSpeedJob = new RotationSpeedJob
       {
           Chunks = chunks,
           RotationType = rotationType,
           RotationSpeedType = rotationSpeedType,
           DeltaTime = Time.deltaTime
       };
       return rotationsSpeedJob.Schedule(chunks.Length,32,inputDeps);
   }
}
在ComponentSystem里面手動執行:
遍歷所有的entities:
    var entityManager = World.Active.EntityManager;
    var allEntities = entityManager.GetAllEntities();
    foreach (var entity in allEntities)
    {
       //...
    }
    allEntities.Dispose();
遍歷所有的chunks:
    var entityManager = World.Active.EntityManager;
    var allChunks = entityManager.GetAllChunks();
    foreach (var chunk in allChunks)
    {
       //...
    }
    allChunks.Dispose();

EntityQuery的用法

最簡單的方式:
在System內部創建:
    EntityQuery m_Query = GetEntityQuery(typeof(RotationQuaternion), ComponentType.ReadOnly<RotationSpeed>());
在System外部創建:
    EntityQuery m_Query = CreateEntityQuery(typeof(RotationQuaternion), ComponentType.ReadOnly<RotationSpeed>());
使用EntityQueryDesc:
    var query = new EntityQueryDesc
    {
       None = new ComponentType[]{ typeof(Frozen) },
       All = new ComponentType[]{ typeof(RotationQuaternion), ComponentType.ReadOnly<RotationSpeed>() }
       Options = EntityQueryDescOptions.FilterWriteGroup
    }
    EntityQuery m_Query = GetEntityQuery(query);
查詢選項:(一般不需要設置)
選項
說明
Deault
不設置
IncludePrefab
包含特定Prefab tag的compnent
IncludeDisable
包含特定Disabled tag的component
FilterWriteGroup
query時考慮component的WirteGroup設置
組合查詢:
    var query0 = new EntityQueryDesc
    {
       All = new ComponentType[] {typeof(RotationQuaternion)}
    };
    var query1 = new EntityQueryDesc
    {
       All = new ComponentType[] {typeof(RotationSpeed)}
    };
    // query0或query1
    EntityQuery m_Query = GetEntityQuery(new EntityQueryDesc[] {query0, query1});
使用filter:
(1)Shared component filters:篩選出擁有特定值的sharedcomponent的entities
    最多同時支持2個shared component filters
    struct SharedGrouping : ISharedComponentData
    {
        public int Group;
    }
    class ImpulseSystem : ComponentSystem
    {
        EntityQuery m_Query;
        protected override void OnCreate(int capacity)
        {
            m_Query = GetEntityQuery(typeof(Position), typeof(Displacement), typeof(SharedGrouping));
        }
        protected override void OnUpdate()
        {
            // Only iterate over entities that have the SharedGrouping data set to 1
            m_Query.SetFilter(new SharedGrouping { Group = 1 });
            var positions = m_Query.ToComponentDataArray<Position>(Allocator.Temp);
            var displacememnts = m_Query.ToComponentDataArray<Displacement>(Allocator.Temp);
            for (int i = 0; i != positions.Length; i++)
                positions[i].Value = positions[i].Value + displacememnts[i].Value;
        }
    }
(2)Change filters:
    protected override void OnCreate(int capacity)
    {
        m_Query = GetEntityQuery(typeof(LocalToWorld), ComponentType.ReadOnly<Translation>());
        m_Query.SetFilterChanged(typeof(Translation));
    }
使用查詢結果:
函數
說明
ToEntityArray()
轉為entities數組
ToComponentDataArray<T>()
轉為T的數組
CreateArchetypeChunkArray()
轉為Chunk數組
還可以直接將query傳給job.Shedule()函數:
    var job = new RotationSpeedJob()
    {
        RotationType = rotationType,
        RotationSpeedType = rotationSpeedType,
        DeltaTime = Time.deltaTime
    };
    return job.Schedule(m_Query, inputDependencies);

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM