IJobEntityBatch에서 I는 인터페이스를 뜻하는 것일테고 Job은 특정한 단일 작업을 수행하는 작은 단위라는 것이고 Entity는 그릇이고 그럼 Batch는 무엇일까?
사전 의미로는 일괄적으로 처리되는 집단[무리] 동사로는 (일괄처리를 위해) 함께 묶다
Chunk는 여러 Batch가 존재하게 되는데 IJobEntityBatch는 Batch를 활용해서 데이터를 처리한다.
IJobEntityBatch 구조체 살펴보기
IJobEntityBatch를 상속받아서 구조체를 정의한다.
public struct UpdateTranslationFromVelocityJob : IJobEntityBatch
{
public ComponentTypeHandle<VelocityVector> velocityTypeHandle;
public ComponentTypeHandle<Translation> translationTypeHandle;
public float DeltaTime;
[BurstCompile]
public void Execute(ArchetypeChunk batchInChunk, int batchIndex)
{
NativeArray<VelocityVector> velocityVectors =
batchInChunk.GetNativeArray(velocityTypeHandle);
NativeArray<Translation> translations =
batchInChunk.GetNativeArray(translationTypeHandle);
for(int i = 0; i < batchInChunk.Count; i++)
{
float3 translation = translations[i].Value;
float3 velocity = velocityVectors[i].Value;
float3 newTranslation = translation + velocity * DeltaTime;
translations[i] = new Translation() { Value = newTranslation };
}
}
}
Execute(ArchetypeChunk batchInChunk, int batchIndex) 설명
Execute는 구조체가 만들어지면 자동으로 실행되는 함수이다. 매개변수로 batchInChunk, batchIndex를 받는데
batchInChunk 는 IJobEntityBatch 구조체를 만들때 Query를 통해 설정한 조건에 맞는 청크의 Batch들이 들어오게 되고 batchIndex는 검색된 Batch들의 식별코드이다. EntityCommand로 병렬 쓰기를 할 때, batchIndex가 sortKey가 될 수 있다.
Job이 접근하는 데이터 선언
위 코드에서 ComponentTypeHandle 라는 녀석을 볼 수 있는데 Job의 Excute에서 사용할 수 있는 데이터를 선언한 것이다. Excute에서 사용할 수 있는 필드는 4가지가 존재한다.
ComponentTypeHandle : Excute 함수로 들어온 batchInChunk는 여러 데이터 타입이 들어오게 되는데 ComponentTypeHandle 으로 특정 데이터 핸들을 만들어서 batchInChunk에서 데이터를 가져올때 사용한다. 즉, ComponentTypeHandle 은 batchInChunk 안의 데이터 중에 특정데이터를 가져오는데 사용하는 것이다.
ComponentDataFromEntity, BufferFromEntity : Excute에 들어온 batchInChunk와 상관없이 모든 Entity에 대한 데이터를 조회할 수 있다. 몰론 그만큼 성능상 안 좋은 부분이 있으니 필요한 경우에만 사용해야 한다.
Other fileds : Job을 실행할 때 다른 정보가 필요한 경우 Job 구조체에서 필드를 정의한 다음 Excute 내부에 있는 필드에 접근할 수 있습니다. Job을 scheduling 할때만 값을 설정할 수 있으며 해당 값은 모든 Batch에 동일하게 유지된다. 예로 Time.delta이 Job에서 필요한 경우 Job 구조체에 float dt를 만들고 Job을 예약할 때 Time.delta 값을 dt에 전달할 수 있다.
Output field : Job에서 쓰기 가능한 Entity Compoent 또는 Buffer를 업데이트하는 것 외에도 Job Struct에 대해 선언된 기본 컨테이너 필드에 쓸 수도 있습니다. 이러한 필드는 NativeArray와 같은 기본 컨테이너여야 합니다. 다른 데이터 유형은 사용할 수 없습니다.
NativeArray??
Job의 결과가 각 복사본 내에 격리되어 메인스레드에서 접근할 수가 없다. 그래서 NativeContainer라는 공유 메모리 타입에 저장해서 메인스레드에서도 해당 결과값을 값 복사가 아닌 참조로 사용할 수 있게 한다. 간단하게 말해서 Job에서는 배열 말고 NativeContainer를 사용하면 된다. NativeContainer에는 NativeList, NativeHashMap, NativeMultiHashMap, NativeQueue 이 있다.
Job Scheduing 하기
Job struct를 정의했으면 이번에는 해당 Job을 예약해서 멀티스레드에서 데이터를 처리하게 해보자.
public partial class UpdateTranslationFromVelocitySystem : SystemBase
{
EntityQuery query;
protected override void OnCreate()
{
// Query 만들기
var description = new EntityQueryDesc()
{
All = new ComponentType[]
{ComponentType.ReadWrite<Translation>(),
ComponentType.ReadOnly<VelocityVector>()}
};
query = this.GetEntityQuery(description);
}
protected override void OnUpdate()
{
// Job 구조체 생성
var updateFromVelocityJob
= new UpdateTranslationFromVelocityJob();
// Component Handle 만들기
updateFromVelocityJob.translationTypeHandle
= this.GetComponentTypeHandle<Translation>(false);
updateFromVelocityJob.velocityTypeHandle
= this.GetComponentTypeHandle<VelocityVector>(true);
// DeltaTime 값을 Job 구조체의 필드로 전달
updateFromVelocityJob.DeltaTime = World.Time.DeltaTime;
// Job 예약하기
this.Dependency
= updateFromVelocityJob.ScheduleParallel(query, this.Dependency);
}
진행 순서는 다음과 같습니다.
1. Query를 만들어서 JobBatch에서 작업할 데이터 타입을 설정합니다.
2. IJobBatch 를 생성합니다.
3. IJobBatch에서 사용할 데이터의 Handle을 만들고 Other fileds(여기서는 Time.Deltatime) 값을 전달합니다.
4. Job 을 예약해서 실행할 준비가 완료되면 실행시킵니다.
GetComponentTypeHandle<T>(false);
여기서 false는 읽기 전용이라는 의미이다. 유니티의 DOTS는 멀티스레드 환경에서 실행되므로 데이터가 읽기만 하는지, 쓰기도 하는지 꼭 정의 해줘야 한다.
this.Dependency
= updateFromVelocityJob.ScheduleParallel(query, this.Dependency);
ScheduleParallel는 멀티스레드에서 병렬로 처리해 달라는 의미이다. 그럼, Dependency가 무엇일까? 종속성이라는 것인데 무엇에 쓰는 물건일까? 일단, Job은 멀티스레드 환경에서 실행될 수 있다는 것이다. 여러 스레드에서 쓰기, 읽기가 처리되니 여러 스레드에서 하나의 데이터에 동시에 접근할 때 경쟁 문제라는 것이 발생할 수 있다. 이러한 경쟁 조건을 방지하기 위해 Job Scheduler는 시스템의 Job이 실행되기 전에 시스템이 의존하는 모든 작업이 완료되었는지를 확인합니다. 이때 사용되는 것이 바로 Dependency이다. 각 Job들이 읽고 쓰는 구성 요소를 기반으로 Dependency를 업데이트하여 각 Job들이 경쟁문제가 발생하지 않도록 한다.
'Unity DOTS > Dots Custom Manual' 카테고리의 다른 글
UNITY DOTS - Entity Command Buffers 에 대해 알아보자. (0) | 2022.07.30 |
---|---|
UNITY DOTS - EntityQuery에 대해 알아보자 (2) | 2022.07.27 |
Entity Profiler Modules에 대해 알아보자 (0) | 2022.07.26 |
UNITY DOTS - ICompoentData 에 대해 알아보자. (0) | 2022.07.25 |
댓글