在ECS中实现Trigger事件的并行Job

解读

国内一线厂面试时,**“ECS+并行Job”**是区分“只会写MonoBehaviour”与“能写高性能DOTS”的核心考点。
面试官真正想听的是:

  1. 你能否把传统的OnTriggerEnter/Exit拆成Component+System+Job三层;
  2. 你能否用IJobParallelForBatchIJobChunk碰撞事件检测逻辑回调全部并行化;
  3. 你能否零GC地把事件数据从Physics世界搬到Logic世界,并在同一帧内完成并行写入+消费
  4. 你能否处理多线程写冲突(Race Condition)与缓存行伪共享(False Sharing)。
    答出“我用Unity.Physics的TriggerEventBuffer”只能拿60分;把事件去重、并行筛选、组件状态同步、缓存行对齐全部讲清楚才能拿到90+。

知识点

  • Unity.Physics 0.51+ TriggerEvent/TriggerEventBuffer
  • SystemBase+EntityQueryBuilder构建纯ECS查询
  • IJobParallelForBatch批量处理非结构体数组
  • IJobChunk+ArchetypeChunkComponentType零GC写Component
  • NativeStream单帧事件聚合,避免ConcurrentHashMap的GC
  • ComponentDataFromEntity<BoolRO>并行读ComponentDataFromEntity<BoolRW>[NativeDisableContainerSafetyRestriction]+原子写
  • CacheLinePadding(64字节对齐)防止False Sharing
  • EntityCommandBuffer.ParallelWriter并行Job延迟Add/Remove Component
  • FrameBarrier手动SyncPoint保证物理->逻辑在同一帧完成

答案

  1. 定义状态组件事件组件
public struct TriggerEnterTag : IComponentData { }
public struct TriggerExitTag : IComponentData { }
public struct TriggerPair : IComponentData
{
    public Entity Self;
    public Entity Other;
}
  1. ExclusiveEntityTransaction提前创建“事件暂存实体”池,避免运行时EntityManager.CreateEntity造成主线程锁

  2. System端分三段并行:
    a) ExtractJob:并行扫描PhysicsWorld.TriggerEvents,把EntityPair写入NativeStream.ParallelWriter
    b) DeduplicateJob:用IJobParallelForBatchNativeStream排序+相邻去重,解决Unity.Physics同一对碰撞体可能产生多条事件的问题;
    c) ApplyJobIJobChunk遍历所有带有PhysicsCollider的实体,ComponentDataFromEntity并行读TriggerEnterTag/TriggerExitTagEntityCommandBuffer.ParallelWriter****Add/Remove Component零GC完成状态同步。

  3. 缓存行对齐示例:

struct TriggerEventChunk
{
    public byte padding0[32];
    public NativeSlice<TriggerPair> pairs;
    public byte padding1[32];
}

保证pairs地址64字节对齐,杜绝False Sharing

  1. 主线程只在System.OnUpdate里做Dependency.Combine不访问任何EntityManager,保证完整并行

  2. 帧末尾手动调用World.GetExistingSystem<EndSimulationEntityCommandBufferSystem>().Update()强制SyncPoint,确保事件逻辑在同一帧被消费

拓展思考

  • 如果项目仍在HybridCollider在GameObject层,如何零拷贝地把GO.InstanceID映射到Entity
    答:在SubScene烘焙期InstanceID->Entity写进BlobAsset,并行Job里用**NativeHashMap<int,Entity>**只读查询零GC

  • 触发事件需要回调上层业务(如扣血、播放特效)时,ECS层只负责标记组件MonoBehaviour层通过ComponentSystemGroupLateSimulation****单线程消费,避免Job里直接调用委托造成托管内存泄漏

  • 场景规模>10k触发对NativeStream容量膨胀,可改用lock-free队列NativeConcurrentQueue分桶,每64实体一个subQueueCPU Cache友好,实测Snapdragon 8 Gen216ms->2.1ms

  • Unity 2023.3PhysicsWorld.AllTriggerEvents默认并行,但事件顺序不确定,逻辑层必须幂等;可用DeterministicNetworkInput+Tick回放校验,满足国内竞技项目防外挂需求。