【Unity DOTS】EntityCommandBuffer 与 JobHandle

文章目录

  • [EntityCommandBuffer 与 JobHandle 及相关机制](#EntityCommandBuffer 与 JobHandle 及相关机制)
    • [1. EntityCommandBuffer(ECB)](#1. EntityCommandBuffer(ECB))
      • [1.1 核心概念](#1.1 核心概念)
      • [1.2 两种 ECB 变体](#1.2 两种 ECB 变体)
      • [1.3 回放(Playback)机制](#1.3 回放(Playback)机制)
      • [1.4 与 JobHandle 的关系](#1.4 与 JobHandle 的关系)
    • [2. JobHandle](#2. JobHandle)
      • [2.1 是什么](#2.1 是什么)
      • [2.2 依赖链与 Job 调度](#2.2 依赖链与 Job 调度)
      • [2.3 常见误区](#2.3 常见误区)
    • [3. 相关核心概念](#3. 相关核心概念)
      • [3.1 EntityManager 结构性更改与主线程](#3.1 EntityManager 结构性更改与主线程)
      • [3.2 EntityCommandBufferSystem](#3.2 EntityCommandBufferSystem)
      • [3.3 同步点(Sync Point)与 ECS Safety System](#3.3 同步点(Sync Point)与 ECS Safety System)
      • [3.4 Barrier 概念](#3.4 Barrier 概念)
      • [3.5 SystemState.Dependency](#3.5 SystemState.Dependency)
      • [3.6 并行写入 ECB](#3.6 并行写入 ECB)
      • [3.7 NativeContainer 的生命周期与依赖](#3.7 NativeContainer 的生命周期与依赖)
      • [3.8 主线程阻塞与 Profiler 标记](#3.8 主线程阻塞与 Profiler 标记)
    • [4. 使用注意事项总结](#4. 使用注意事项总结)
      • [ECB 相关](#ECB 相关)
      • [JobHandle 相关](#JobHandle 相关)
      • 联合使用

EntityCommandBuffer 与 JobHandle 及相关机制

1. EntityCommandBuffer(ECB)

1.1 核心概念

EntityCommandBuffer 是一个线程安全的命令队列,用于延迟执行 ECS 中的结构性更改(Structural Changes),即会改变实体原型(Archetype)的操作,包括:

  • 创建 / 销毁实体
  • 添加 / 移除 / 设置组件
  • 添加 / 移除动态缓冲区(DynamicBuffer)
  • 实例化 Prefab

ECS 中的 Job 不允许直接调用 EntityManager 执行结构性更改,因为这会立即改变全局数据结构,破坏并行安全性。因此所有此类操作必须通过 ECB 记录下来,统一在指定时机回放(Playback)。

1.2 两种 ECB 变体

类型 创建方式 使用场景
单线程 ECB new EntityCommandBuffer(Allocator.TempJob) 或从 EntityCommandBufferSystem.CreateCommandBuffer() 仅在主线程使用,通常用于 System 的 OnUpdate 中手动记录少量命令,或自定义回放。
并发 ECB 通过 SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>().CreateCommandBuffer(state.WorldUnmanaged) (或类似系统)获得,然后调用 .AsParallelWriter() 获取 ParallelWriter 接口。 在多线程 Job 中使用,每个线程安全写入同一个 ECB 的并行写入器。

重要 :并发 ECB 不能在多个 Job 之间共享同一个实例,必须传递它的 ParallelWriter(通过 AsParallelWriter() 获得)。一个并发 ECB 可以有一个 ParallelWriter 供多个并行 Job 共同使用。

1.3 回放(Playback)机制

ECB 中记录的命令不会自动执行,需要由 EntityCommandBufferSystem 来播放。

  • BeginSimulationEntityCommandBufferSystem:在下一帧 Simulation 阶段开始时回放(通常用于大多数逻辑)。
  • EndSimulationEntityCommandBufferSystem:在当前帧 Simulation 阶段末尾回放(用于必须在本帧生效的结构性更改)。
  • 你还可以创建自定义 EntityCommandBufferSystem,精确控制回放点。

回放时机示例

  • 若你在 Job 中使用 BeginSimulation 的 ECB,则命令会在下一帧的 Simulation 开始前执行。这意味着本帧后续的 Job 无法看到这些结构性更改。
  • 若你使用 EndSimulation 的 ECB 并确保依赖完成,则更改可在当帧稍后的 System 中生效。

1.4 与 JobHandle 的关系

ECB 回放必须等待所有写入该 ECB 的 Job 完成。这通过以下方式自动处理:

  • 当你在 System 的 OnUpdate 中将 Job 的 JobHandle 赋值给 state.Dependency 后,EntityCommandBufferSystem 会从注入的 Dependency 中获取依赖,并等待所有使用它的 Job 完成后才执行回放。
  • 也就是说,你无需手动为 ECB 添加 JobHandle 依赖,只需确保 Job 的句柄正确链入系统依赖链

2. JobHandle

2.1 是什么

JobHandle 是 Unity C# Job System 的依赖与同步核心,代表一个或多个异步 Job 的完成状态。它:

  • 提供 IsCompleted 检查。
  • 提供 Complete() 强制主线程等待完成。
  • 作为 Schedule 方法的输入/输出,形成依赖链。

2.2 依赖链与 Job 调度

  • 每个 Schedule / ScheduleParallel 方法接收一个 JobHandle 依赖参数,并返回一个新 JobHandle
  • 只有当输入依赖完成后,新 Job 才会被调度执行,从而保证数据访问顺序。
  • 多个输入依赖可通过 JobHandle.CombineDependencies(handle1, handle2, ...) 合并。
  • 最终的 JobHandle 应赋值给 SystemState.Dependency,使 ECS 能自动衔接各系统的依赖。

2.3 常见误区

  • 忘记传递依赖:导致 Job 无序执行,出现数据竞争。
  • 过早 Complete :在 Job 链中途调用 Complete() 会强制主线程等待,破坏并行性;应只在真正需要结果时才同步。
  • 空 JobHandle :第一个被调度的 Job 可以传入 default(JobHandle),表示无前置依赖。

3. 相关核心概念

3.1 EntityManager 结构性更改与主线程

  • 在主线程(System 的 OnUpdate 非 Job 部分)可以直接调用 EntityManager 执行结构性更改,无需 ECB。
  • 但高频的结构性更改建议仍然使用 ECB 批量执行,因为 ECB 回放阶段会统一处理并减少同步成本。

3.2 EntityCommandBufferSystem

  • ECS 提供的内建 Barrier 系统:BeginInitializationEntityCommandBufferSystem, EndInitializationEntityCommandBufferSystem, BeginSimulationEntityCommandBufferSystem, EndSimulationEntityCommandBufferSystem 等。
  • 每个 EntityCommandBufferSystem 在帧的固定点执行回放,并清理已播放的 ECB。
  • 你可以通过继承 EntityCommandBufferSystem 创建自定义播放点,适用于更复杂的帧内阶段控制。

3.3 同步点(Sync Point)与 ECS Safety System

  • 当在 Job 中尝试读取尚未完成写入的数据时,会触发同步点(Sync Point),强制主线程等待所有依赖 Job 完成,严重影响性能。
  • ECS 的安全系统(Safety System)会检测 NativeContainer 的竞争访问,确保只读/只写标记([ReadOnly]NativeDisableContainerSafetyRestriction)正确使用以避免意外同步。
  • 违反安全规则时会抛出异常,但在 Release 构建中这些检查被移除以提高性能。

3.4 Barrier 概念

  • "Barrier"通常指一种同步机制,将 Job 流分为前后两部分。EntityCommandBufferSystem 就是一种 Barrier,它将"记录命令"和"应用命令"分开。
  • 也可以使用 JobHandle.Complete() 或依赖链构造自定义 Barrier,但更推荐利用 ECS 内建系统。

3.5 SystemState.Dependency

  • 每个 System 都有一个 Dependency 属性,它记录了上一步系统完成工作的 JobHandle
  • OnUpdate 中,你应该:
    1. 读取 state.Dependency 作为输入依赖(或与其他 JobHandle 组合)。
    2. 将最终输出的 JobHandle 写回 state.Dependency
  • 这样 ECS 能够自动管理跨系统的 Job 执行顺序,而无需手动传递 JobHandle。

3.6 并行写入 ECB

  • 并发 ECB 通过 AsParallelWriter() 方法暴露 ParallelWriter,可以在 IJobEntityIJobChunk 中安全地并行记录命令。
  • 每个 ParallelWriter 内部使用线程局部存储(TLS)避免竞争,回放时再合并,所以即使多个线程同时写入也不会冲突。
  • 一个 ECB 可以只调用一次 AsParallelWriter(),并将同一个 ParallelWriter 实例传入多个 Job 是安全的。

3.7 NativeContainer 的生命周期与依赖

  • 创建 NativeArray/NativeList 时使用 Allocator.TempJobPersistent,必须手动 Dispose()
  • 使用 NativeContainer 作为 Job 数据时,安全系统会自动添加依赖跟踪,确保在容器释放前所有使用它的 Job 都已完成。
  • 若手动过早 Dispose 容器,会触发 ObjectDisposedException
  • 可以通过 NativeList.AsDeferredJobArray() 等接口让 Job 等待容器最终长度确定(例如 IJobParallelForDefer)。

3.8 主线程阻塞与 Profiler 标记

  • 频繁调用 Complete() 会在 Profiler 中显示为 "WaitForJobGroup" 的等待,是优化的关键指标。
  • 尽量将 Complete() 推到帧的末端,或使用 ECB 替换立即的结构性更改需求,以最大化并行度。

4. 使用注意事项总结

ECB 相关

  • 并发 ECB 必须传递 ParallelWriter,不能传递 ECB 实例。
  • 始终调用 Dispose() 释放 ECB(回放系统会自动释放已播放的 ECB,但手动创建的需要显式释放)。
  • 记住你的 ECB 属于哪个回放系统,确保命令在正确的时机生效。
  • 同一帧内,若需要让后续 Job 看到 ECB 的更改,务必使用 EndSimulationEntityCommandBufferSystem 或自定义早期回放系统,并保证依赖关系正确。

JobHandle 相关

  • 永远不要遗漏依赖传递,使用 CombineDependencies 处理多个输入。
  • 在 ECS System 中,将最终 JobHandle 赋值给 state.Dependency
  • 对于不需要结构性更改的场景,尽量用 Job 链代替 ECB + 回放,以减少开销。

联合使用

  • 结构性更改(ECB)与数据计算(Job)要明确划分阶段:先并行计算,再通过 Barrier(ECB System)应用更改,最后让后续 System 读取新状态。
  • 利用 ECS 的 System 分组(如 [UpdateInGroup(typeof(SimulationSystemGroup))])和屏障系统,可以自然地将帧切分为多个阶段,每个阶段内部并行,阶段之间同步。

更多内容请查看总目录【Unity】Unity学习笔记目录整理

相关推荐
xcLeigh2 小时前
Unity基础:Camera相机组件详解——透视投影与正交投影的区别
数码相机·unity·游戏引擎
玖玥拾4 小时前
Unity3D RPG 入门项目(二)移动 Bug 修复、环绕相机、NPC 任务系统、UI 穿透处理
unity·游戏引擎
Yasin Chen6 小时前
Unity UGUI RectMask2D 原理
unity·游戏引擎
hashiqimiya7 小时前
unity---unity编译手机apk人物角色移动
unity·游戏引擎
LONGZETECH8 小时前
传统实训 vs 仿真实训:从技术架构拆解新能源汽车教学的范式之变
c语言·3d·unity·架构·汽车
℡枫叶℡1 天前
Unity - 图集大小与内存占用
unity·图集内存
_ZHOURUI_H_1 天前
Unity MyFramework 用法说明(二十五):使用 AtlasManager 统一管理图集与 Sprite 引用
ui·unity·游戏引擎·unity3d·游戏开发
新手unity自用笔记1 天前
Unity网络基础_2
unity·游戏引擎
音视频牛哥1 天前
从数字孪生到机器人操控:Android Unity3D下RTMP/RTSP多路低延迟播放实践
android·unity·音视频·unity rtsp播放器·unity rtmp播放器·rtsp player·rtmp player