이 글에서는 "Transient Package가 왜 존재하는지", "Outer로서 어떤 의미를 가지는지", "GC 및 수명과 어떤 관계가 있는지", "실무에서 언제 쓰고 언제 피해야 하는지" 정리한다.

 

목차

     

     


     

     

    GetTransientPackage()


     

     

    Transient UObject의 소속, 수명

     

    Unreal Engine에서 UObject를 다루다 보면 한 번쯤은 다음과 같은 코드를 마주치게 된다.

    UObject* Obj = NewObject<UObject>(GetTransientPackage());
     

    겉보기에는 단순히 “임시 객체를 만드는 방법”처럼 보이지만,
    GetTransientPackage()는 UObject 저장 모델, 경로 시스템, GC 설계와 연결된 개념이다.


     

     

    Transient Package란 무엇인가?

     

    GetTransientPackage()는 엔진이 전역으로 유지하는 단 하나의 UPackage를 반환한다.
    이 패키지는 디스크에 저장되지 않는(Runtime-only) UObject들의 최상위 컨테이너 역할을 한다.

    Transient Package는 “저장되지 않는 UObject들을 위한 합법적인 소속(Outer)”이다.

     

    /** Transient package. */
    static UPackage* GObjTransientPkg = nullptr;
    
    UPackage* GetTransientPackage()
    {
        return GObjTransientPkg;
    }

     


     

     

    Unreal의 핵심 전제: UObject는 반드시 “어디엔가” 속해야 한다

     

    (Outer, Name) → Full Path Name

     

    예를 들면:

    • /Game/Items/Sword.Sword
    • /Engine/Transient.MyTempObject

    즉,

    • UObject는 반드시 Outer 트리에 매달려야 하고
    • 이 Outer 트리의 최상단은 항상 UPackage

    문제는 여기서 발생한다.

     

    런타임 전용 객체는 어디에 속해야 할까?

    • 에셋 패키지(/Game/...) → ❌ 저장/쿠킹 대상이 됨
    • 맵 패키지(/Game/Maps/...) → ❌ 에디터/PIE 오염 가능
    • 월드/레벨 → ❌ 수명 결합 위험

    이때 등장하는 해결책이 바로 Transient Package다.


     

     

    Transient Package의 역할 정리

     

    Transient Package는 다음 목적을 동시에 만족한다.

    1) UObject 경로 시스템을 유지한다

    • UObject는 여전히 유효한 PathName을 가진다
    • 리플렉션, 디버깅, 로깅, FindObject 계열 API와 정상적으로 호환된다

    2) 저장/쿠킹 파이프라인에서 분리된다

    • 디스크에 저장되지 않는다
    • 에셋 레지스트리, 패키지 로딩, 쿠킹 대상이 아니다

    3) 엔진 전반에서 “공용 임시 루트”로 사용된다

    • 엔진이 단 하나만 생성
    • 모든 임시 UObject가 동일한 기준을 공유

     

     

    Transient ≠ GC 안전

     

    UObject* Temp = NewObject<UObject>(GetTransientPackage());

     

    위 코드에서 Temp는 GC로부터 보호되지 않는다.

     

    중요한 사실

    • Outer는 수명을 보장하지 않는다
    • Unreal GC는 Reachability(도달 가능성) 기반이다
    • Transient Package는 Root Object가 아니다

    즉, 다음 중 하나라도 없으면 객체는 GC 대상이다.

    • 다른 살아있는 UObject가 UPROPERTY()로 참조
    • FGCObject를 통한 참조 등록
    • AddToRoot() (주의해서 사용)
    • GameInstance / Subsystem / Manager 객체가 멤버로 보유

     

    Transient Package는 “저장 안 됨”을 보장할 뿐, “살아 있음”을 보장하지 않는다.


     

     

    언제 GetTransientPackage()를 써야 하는가

     

    1. 런타임 전용 데이터 객체

    • UI ViewModel (MVVM)
    • 임시 계산 결과
    • 테스트/디버그용 UObject
    • 세션 간 유지될 필요 없는 상태 객체
    UMyViewModel* VM = NewObject<UMyViewModel>(GetTransientPackage());

    단, 반드시 다른 UObject가 UPROPERTY로 보유해야 한다.

     

    2. 특정 월드에 종속되면 안 되는 객체

    • 월드 전환 시 유지돼야 하거나
    • 반대로, 월드 수명과 무관해야 하는 객체

    이 경우 Transient가 적절한 선택이 될 수 있다.


     

     

    언제 쓰면 안 되는가

     

    1. 월드/레벨 수명과 함께 가야 하는 객체

    다음과 같은 경우라면 Transient는 잘못된 선택이다.

    • 특정 월드에서만 의미 있음
    • 레벨 언로드 시 반드시 정리돼야 함
    • PIE 재시작 시 초기화돼야 함

    이런 경우는 보통:

    • UWorld
    • ULevel
    • UGameInstance
    • USubsystem
    • AActor

    를 Outer로 두는 것이 맞다.

     

    2. “저장 안 되게 하려고” Transient만 쓰는 경우

    저장/직렬화 제어는 Outer 하나로 끝나지 않는다. 실무에서는 보통 다음이 함께 필요하다.

    • RF_Transient 플래그
    • SaveGame 로직에서의 명시적 제외
    • 에디터 트랜잭션(Undo/Redo) 제어

     

    본질 플래그 (속성) 소속(Outer) 패키지
    해결하는 문제 직렬화/저장 패키지 경로/소속
    GC 영향 ❌ 없음 ❌ 없음
    Outer 대체 가능
    저장 방지 범위 객체 단위 패키지 단위
    주 용도 “이 객체는 저장하지 마라” “이 객체는 어디에도 속하지 않는다”

     

    RF_Transient = “저장 금지 표시”
    Transient Package = “저장 대상이 아닌 소속”


     

     

    내부 구현 관점에서 본 GetTransientPackage()

     

    GetTransientPackage()는 단순한 getter처럼 보이지만, 실제 의미는 다음과 같다.

    • 엔진 초기화 시 정확히 한 번 생성
    • 전역으로 공유되는 유일한 Transient Package
    • 모든 임시 UObject가 일관된 기준으로 소속됨

    이 일관성이 깨지면:

    • 경로 충돌
    • GC 추적 문제
    • 에디터/리디렉션/디버깅 이상 동작

    같은 문제가 발생할 수 있다.

    그래서 엔진은 직접 패키지를 만들게 하지 않고,
    반드시 GetTransientPackage()를 통해 접근하도록 설계되어 있다.


     

     

    GetTransientPackage()를 사용하기 전 체크리스트

     

    1. 이 객체는 저장되면 안 되는가?
    2. 이 객체의 수명은 무엇과 동기화돼야 하는가?
    3. GC에서 살아남을 참조 경로가 있는가?
    4. 에디터(PIE, Undo/Redo)에서 부작용은 없는가?

    이 질문에 명확히 답할 수 없다면,
    Transient Package는 아직 이 객체의 올바른 Outer가 아닐 가능성이 크다.

     


     

    마무리

     

    GetTransientPackage()는 단순한 “임시 객체용 함수”가 아니다.

    • 저장 모델을 이해하지 못하면 오용하기 쉽고
    • GC 모델을 이해하지 못하면 크래시로 이어지며
    • 수명 설계를 잘못하면 구조가 무너진다