Automatic job dependency management
自动管理Job依赖。
管理Job的依赖是件很麻烦的事。而JobComponentSystem帮我们自动处理了。规则很简单:不同系统的Jobs可以并行地访问同样类型的IComponentData。但是如果有一个Job在向该数据写操作,则这些Jobs不能并行执行,必须根据依赖调度Jobs。
public class RotationSpeedSystem : JobComponentSystem
{
[BurstCompile]
struct RotationSpeedRotation : IJobForEach<Rotation, RotationSpeed>
{
public float dt;
public void Execute(ref Rotation rotation, [ReadOnly]ref RotationSpeed speed)
{
rotation.value = math.mul(math.normalize(rotation.value), quaternion.axisAngle(math.up(), speed.speed * dt));
}
}
// Any previously scheduled jobs reading/writing from Rotation or writing to RotationSpeed
// will automatically be included in the inputDeps dependency.
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
var job = new RotationSpeedRotation() { dt = Time.deltaTime };
return job.Schedule(this, inputDeps);
}
}
How does this work?
所有这些Jobs和Systems声明了它们要读或写的ComponentType,因此当JobComponentSystem返回JobHandle时,它自动将对这些ComponentType的读写访问注册到EntityManager中。
因此当一个系统向A写操作,其它系统之后读取A,那么JobComponentSystem会检查列表中的读取操作,并将传递一个对第一个系统的依赖。
JobComponentSystem简单地将Jobs按照依赖关系连接到一起,简化了我们在主线程中的依赖关系管理。但是,如果一个非Job的ComponentSystem要访问这些数据呢?因为所有的数据访问时事先声明好的,因此ComponentSystem会在与其它有共同依赖的component的所有的jobs完成后,再调用它的OnUpdate。
Dependency management is conservative & deterministic
依赖管理是保守且确定的
依赖管理倾向于保守。ComponentSystem简单地跟踪用到过的EntityQuery对象并记录它们对组件类型的读写配置。
而且,当在一个System中调度多个jobs时,无论这些Jobs是否是依赖的,都要讲依赖对象传递给它们。如果证明这导致效率问题,那么尝试把该系统拆分成2个系统。
依赖管理被设计的保守,这样能够用简单的API保证逻辑的确定性和正确性。
Sync points
所有对结构的改变,都会导致“强制同步点”,如,CreateEntity,Instantiate,Destroy,AddComponent,RemoveComponent,SetSharedComponentData。这意味着这些调用发生时,之前通过JobComponentSystem调度的所有的Jobs都要完成。这是自动的。比如,在逻辑帧的执行中间,调用EntityManager.CreateEntity,会导致逻辑停下来,等待之前的Jobs 完成。
可以通过EntityCommandBuffer来避免实体创建类操作导致的Sync Points。
Multiple Worlds
每个World都有自己的EntityManager,以及互相独立的JobHandle依赖管理。一个World的Hard Sync Point,不会对另一个World产生影响。因此,对于序列化和创建对象,一个有用的建议是,在一个World中创建对象,创建好后,在一帧的开始将他们移动到另一个World中。
可以参考System update order 以及 ExclusiveEntityTransaction来避免实体创建和序列化导致的Sync Points。