ECB
유니티 ECS에는 EntityCommandBuffer(ECB)라는 개념이 있다.
엔티티의 생성/파괴, 컴포넌트 추가/제거 등은 구조변경(Structural Change)및 Sync Point를 만들어서 성능에 악 영향을 준다.
특히나 병렬 Job에서는 구조 변경 자체가 불가하다.
그래서 여러 Job등에서 구조변경을 유발하는 명령들을 ECB에 모아두고 한번에 Playback한다. 이렇게 하면 병렬 Job에서도 구조 변경이 안전하게 가능해지고, Sync Point를 한 번만 만들어내서 성능에도 좋다.
ECB는 두 가지 방법으로 사용할 수 있는데, 다음과 같다.
- 그때그때 직접 만들기
- 이미 있는 걸 쓰기
아래와 같이 체력이 0이하인 Entity를 파괴하는 Job이 있다. 이 Job은 병렬로 실행할거라 ECB를 외부에서 받아 사용해야 한다.
[BurstCompile]
public partial struct DestroyDeadJob : IJobEntity
{
public EntityCommandBuffer.ParallelWriter Ecb;
void Execute(
[EntityIndexInQuery] int sortKey,
Entity entity,
in Health health)
{
if (health.Value <= 0)
{
Ecb.DestroyEntity(sortKey, entity);
}
}
}
1. 그때 그때 ECB 만들어 쓰기
[BurstCompile]
public partial struct DestroyDead_DirectECBSystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
// 1) 이 시스템 안에서 쓸 ECB를 직접 생성
var ecb = new EntityCommandBuffer(Allocator.TempJob);
// 2) 병렬 잡에 넘겨서 커맨드 기록
var job = new DestroyDeadJob
{
Ecb = ecb.AsParallelWriter()
};
var handle = job.ScheduleParallel(state.Dependency);
handle.Complete(); // 반드시 완료 후에 Playback
// 3) 원하는 타이밍에 직접 Playback
ecb.Playback(state.EntityManager);
ecb.Dispose();
state.Dependency = handle;
}
}
만든 ECB의 Playback()과, Dispose()를 꼭 호출해줘야 한다는 점을 명심하자.
2. 원래 있는 걸 쓰기
[BurstCompile]
[UpdateInGroup(typeof(SimulationSystemGroup))]
public partial struct DestroyDead_UsingECBSystem : ISystem
{
public void OnCreate(ref SystemState state)
{
// EndSimulation ECB 시스템이 존재할 때만 동작하도록
state.RequireForUpdate<EndSimulationEntityCommandBufferSystem.Singleton>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
// 1) 이미 존재하는 EndSimulation ECB 싱글턴에서 커맨드 버퍼 생성
var ecbSingleton = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>();
var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged).AsParallelWriter();
// 2) 잡에 ParallelWriter 전달
var job = new DestroyDeadJob
{
Ecb = ecb
};
state.Dependency = job.ScheduleParallel(state.Dependency);
// 의존성 등록은 ECB 시스템이 처리 (버전별 패턴 약간 차이 가능)
}
}
SimulationSystemGroup이 끝날 때 Playback되는 EndSimulationEntityCommandBufferSystem에 접근하여 이것을 사용한다.
차이점이 있다면, 직접 만든 것과 달리 Playback이나 Dispose를 호출하지 않는다. (호출하면 에러가 난다.)
Playback을 하면 SyncPoint가 생성되는데, 이 때 모든 병렬 Job들이 SyncPoint를 대기하기 때문에 성능좋지 않다. 따라서 가급적 ECB의 Playback은 하나로 뭉쳐주는 것이 병렬작업 효율 최대화 하기 좋다. 따라서 구조 변경이 즉시 반영되어야 하는 게 아니라면 ECB 하나에 구조 변경을 몰아주는 것이 좋다.
직접 만들어 쓸 수도 있다.
using Unity.Entities;
// SimulationGroup 중간 어딘가에서 Playback 하고 싶은 ECB 시스템
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(BeginSimulationEntityCommandBufferSystem))]
public partial class MidSimulationEntityCommandBufferSystem : EntityCommandBufferSystem
{
}
[BurstCompile]
public partial struct SomeSystem : ISystem
{
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<MidSimulationEntityCommandBufferSystem.Singleton>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var ecbSingleton = SystemAPI.GetSingleton<MidSimulationEntityCommandBufferSystem.Singleton>();
var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged).AsParallelWriter();
var job = new SomeJob { Ecb = ecb };
state.Dependency = job.ScheduleParallel(state.Dependency);
}
}
SortKey
그럼 SortKey는 무엇인가?
다시 위에서 봤던 HP가 0이하인 엔티티를 제거하는 Job을 보자.
[BurstCompile]
public partial struct DestroyDeadJob : IJobEntity
{
public EntityCommandBuffer.ParallelWriter Ecb;
void Execute(
[EntityIndexInQuery] int sortKey,
Entity entity,
in Health health)
{
if (health.Value <= 0)
{
Ecb.DestroyEntity(sortKey, entity);
}
}
}
Ecb.DestoryEntity를 할 때 첫 번째 인자로 sortKey를 넘겨주고 있음을 볼 수 있다.
ECB를 ParallelWriter로 쓰게 되면 Lock을 방지하기 위해 쓰레드 별로 따로 버퍼를 둔다. 그리고 Playback이전에 이것들을 하나로 합쳐서 하나씩 수행하게 된다. (구조 변경은 메인쓰레드에서만 가능하다.)
sortKey는 쓰레드별 버퍼를 하나로 합칠 때 어떤 순서로 합칠 것인지에 대한 우선순위이다.
딱히 어떻게 되든 상관이 없다면 0으로 넘겨줘도 되고, 작업이 엄격하게 결정적(Deterministic)이어야 한다면 직접 sortKey를 관리해 주는 것이 좋다.
void Execute(
[EntityIndexInQuery] int sortKey,
위 코드를 보면 [EntityIndexInQuery]를 주고 있는데, 이것은 쿼리 내에서 해당 엔티티가 몇번째인지를 유니티ECS에서 자동으로 넘겨준다고 보면 된다.
자매품으로 [EntityIndexInChunk]가 있다. (이름처럼 청크에서 몇번째인지)
'게임엔진 > ECS(Unity)' 카테고리의 다른 글
| Entities - 컴포넌트 구조 (0) | 2023.02.25 |
|---|