UE5 Outer & GarbageCollection

에크까망·2024년 9월 29일

들어가기 전에

  • 단순화하기 위해서 문체를 단정적으로 사용하지만 지극히 개인 의견일 뿐입니다.
  • 본문과 관련해서 오류 지적이나 의견이 있으시면 꼭 댓글 부탁드립니다.
  • 글 작성 시점은 2024/09입니다.
  • 글 작성 기준은 UE5.4.4입니다.

들어가며

UObject에서 Outer는 구조를 유지하는 기능을 합니다. 이번 포스팅에서는 대표적인 Outer의 사용처를 먼저 살펴보고, GarbageCollection에서 Outer가 어떻게 작용되는지 살펴보겠습니다.

대표적인 사용처

인터넷의 다른 Article에도 잘 나와 있지만, 우선 World 구조를 유지합니다.

class UWorld* UObject::GetWorld() const  
{  
    if (UObject* Outer = GetOuter())  
    {       
	    return Outer->GetWorld();  
    }  
    
   return nullptr;  
}
UWorld* AActor::GetWorld() const  
{  
    // CDO objects do not belong to a world  
    // If the actors outer is destroyed or unreachable we are shutting down and the world should be nullptr    
    if (!HasAnyFlags(RF_ClassDefaultObject) && ensureMsgf(GetOuter(), TEXT("Actor: %s has a null OuterPrivate in AActor::GetWorld()"), *GetFullName())  
       && !GetOuter()->HasAnyFlags(RF_BeginDestroyed) && !GetOuter()->IsUnreachable())  
    {       
	    if (ULevel* Level = GetLevel())  
       {          
	       return Level->OwningWorld;  
       }    
    }    
    
    return nullptr;  
}

또한 Packaging 등에서 Serializing의 구조 역시 유지합니다.

Outer가 유효하면 GC되지 않을까?

TestCode

UMyTestObj

UCLASS()  
class EKUE54_API UMyTestObj : public UObject  
{  
    GENERATED_BODY()  
public:  
    virtual void BeginDestroy() override;  
};
void UMyTestObj::BeginDestroy()  
{  
    UE_LOG(LogTemp, Warning, TEXT("UMyTestObj::BeginDestroy() - %s"), *GetName());  
    Super::BeginDestroy();  
}

AEkUe54GameMode

class AEkUe54GameMode : public AGameModeBase  
{  
    GENERATED_BODY()  
  
protected:  
    TWeakObjectPtr<class UMyTestObj> WeakTestObj;  
          
public:  
    AEkUe54GameMode();  
  
    virtual void BeginPlay() override;  
    virtual void Tick(float DeltaSeconds) override;  
    virtual void BeginDestroy() override;  
};
void AEkUe54GameMode::BeginPlay()  
{  
    Super::BeginPlay();  
  
    {       
	    UMyTestObj* MyTestObj = NewObject<UMyTestObj>(this);  
       WeakTestObj = MyTestObj;  
       GEngine->ForceGarbageCollection(true);  
    }  
}  
  
void AEkUe54GameMode::Tick(float DeltaSeconds)  
{  
    Super::Tick(DeltaSeconds);  
    UE_LOG(LogTemp, Warning, TEXT("WeakTestObj is - %s"), WeakTestObj.IsValid() ? TEXT("Valid") : TEXT("Invalid") );  
}  
  
void AEkUe54GameMode::BeginDestroy()  
{  
    UE_LOG(LogTemp, Warning, TEXT("AEkUe54GameMode::BeginDestroy() - %s"), *GetName());  
    Super::BeginDestroy();  
}

Scenario

UMyTestObj를 생성하고 Outer로 AEkUe54GameMode를 지정하였습니다. GameMode는 해당 World가 내려가기 전에는 파괴되지 않습니다. 추적을 위해서 WeakTestObj에 Referencing하였습니다. 따라서 생성된 MyTestObj로의 GC Referencing은 없습니다.

Result

[2024.09.29-04.24.31:103][  0]LogTemp: Warning: UMyTestObj::BeginDestroy()
[2024.09.29-04.24.31:794][  1]LogTemp: Warning: WeakTestObj is - Invalid

Outer가 Valid하지만 Referencing이 없으니 파괴됩니다.

Outer가 그 상위로부터 GC Referencing되지 않으면 하위도 모두 파괴될까?

TestCode

UMyTestObj

이전 시나리오와 동일합니다.

AEkUe54GameMode

class AEkUe54GameMode : public AGameModeBase  
{  
    GENERATED_BODY()  
  
protected:  
    UPROPERTY()  
    TObjectPtr<class UMyTestObj> KeepTestObj;  
    
    TWeakObjectPtr<class UMyTestObj> WeakTestObj;
    
public:  
    AEkUe54GameMode();  
  
    virtual void BeginPlay() override;  
    virtual void Tick(float DeltaSeconds) override;  
    virtual void BeginDestroy() override;  
};
void AEkUe54GameMode::BeginPlay()  
{  
    Super::BeginPlay();  
  
    {       
	   UMyTestObj* MyTestObj = NewObject<UMyTestObj>(this);  
       WeakTestObj = MyTestObj;  
  
       UMyTestObj* MyTestSubObj = NewObject<UMyTestObj>(MyTestObj);  
       KeepTestObj = MyTestSubObj;  
  
       GEngine->ForceGarbageCollection(true);  
    }
}  
  
void AEkUe54GameMode::Tick(float DeltaSeconds)  
{  
    Super::Tick(DeltaSeconds);  
    UE_LOG(LogTemp, Warning, TEXT("WeakTestObj is - %s"), WeakTestObj.IsValid() ? TEXT("Valid") : TEXT("Invalid") );  
}  
  
void AEkUe54GameMode::BeginDestroy()  
{  
    UE_LOG(LogTemp, Warning, TEXT("AEkUe54GameMode::BeginDestroy() - %s"), *GetName());  
    Super::BeginDestroy();  
}

Scenario

MyTestSubObj를 만들고 Outer로 이전에 GC되고 있는 상태였던 MyTestObj를 지정해 주었습니다. 그리고 AEkUe54GameMode에서 MyTestSubObj로의 Referencing을 유지하고 있습니다.

Result

[2024.09.29-04.54.31:639][324]LogTemp: Warning: WeakTestObj is - Valid

MyTestObj로의 Referencing이 AEkUe54GameMode로부터 걸려있지 않아도 잘 살아 있습니다. 이는 SubObject인 MyTestSubObj로 Referencing이 걸려있고, MyTestSubObj에서 Outer인 MyTestObj로 Referencing이 걸리기 때문입니다.

Outer GC 규칙

Outer는 SubObject의 GC를 막지 않습니다(Referencing하지 않습니다). 오히려 SubObject로부터 Outer로 Referencing이 걸립니다.

profile
Game Client Programmer

0개의 댓글