笨木头  2019-12-05 14:24     ECS入门     阅读(6760)     评论(8)
转载请注明,原文地址: http://www.benmutou.com/archives/2801
文章来源:笨木头与游戏开发
来自2020.04.28的说明:

网友提示,0.9.1版本下的Entities已经放弃使用 IJobForEach,木头本人当时学习的是0.3版本,所以,理论上来说,本文已作废。
 

上一篇我们介绍了JobComponentSystem的基础用法,其实还有更多的用法,这次来介绍一下IJobForEach。

同样是用于筛选实体数据,执行一些逻辑操作,但是IJobForEach比起单纯地使用Entities.ForEach,在大部分情况下会有更优秀的性能。

1.IJobForEach

我们还是用之前的代码,但是,System类又要做一些改动:
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;

public class RotationSpeedSystem_ForEach : JobComponentSystem { struct RotationSpeedJob : IJobForEach<Rotation, RotationSpeed_ForEach> { public float dt;

public void Execute (ref Rotation rotation, [ReadOnly]ref RotationSpeed_ForEach rotationSpeed) { rotation.Value = math.mul( math.normalize(rotation.Value), quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * dt)); } }

protected override JobHandle OnUpdate (JobHandle inputDeps) { var job = new RotationSpeedJob() { dt = Time.DeltaTime }; return job.Schedule(this, inputDeps); } }
现在,OnUpdate里的逻辑都放到一个RotationSpeedJob里处理,然后通过调用这个Job的Schedule函数返回了一个JobHandle对象。和之前有点像,只不过是用一个称之为Job的东西把逻辑封装了。

 

RotationSpeedJob继承了IJobForEach接口,然后实现了Execute 函数。

Execute函数里做的事情和之前类似,修改实体的旋转值。

 

但是,这里有一点不一样,很重要的一点——Execute里没有使用Entities.ForEach了。

是的,每一个IJobForEach,只会处理一个实体的组件对象。

并且,这些Job是可以并行处理的,这就是JobComponentSystem配合IJobForEach的优势——并行。

我们来理一理:

a. JobComponentSystem的OnUpdate函数的主要逻辑移到了RotationSpeedJob中处理。

b. JobHandle对象需要通过IJobForEach来返回,实际上能返回JobHandle对象的不仅仅是IJobForEach这种接口,还有其他的,即,JobComponentSystem可以和若干种不同类型的Job配合。

c. IJobForEach接口需要实现Execute函数,函数参数指定了需要筛选的组件类型(可多个),以此来获得所需实体的组件对象,然后进行读写操作。指定的组件类型需要和接口声明的泛型对应,比如我们的接口声明的泛型是: IJobForEach<Rotation, RotationSpeed_ForEach>,和Execute函数的参数类型是对应的。

d. Execute函数只能对单个实体的组件(组件可以多个)进行操作。

e. IJobForEach,我们称之为Job,Job是可以并行执行的(多线程),因此能提升性能。

f. 请注意,Execute的参数可以带有ReadOnly特性,代表获取的这个类型的组件是只读的,不能进行写操作。

g. 如果某个Job对某个组件对象进行了写操作,则其他需要读取这个组件对象的Job无法并行执行,相当于锁住了。

关于JobSystem的知识,我现在还没有掌握,我会在后续的教程里和大家分享。

2.运行

好了,看看效果吧,除了System发生了改变,其他内容没变,大家修改了System类后,运行程序,结果是是一样的。

3.要注意的地方,重要!

利用IJobForEach时,系统会自动并行处理,但是,它只会按照块(Chunk)来并行处理。

 

什么是块(Chunk)?因为这是一个很重要的概念,我会在下一篇单独讲(重要,但理解起来很简单)。

 

虽然现在大家还没有块的概念,但是可以先注意一下。

如果我们的实体数量很少,筛选出来的实体刚好都在一个块里。

那么,IJobForEach其实是没有帮助的,因为它是按照块为单位进行并行处理的,只有一个块的话,也就不存在并行了。

 

大家看看下一篇关于块(Chunk)的解释,就能理解这段话的意思了。

 

注意,本系列教程基于DOTS相关预览版的Package包,是预览版,不代表正式版的时候也适用。

转发本系列文章的朋友请注意,带上原文链接和原文日期,避免误导未来使用正式版的开发者。
8 条评论
  • 啄木猫 2020-05-20 15:37:57

    终于看懂了,官方文档看的我炸毛
    0回复
  • dada_cheng 2020-04-28 15:02:15

    0.9.1版本下的Entities已经放弃使用 IJobForEach了,希望博主能修改一下文章(注明这个方法在某某版本被放弃了)
    0回复
    • 博主 笨木头 2020-04-28 17:06:43

      都到0.9了,真快,我注明一下
      0回复
    • 像素魔法师 2020-11-24 14:06:03

      IJobForEach已弃用的话,对应的替补方案是什么?
      Entities.ForEach底层有做到对多线程的优化吗?
      0回复
  • sneaking 2019-12-13 21:21:52

    这里有个疑问,假如说通过Rotation+RotationSpeed_ForEach筛选出来的实体不止一个,那IJobForEach这种方式实现的方法会处理哪一个呢?
    0回复
    • 博主 笨木头 2019-12-16 08:29:23

      都会处理的,每一个被筛选出来的实体(组件)都会执行一次Excute函数。
      这也是我们要转换的思维,在ECS中,我们只针对数据进行操作,只要这些数据满足我们的筛选条件,不断有多少个,我们都做一样(或类似)的处理。
      0回复
      • wuxiao 2020-04-22 16:09:35

        就是您说了一个“Execute函数只能对单个实体的组件”,这个有点没理解什么意思,理论上说entities.foreach是不是可以理解为一个循环遍历,所以拥有筛选组件的实体都会旋转。但是继承了Ijobforeach后,这个遍历是在哪里做的呢?
        0回复
        • 博主 笨木头 2020-04-22 18:35:34

          用了Ijobforeach,这个遍历就是底层处理的,这时候的Execute 就相当于循环当中的某次调用。
          0回复
发表评论
粤ICP备16043700号

本博客基于 BlazorAnt Design Blazor 开发