Unity DOTS/Dots Custom Manual

UNITY DOTS - IJobEntity Jobs 에 대해 알아보자

개양반 2022. 7. 31.

IJobEntity? 란 무엇인가?

Entities.ForEach처럼 ComponentData를 반복합니다. 다만, IJobEntity는 여러 시스템에서 호출해서 사용할 수 있다는 점입니다.

아래는 IJobEntity의 심플한 샘플입니다.

// IJobEntity
public partial struct ASampleJob : IJobEntity
{
    // 모든 translation Component에 1를 추가합니다
    void Execute(ref Translation translation)
    {
        translation.Value += 1f;
    }
}

public partial class ASample : SystemBase
{
    protected override void OnUpdate()
    {
        // Job 예약
        new ASampleJob().ScheduleParallel();
    }
}

IJobEntity 인터페이스를 사용하여 구조체를 만들고 Excute()를 구현합니다. 

 


Query 하는 방법(ComponentData 조회)

IJobEntity에 대한 쿼리를 지정하는 방법에는 두 가지가 있습니다.

  • Excute() 함수의 매개변수를 이용해서 Query를 작성합니다. (Entities.ForEach처럼)
partial struct QueryJob : IJobEntity
{
    // 모든 Translation을 반복하고 각각의 translation.Value 값을 (0, 1, 0)씩 증가시킵니다.
    public void Execute(ref Translation translation)
    {
        translation.Value += math.up();
    }
}

 

  • Query로 원하는 요구사항을 지정합니다.
// 두개의 Query를 전역변수로 만들었습니다.
EntityQuery m_QueryBoidTarget;
EntityQuery m_QueryBoidObstacle;

protected override void OnCreate()
{
    // BoidTarget(읽기전용), Translation(쓰기가능) 컴포넌트를 가진 Query 작성
    m_QueryBoidTarget 
    = GetEntityQuery(ComponentType.ReadWrite<Translation>(),ComponentType.ReadOnly<BoidTarget>());

    //BoidObstacle(읽기전용), Translation(쓰기가능) 컴포넌트를 가진 Query 작성
    m_QueryBoidObstacle 
    = GetEntityQuery(ComponentType.ReadWrite<Translation>(),ComponentType.ReadOnly<BoidObstacle>());
}

 

protected override void OnUpdate()
{
    // m_QueryBoidTarget로 조회한 Entity의 Component만 QueryJob에서 반복 실행됩니다.
    new QueryJob().ScheduleParallel(m_QueryBoidTarget);

    // m_QueryBoidObstacle 조회한 Entity의 Compoent만 QueryJob에서 반복 실행됩니다.
    new QueryJob().ScheduleParallel(m_QueryBoidObstacle);

    // Exute에서 조회한 모든 Entity의 Component에 대해 반복 실행됩니다.
    new QueryJob().ScheduleParallel();
}

Query를 전달하면 Query로 조회된 Entitiy 중에 Excute의 매개변수로 지정된 ComponentData를 반복 실행하며, 전달된 Qeury가 없는 경우는 모든 Entitiy를 대상으로 Excute의 매개변수로 지정된 ComponentData를 반복 실행합니다.

 


Entities.ForEach와 IJobEntity의 차이점

Entities.ForEach에 비해 IJobEntity의 핵심 이점은 한 번이 아니라 여러 시스템에서 사용할 수 있는 코드를 한 번 작성할 수 있다는 것입니다.

다음은 boids에서 가져온 예입니다. Entities.ForEach입니다. 전체코드를 전부다 해석할 필요는 없습니다. 3개의 Entities.ForEach를 보면 Query만 다를 뿐 로컬 포지션 값을 NativeArray에 저장하는 코드입니다.

