PART 7 · 강의 2/3

스레드와 비동기 처리

GameThread, WorkerThread, TaskGraph 기반 애니메이션 병렬 평가 메커니즘을 심층 분석합니다.

SECTION 01

애니메이션 스레딩 모델

게임 스레드와 워커 스레드 간 애니메이션 작업 분배

애니메이션 스레딩 타임라인
Frame N: Game Thread: [──Tick──][──AnimUpdate──][──PostAnim──]─────────────── │ ↑ │ Dispatch │ Complete ↓ │ Worker Thread: ─────────────[─AnimGraph Evaluate─]───────────┘ [─Bone Transform─────] Render Thread: ──────────────────────────[──Skin──][──Draw──] Game Thread 작업: - NativeUpdateAnimation (데이터 수집) - FAnimInstanceProxy::PreUpdate (데이터 복사) - AnimGraph 평가 태스크 디스패치 Worker Thread 작업: - FAnimInstanceProxy::Update (노드 업데이트) - FAnimInstanceProxy::Evaluate (포즈 계산) - 본 트랜스폼 컴포넌트 스페이스 변환 Game Thread 후처리: - CompleteParallelAnimationEvaluation - 물리 바디 동기화 - Notify 발동
병렬 평가 조건

병렬 애니메이션 평가는 bUseParallelAnimEvaluation이 true일 때 활성화됩니다. 다음 조건을 모두 만족해야 합니다: AnimInstance가 NeedsImmediateUpdate에서 false 반환, 게임 스레드 의존성 없음, a.ParallelAnimEvaluation CVar 활성화.

SECTION 02

TaskGraph와 병렬 평가

UE5 TaskGraph 시스템을 활용한 애니메이션 태스크 스케줄링

애니메이션 병렬 평가는 TaskGraph 시스템을 통해 스케줄링됩니다. 각 SkeletalMeshComponent의 AnimGraph 평가가 독립 태스크로 생성되어 워커 스레드 풀에서 병렬 실행됩니다.

C++ - 내부 구현 (간소화)
// USkeletalMeshComponent::TickAnimation 내부 void USkeletalMeshComponent::TickAnimation(float DeltaTime) { if (ShouldUseParallelEvaluation()) { // 게임 스레드: 데이터 준비 AnimInstance->PreUpdateAnimation(DeltaTime); // 워커 스레드로 평가 태스크 디스패치 ParallelAnimationEvaluationTask = TGraphTask<FParallelAnimEvaluationTask>:: CreateTask(nullptr, ENamedThreads::GameThread) .ConstructAndDispatch<>(this); // 이 시점에서 게임 스레드는 다른 작업 수행 가능 // 다음 컴포넌트의 TickAnimation 호출 등 } else { // 게임 스레드에서 직접 평가 (폴백) AnimInstance->UpdateAnimation(DeltaTime); AnimInstance->EvaluateAnimation(); } } // 프레임 후반에 완료 대기 void USkeletalMeshComponent::CompleteParallelAnimationEvaluation() { // 워커 스레드 태스크 완료 대기 ParallelAnimationEvaluationTask->Wait(); // 결과 적용 (게임 스레드) AnimInstance->PostEvaluateAnimation(); UpdateBoneTransforms(); SyncPhysicsBodies(); }
CVar 설명 기본값
a.ParallelAnimEvaluation 병렬 애니메이션 평가 전역 활성화 1 (활성)
a.ParallelAnimUpdate 병렬 애니메이션 업데이트 활성화 1 (활성)
a.ParallelAnimInterpolation 병렬 본 보간 활성화 1 (활성)
SECTION 03

스레드 안전 프로그래밍

커스텀 AnimNode에서 스레드 안전하게 데이터를 접근하는 패턴

금지: 게임 스레드 데이터 직접 접근

AnimNode의 Update/Evaluate에서 UAnimInstance, AActor, UWorld 등 게임 스레드 객체에 직접 접근하면 경합(Race Condition)이 발생합니다.

권장: FAnimInstanceProxy 사용

PreUpdate에서 필요한 데이터를 Proxy에 복사하고, Update/Evaluate에서는 Proxy의 캐시 데이터만 사용합니다.

권장: Property Access

AnimGraph의 Property Access 바인딩은 스레드 안전하게 구현되어 있어, 직접 프로퍼티 복사 없이 안전하게 데이터에 접근 가능합니다.

주의: Notify 발동 타이밍

