게임 개발 메모장
[ UE5 ] Garbage Collection (G.C) 본문
1. PendingKill
Actor는 일반적으로 가비지 콜렉팅되지 않는데,
월드 오브젝트가 액터 레퍼런스 목록을 저장하기 때문이다.
액터는 Destroy() 를 호출하여 명시적으로 소멸시킬 수 있다.
그러면 레벨에서 제거되어 pending kill (킬 대기) 상태로 마킹,
잠시 후 다음 가비지 콜렉션 때 지워진다는 뜻이다.
언리얼의 엑터나 컴포넌트는 Destroy를 호출하여 제거를 하게 되면
씬 상에서는 제거된 것으로 보이지만 메모리에서는 Pending Kill 상태가 되어
존재하며 다음 GC(Garbage Collection) 실행시 메모리에서 해제된다.
이는 에디터의 프로젝트 기본 설정상으로
1분단위로 Pending Kill 상태인 오브젝트를 제거하며
사용자가 설정을 변경할 수 있다.
C++ 코드 상에서 유효성을 검사할 때 nullptr과 IsValid() 함수를
사용하게 되는데 Pending Kill 상태까지 검사하기 위해서는
IsValid() 함수를 사용해야 한다.
2. 가비지 컬렉션
언리얼에서 UProperty 를 붙인 객체는 언리얼 엔진이 자동으로
가비지 컬렉터를 이용해 메모리를 관리한다.
가비지 컬렉션을 수행함에 있어 리플렉션 시스템을 사용하는데,
엔진이 객체와 속성값을 알고 있으므로, 더 이상 사용되지 않아 삭제해도
괜찮은 객체들을 구분할 수 있기 때문이다.
언리얼 엔진에서는 Reference Graph 를 만들어 오브젝트들의 사용 여부를 구분한다.
이 그래프 루트에는 "Root Set" 이라 지정된 오브젝트 셋이 존재하며,
"Root Set" 에 포함된 객체들은 가비지 컬렉션 대상에서 제외된다.
UObject::BaseUtility::AddToRoot 함수를 이용하면
객체를 "Root Set" 에 추가시킬 수 있다. ( 예시 : UMyObject->AddToRoot() )
가비지 컬렉션이 실행되면 엔진은 "Root Set" 을 시작으로
UObject 레퍼런스 트리를 검색해 참조된 오브젝트를 모두 추적한다.
이 검색 과정에서 찾지 못한 것들은
더 이상 필요하지 않는 오브젝트라고 판단하고 제거할 수 있는 것이다.
이는 가비지 컬렉션이 리플렉션 데이터에 의존하므로 가능한 일이다.
다만 엔진이 가비지 컬렉터를 제대로 수행하게 만들기 위해서는 다음과 같은 규칙들을 지켜줘야 한다.
1) UPROPERTY 선언
클래스 내부 멤버 변수가 클래스의 객체의 수명과 운명을 함께한다면
UPROPERTY 로 선언해야 한다. 이는 수명 주기가 같다는 뜻이다.
반면 잠시 사용할 UObject 나 일반 클래스 객체들은 별도로 관리해 주어야 한다.
2) 멤버가 가리키는 포인터
엔진이 인식하거나 관리하지 않는 메모리 영역을 가리키도록 만들면 안된다.
따라서 멤버가 가리키는 포인터는 UObject 또는 그 자식들로 한정시키는 것이 좋다.
3) TArray 를 활용하자
UObject 또는 자식들에 대한 포인터를 안전하게 담을 수 있는 컨테이너는 TArray 가 유일하다.
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY()
MyGCType* SafeObject;
MyGCType* DoomedObject;
AMyActor(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
SafeObject = NewObject<MyGCType>();
DoomedObject = NewObject<MyGCType>();
}
};
void SpawnMyActor(UWorld* World, FVector Location, FRotator Rotation)
{
World->SpawnActor<AMyActor>(Location, Rotation);
}
위에서, SafeObject 는 UPROPERTY( ) 선언이 되어 있으므로,
Root Set Object 로부터 참조가 가능해 가비지 컬렉션 대상이 되지 않는다.
하지만 DoomedObject 는 Root Set Object 로부터 참조가 되지 않아
가비지 컬렉팅이 되고, 결국 Dangling Pointer 로 남아 있을 수 있다.
특정 UObject 가 가비지 컬렉션되면, 모든 UPROPERTY reference 가 null 로 세팅된다.
이는 오브젝트가 가비지 컬렉션되었는지 아닌지를 안전하게 검증할 수 있게 만들어 준다.
if (MyActor->SafeObject != nullptr)
{
// Use SafeObject
}
위에서 MyActor->SafeObject 가 nullptr 가 아니라는 뜻은
해당 UObject 가 가비지 컬렉팅을 기다리고 있지 않다는 뜻이다.
액터를 Destroy 하게 되면, 가비지 컬렉터가 다시 실행되기 전까지는
실제로 제거되지 않으므로, IsPendingKill 메소드를 사용하여
해당 UObject 가 수거를 기다리고 있는지 체크할 수 있다.
3. UStructs vs UObjects
구조체는 "value" 타입으로 사용하기 위한 것이다.
구조체는 가비지 컬렉션의 대상이 아니므로,
UObject 내에 위치해야 메모리가 올바르게 회수될 수 있다.
UStruct 의 장점은 크기가 매우 작다는 것인데,
UObject 는 데이터 외에도 book-keeping 데이터를 가져야 하지만,
UStruct (기술적으로 UScriptStruct) 는 사용자가 입력한 크기만큼만 사용하기 때문이다.
반면, UStruct 는 다른 객체의 멤버 구조체를 직접 가리키는 것이
안전하지 않다는 단점 또한 존재한다.
UObject 는 UStruct 에 비해 무겁지만 일반적으로 안전하게 포인팅될 수 있다.
4. 가비지 컬렉션 요청
UObject 들은 가비지 컬렉션을 명시적으로 요청할 수 있다.
해당 함수들을 호출하면, 가비지 컬렉션 수행 대상으로 등록되게 된다.
1) UObejct
UObejct::ConditionalBeginDestroy()
2) AActor
AActor::DestroyActor()
5. 강제로 가비지 컬랙션 하기
World::ForceGarbageCollection(bool bFullPurge) 함수를 통해 강제로 가비지 컬렉션을 수행할 수 있다.
* UBT(Unreal Build Tool) / UHT(Unreal Header Tool)
언리얼은 이 둘을 이용해 리플렉션을 더 강화하게 되는데.
UBT는 전체 헤더 파일들 중 리플렉션 시스템에 들어오는 파일들을 기억하고
이후 이중 어떤 것이든 변경이 된 것이라면 UHT를 이용해서
리플렉션 데이터를 수집, 업데이트한다.
이후 수집이 된 정보는 별개의 C++ 코드인 ~~~.generated.h, .cpp로 저장하게 된다.
https://docs.unrealengine.com/5.0/ko/unreal-engine-actor-lifecycle/
액터의 수명 주기
액터가 로드 또는 스폰된 후 결국 죽을 때 벌어지는 일에 대해서입니다.
docs.unrealengine.com
'언리얼 엔진 > 기능' 카테고리의 다른 글
[ C++ ] 정적/동적 라이브러리 (0) | 2023.08.27 |
---|---|
[ UE5 ] Build Process (0) | 2023.08.27 |
[ UE5 ] GetClass vs StaticClass (0) | 2023.08.17 |
[ UE5 ] 게임플레이 디버거 (0) | 2023.08.06 |
[ UE5 ] Unreal Assert (1) | 2023.06.01 |