PART 2 · 강의 3/3

실행 순서와 의존성

Processor 간의 실행 순서 제어와 멀티스레드 병렬 처리를 학습합니다

01

ExecutionOrder 시스템

Processor 간 실행 순서를 명시적으로 지정하는 방법

같은 Processing Phase 내에서 여러 Processor의 실행 순서를 제어해야 할 때 ExecutionOrder를 사용합니다.

C++ - ExecutionOrder 설정 UMyMovementProcessor::UMyMovementProcessor() { ProcessingPhase = EMassProcessingPhase::PrePhysics; // 특정 그룹에 속하기 ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Movement; // 특정 Processor 이후에 실행 ExecutionOrder.ExecuteAfter.Add( TEXT("UMSMovementProcessor")); // 특정 Processor 이전에 실행 ExecutionOrder.ExecuteBefore.Add( TEXT("UVisualizationProcessor")); } // AI 판단 → 이동 적용 → 시각화의 순서를 보장 UAIDecisionProcessor::UAIDecisionProcessor() { ProcessingPhase = EMassProcessingPhase::PrePhysics; ExecutionOrder.ExecuteBefore.Add( TEXT("UMyMovementProcessor")); }
실행 순서 다이어그램 // PrePhysics 단계 내 실행 순서 // ┌─────────────────────────────────────────────┐ // │ PrePhysics Phase │ // │ │ // │ UAIDecisionProcessor │ // │ ↓ (ExecuteBefore) │ // │ UMyMovementProcessor │ // │ ↓ (ExecuteAfter) │ // │ UCollisionProcessor │ // │ ↓ │ // │ UVisualizationProcessor │ // └─────────────────────────────────────────────┘
Processor 이름 규칙

ExecuteAfter/ExecuteBefore에는 Processor 클래스의 FName을 사용합니다. U 접두사를 포함한 전체 클래스 이름을 문자열로 전달합니다. 오타가 있으면 조용히 무시되므로 주의하세요.

02

Processor 그룹

관련 Processor를 논리적으로 그룹화하여 관리

ExecuteInGroup을 사용하면 관련 Processor들을 논리적 그룹으로 묶어 순서를 더 쉽게 관리할 수 있습니다.

C++ - Processor 그룹 활용 // 기본 제공 그룹 이름 (UE::Mass::ProcessorGroupNames) // - Movement : 이동 관련 // - Avoidance : 충돌 회피 // - LOD : LOD 계산 // - Representation : 시각화 // - Tasks : StateTree/AI 태스크 // 커스텀 그룹 정의 namespace MyProcessorGroups { const FName Combat = TEXT("Combat"); const FName Spawning = TEXT("Spawning"); } // 전투 그룹에 속하는 Processor들 UDamageProcessor::UDamageProcessor() { ExecutionOrder.ExecuteInGroup = MyProcessorGroups::Combat; } UHealProcessor::UHealProcessor() { ExecutionOrder.ExecuteInGroup = MyProcessorGroups::Combat; ExecutionOrder.ExecuteAfter.Add(TEXT("UDamageProcessor")); } // 전투 그룹 전체를 이동 그룹 이후에 실행 // → 그룹 간 순서도 설정 가능
빌트인 그룹 실행 순서 포함 Processor 예시
Tasks 최우선 StateTree 업데이트
Avoidance 이동 전 충돌 회피 계산
Movement 중간 위치/속도 업데이트
LOD 이동 후 LOD 레벨 계산
Representation 최후순 시각화 업데이트
03

멀티스레드 실행

Query의 접근 모드가 결정하는 병렬 실행 가능성

Mass Entity는 Query의 접근 모드를 분석하여 데이터 충돌이 없는 Processor들을 자동으로 병렬 실행합니다.

멀티스레드 실행 판단 기준 // 병렬 실행 가능 조건: // 1. ReadOnly Fragment가 겹치는 경우 → 안전 // 2. 서로 다른 Fragment에 접근하는 경우 → 안전 // // 병렬 실행 불가: // 1. 같은 Fragment에 ReadWrite로 접근하는 경우 → 순차 실행 // 2. bRequiresGameThreadExecution = true → 게임 스레드 대기 // Processor A: Transform(RW), Velocity(RO) // Processor B: Health(RW), Velocity(RO) // → Transform과 Health가 다르므로 병렬 실행 가능! // Velocity는 둘 다 ReadOnly이므로 안전 // Processor A: Transform(RW) // Processor C: Transform(RW) // → 같은 Fragment에 ReadWrite → 순차 실행 필요
병렬화 최적화 팁

가능한 한 Fragment를 ReadOnly로 접근하세요. ReadWrite 접근이 적을수록 더 많은 Processor가 병렬로 실행됩니다. 꼭 필요한 Fragment만 ReadWrite로 설정하는 것이 성능 최적화의 핵심입니다.

서브시스템 스레드 안전성

C++ - 서브시스템 스레드 트레이트 // 서브시스템의 스레드 안전성을 명시적으로 선언 template<> struct TMassExternalSubsystemTraits<UMyWorldSubsystem> final { enum { // 여러 Processor가 동시에 읽기 가능? ThreadSafeRead = true, // 여러 Processor가 동시에 쓰기 가능? ThreadSafeWrite = false }; }; // 스레드 안전한 접근 매크로 void UMyProcessor::Execute(...) { MyQuery.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Ctx) { UMyWorldSubsystem& Subsystem = Ctx.GetMutableSubsystemChecked<UMyWorldSubsystem>(); // 읽기 접근 보호 UE_MT_SCOPED_READ_ACCESS(Subsystem); // ... 읽기 작업 ... // 쓰기 접근 보호 UE_MT_SCOPED_WRITE_ACCESS(Subsystem); // ... 쓰기 작업 ... }); }
04

의존성 그래프 시각화

Processor 의존성을 이해하고 디버깅하는 방법

Mass Debugger를 통해 현재 등록된 모든 Processor의 의존성 그래프를 시각적으로 확인할 수 있습니다.

Mass Debugger 접근 // 에디터에서 Mass Debugger 열기: // Window → Developer Tools → Mass Entity Debugger // // Processor 탭에서 확인 가능한 정보: // - 등록된 모든 Processor 목록 // - 각 Processor의 처리 단계 (Phase) // - 실행 그룹 (ExecuteInGroup) // - 의존성 관계 (Before/After) // - Query 요구사항 (Fragment/Tag) // - 매칭되는 Archetype 수 // - 처리한 엔티티 수
순환 의존성 주의

A가 B 이후, B가 A 이후에 실행되도록 설정하면 순환 의존성(Circular Dependency)이 발생합니다. 이 경우 에디터 로그에 경고가 출력되며, 실행 순서가 정의되지 않습니다. Mass Debugger에서 의존성 그래프를 확인하여 이를 방지하세요.

실전 설계 패턴

C++ - 전형적인 Processor 파이프라인 // 전형적인 게임 시뮬레이션 Processor 파이프라인 // // Phase: PrePhysics // ┌───────────────┐ // │ Input/AI │ ← StateTree, 입력 처리 // │ (Tasks Group) │ // └───────┬───────┘ // ↓ // ┌───────────────┐ // │ Avoidance │ ← 충돌 회피 계산 // └───────┬───────┘ // ↓ // ┌───────────────┐ // │ Movement │ ← 위치 업데이트 // └───────┬───────┘ // ↓ // Phase: PostPhysics // ┌───────────────┐ // │ LOD Calc │ ← LOD 레벨 계산 // └───────┬───────┘ // ↓ // Phase: FrameEnd // ┌───────────────┐ // │ Visualization │ ← ISM/Actor 업데이트 // └───────────────┘
SUMMARY

핵심 요약

  • ExecutionOrder로 같은 Phase 내 Processor 간 실행 순서를 제어한다 (ExecuteAfter, ExecuteBefore)
  • ExecuteInGroup으로 관련 Processor를 논리적 그룹으로 묶어 관리한다 (Movement, Tasks, LOD 등)
  • Mass Entity는 Query의 접근 모드를 분석하여 데이터 충돌이 없는 Processor들을 자동 병렬 실행한다
  • ReadOnly 접근을 최대화하면 더 많은 Processor가 병렬로 실행되어 성능이 향상된다
  • TMassExternalSubsystemTraits로 서브시스템의 스레드 안전성을 명시적으로 선언한다
  • Mass Debugger를 통해 Processor 의존성 그래프를 시각적으로 확인하고 순환 의존성을 방지한다
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: Processor 실행 그룹 설정

ExecutionFlags를 사용하여 Processor를 올바른 Processing Phase(PrePhysics, DuringPhysics, PostPhysics 등)에 등록하세요. 이동 Processor는 PrePhysics, 렌더링 관련은 PostPhysics에 배치하세요.

실습 2: Processor 간 의존성 선언

ExecutionOrder.ExecuteAfter에 다른 Processor 클래스를 지정하여 실행 순서를 보장하세요. 예: UMovementProcessor가 UDamageProcessor보다 먼저 실행되도록 설정하고, 순서가 바뀌었을 때의 문제를 확인하세요.

심화 과제: ProcessingPhase별 성능 프로파일링

6개의 Processing Phase별 소요 시간을 stat MassEntity 또는 Insights로 측정하세요. 특정 Phase에 Processor가 집중되어 있다면 Phase 분산을 통해 프레임 시간을 균등화하세요.