Queued Notify는 게임 스레드에서 발동되지만, Branching Point는 평가 스레드에서 트리거될 수 있습니다. Notify 콜백에서의 스레드 안전성을 확인하세요.

C++
// 올바른 패턴: Proxy를 통한 데이터 접근 struct FMyAnimProxy : public FAnimInstanceProxy { // 게임 스레드에서 복사 (PreUpdate에서) float CachedSpeed = 0.f; bool bCachedIsInAir = false; virtual void PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds) override { Super::PreUpdate(InAnimInstance, DeltaSeconds); UMyAnimInstance* MyAnim = Cast<UMyAnimInstance>(InAnimInstance); // 게임 스레드 데이터를 안전하게 복사 CachedSpeed = MyAnim->Speed; bCachedIsInAir = MyAnim->bIsInAir; } }; // AnimNode에서 Proxy 데이터 사용 void FAnimNode_Custom::Update_AnyThread(const FAnimationUpdateContext& Context) { FMyAnimProxy& Proxy = Context.AnimInstanceProxy->GetProxyOnAnyThread<FMyAnimProxy>(); // 안전: Proxy의 캐시 데이터 사용 float Speed = Proxy.CachedSpeed; // 위험: UAnimInstance 직접 접근 금지! // float Speed = AnimInstance->Speed; // RACE CONDITION! }
SECTION 04

비동기 애니메이션 로딩

애니메이션 애셋의 비동기 스트리밍과 로딩 전략

C++
// 비동기 애니메이션 로딩 void AMyCharacter::AsyncLoadAnimation(TSoftObjectPtr<UAnimMontage> MontageRef) { FStreamableManager& StreamableManager = UAssetManager::GetStreamableManager(); StreamableManager.RequestAsyncLoad( MontageRef.ToSoftObjectPath(), FStreamableDelegate::CreateUObject( this, &AMyCharacter::OnAnimationLoaded, MontageRef) ); } void AMyCharacter::OnAnimationLoaded( TSoftObjectPtr<UAnimMontage> MontageRef) { UAnimMontage* Montage = MontageRef.Get(); if (Montage) { // 애니메이션 로딩 완료, 사용 가능 GetMesh()->GetAnimInstance()->Montage_Play(Montage); } } // PoseSearchDatabase의 비동기 빌드 // DB의 SearchIndex는 에디터에서 저장 시 빌드됨 // 런타임에는 직렬화된 인덱스를 로드하여 즉시 사용
Animation Streaming

UE5에서 AnimSequence의 Compression 데이터 스트리밍이 지원됩니다. 전체 애니메이션을 메모리에 로드하지 않고, 필요한 프레임 범위만 스트리밍하여 메모리 사용량을 줄입니다. 프로젝트 설정의 bEnablePerformanceLog로 스트리밍 히트/미스를 모니터링하세요.

SUMMARY

핵심 요약

  • UE5 애니메이션은 게임 스레드(데이터 준비/후처리)워커 스레드(AnimGraph 평가)로 분리 실행된다.
  • TaskGraph로 각 SkeletalMeshComponent의 평가가 독립 태스크로 병렬 실행된다.
  • AnimNode에서는 FAnimInstanceProxy를 통해서만 데이터에 접근하여 스레드 안전성을 보장한다.
  • Property Access는 스레드 안전한 데이터 바인딩 시스템으로, Proxy 수동 복사를 대체할 수 있다.
  • 비동기 애니메이션 로딩과 Animation Streaming으로 메모리와 로딩 시간을 최적화한다.
PRACTICE

도전 과제

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

실습 1: 병렬 애니메이션 평가 확인

프로젝트 설정에서 Allow Multi Threaded Animation Update가 활성화되어 있는지 확인하세요. Unreal Insights로 애니메이션 평가가 Worker Thread에서 수행되는지 타이밍을 확인하고, Game Thread 대비 병렬 처리의 효과를 측정하세요.

실습 2: Thread-Safe 변수 접근 패턴

AnimBP에서 Property Access 노드를 사용하여 Character Movement의 Velocity를 AnimGraph에서 직접 읽는 패턴을 구현하세요. 기존 Event Graph 캐싱 방식과 비교하여 스레드 안전성과 성능 차이를 확인하세요.

심화 과제

C++에서 FAnimInstanceProxy를 오버라이드하여 워커 스레드에서 안전하게 실행되는 커스텀 애니메이션 로직을 구현하세요. PreUpdate, Update, PostUpdate의 실행 스레드를 이해하고, 게임 스레드 데이터를 Proxy로 복사하는 패턴을 적용하세요.