zoukankan      html  css  js  c++  java
  • 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);

     

  • 相关阅读:
    Azure 认知服务 (3) 计算机视觉API
    Azure 认知服务 (2) 计算机视觉API
    Azure 认知服务 (1) 概述
    Azure PowerShell (13) 批量设置Azure ARM Network Security Group (NSG)
    Azure SQL Database (22) Azure SQL Database支持中文值
    HighCharts设置图表背景透明
    跨域资源共享(CORS)--跨域ajax
    "Ext 4.1 Grid 'el.dom' 为空或不是对象"问题的解决
    Ant编译utf-8非法字符:/65279 解决方法
    lvs 隧道模式请求没有回应的解决
  • 原文地址:https://www.cnblogs.com/sifenkesi/p/12383465.html
Copyright © 2011-2022 走看看