public partial class BoidForEachSystem : SystemBase
{
    EntityQuery m_BoidQuery;
    EntityQuery m_ObstacleQuery;
    EntityQuery m_TargetQuery;
    protected override void OnUpdate()
    {
        // 각 쿼리에서 엔티티의 양을 계산합니다.
        var boidCount = m_BoidQuery.CalculateEntityCount();
        var obstacleCount = m_ObstacleQuery.CalculateEntityCount();
        var targetCount = m_TargetQuery.CalculateEntityCount();

        // 데이터를 저장하기 위해 배열을 할당하여 각 쿼리와 일치하는 엔티티의 양과 같습니다.
        var cellSeparation = CollectionHelper.CreateNativeArray<float3, RewindableAllocator>(boidCount, ref World.UpdateAllocator);
        var copyTargetPositions = CollectionHelper.CreateNativeArray<float3, RewindableAllocator>(targetCount, ref World.UpdateAllocator);
        var copyObstaclePositions = CollectionHelper.CreateNativeArray<float3, RewindableAllocator>(obstacleCount, ref World.UpdateAllocator);

        // 각 배열이 각각의 쿼리에 저장 될 작업을 예약하십시오.
        Entities
            .WithSharedComponentFilter(new BoidSetting{num=1})
            .ForEach((int entityInQueryIndex, in LocalToWorld localToWorld) =>
            {
                cellSeparation[entityInQueryIndex] = localToWorld.Position;
            })
            .ScheduleParallel();

        Entities
            .WithAll<BoidTarget>()
            .WithStoreEntityQueryInField(ref m_TargetQuery)
            .ForEach((int entityInQueryIndex, in LocalToWorld localToWorld) =>
            {
                copyTargetPositions[entityInQueryIndex] = localToWorld.Position;
            })
            .ScheduleParallel();

        Entities
            .WithAll<BoidObstacle>()
            .WithStoreEntityQueryInField(ref m_ObstacleQuery)
            .ForEach((int entityInQueryIndex, in LocalToWorld localToWorld) =>
            {
                copyObstaclePositions[entityInQueryIndex] = localToWorld.Position;
            })
            .ScheduleParallel();
    }
}

 

 IJobEntitiy를 이용하면 좀더 간단해집니다. 전체 코드를 다 살펴볼 필요없이 OnUpdate에서 new CopyPositionJob으로 간단하게 로컬포지션 값을 NativeArray에 저장하고 있습니다. 이렇게 IJobEntity는 여러 호출에서 재사용할 수 있다는 장점이 있습니다.

public partial class BoidJobEntitySystem : SystemBase
{
    EntityQuery m_BoidQuery;
    EntityQuery m_ObstacleQuery;
    EntityQuery m_TargetQuery;

    protected override void OnUpdate()
    {
        // 각 쿼리에서 엔티티의 양을 계산합니다.
        var boidCount = m_BoidQuery.CalculateEntityCount();
        var obstacleCount = m_ObstacleQuery.CalculateEntityCount();
        var targetCount = m_TargetQuery.CalculateEntityCount();

        // 데이터를 저장하기 위해 배열을 할당하여 각 쿼리와 일치하는 엔티티의 양과 같습니다.
        var cellSeparation = CollectionHelper.CreateNativeArray<float3, RewindableAllocator>(boidCount, ref World.UpdateAllocator);
        var copyTargetPositions = CollectionHelper.CreateNativeArray<float3, RewindableAllocator>(targetCount, ref World.UpdateAllocator);
        var copyObstaclePositions = CollectionHelper.CreateNativeArray<float3, RewindableAllocator>(obstacleCount, ref World.UpdateAllocator);

        // 각 배열이 각각의 쿼리에 저장 될 작업을 예약하십시오.
        new CopyPositionsJob { CopyPositions = cellSeparation}.ScheduleParallel(m_BoidQuery);
        new CopyPositionsJob { CopyPositions = copyTargetPositions}.ScheduleParallel(m_TargetQuery);
        new CopyPositionsJob { CopyPositions = copyObstaclePositions}.ScheduleParallel(m_ObstacleQuery);
    }

    protected override void OnCreate()
    {
        // 앞에서 설명한`copypositionsjob`에 필요한 구성 요소가 포함 된 각각의 쿼리를 가져옵니다.
        m_BoidQuery = GetEntityQuery(typeof(LocalToWorld));
        m_BoidQuery.SetSharedComponentFilter(new BoidSetting{num=1});
        m_ObstacleQuery = GetEntityQuery(typeof(LocalToWorld), typeof(BoidObstacle));
        m_TargetQuery = GetEntityQuery(typeof(LocalToWorld), typeof(BoidTarget));;
    }
}

 


사용할 수 있는 Attribue

이것은 Job과 유사하므로 Job에서 작동하는 모든 속성도 작동합니다.

Unity.Burst.BurstCompile BurstComplie를 사용하여 성능 이점을 최대한 얻는다.
Unity.Collections.DeallocateOnJobCompletion Job이 완료되면 자동으로 메모리 할당을 해제합니다.
Unity.Collections.NativeDisableParallelForRestriction 병렬 작업에서 ComponentDataFromEntity에 쓰기 작업을 할 수 있게 한다.
Unity.Burst.BurstDiscard BurstComplie을 비활성화 시킨다.
Unity.Collections.LowLevel.Unsafe.NativeSetThreadIndex 이 속성은 작업자 스레드 인덱스를 작업 구조체의 int에 주입할 수 있습니다. 이것은 일반적으로 원자 컨테이너의 구현에 사용됩니다. 인덱스는 병렬로 실행될 수 있는 다른 작업에 대해 고유함을 보장합니다.
Unity.Burst.NoAlias 설명을 봐도 모르겠다. 컴공과가 아니라서 뭔 말인지 모르겠다.
https://blog.unity.com/kr/technology/enhanced-aliasing-with-burst

 

댓글

💲 추천 글