언리얼엔진5에서는 C#의 Attribute와 Reflection을 C++로 구현할려고 굉장히 노력을 많이 해놨다.
-> C#의 Reflection을 모방해놨다.
UE5에서 메모리관리가 되는 가장 최소한의 단위는 UObject이다.
레벨에 배치되는 최소한의 단위인 Actor도 UObject를 상속받은 구조다
UE5 내부에선 고전적인 new delete 방식이 아니라 언리얼이 제공하는 메모리 방식으로 관리한다.
-> Garbage Collector
여기서 C++과 C#의 메모리 관리방식부터 이해해야 한다.
C++ : 스마트 포인터
대표적인 Shared Pointer를 얘기하자면, reference count를 이용해서 어떤 객체가 있을 때
그 객체가 몇 번 참조가 되고 있는지를 기록 한다.
count를 기반으로 관리 (count가 0이 되면 메모리를 날리는 방식)
까다로운 점 : 상호 참조로 인한 문제
C# : 메모리를 신경쓰지 않고 작업을 헀는데, GC가 어떻게 판별했을까?
-> 언리얼과 비슷한 방식이다.
UE5의 공식문서 Unreal Object Handling란을 찾아보면, Garbage Collection 항목에 나온다.
"Root set"라는 용어가 나오게 된다.
Reference Graph를 만들어서, 어떤 UObject가 사용되고 있는지 확인하고, 고아가 된(orphaned) 것들이 어떤건지 찾아본다.
트리 구조를 생각하면 된다.
최상위 Root node가 있고, GC는 청소부의 역할
Root부터 타서, Root가 참조하고 있는 애들을 전부 스캔한다.
모든건 UObject 단위인데, UObject를 상속 받았다면 Root의 자식노드에 등록이 된다.
Alice라는 이름을 가진 UObject가 등록되었다고 치자.
얘의 멤버변수에도 다른 애를 또 참조할 경우가 생기는데, 그럼 트리의 자식 노드로 추가된다.
만약 위의 Alice가 Root로 부터 끊겼다면, GC는 Alice를 소거 대상으로 인지한다.
장점 : 상호 참조에 관련된 문제가 없다.
단점 : 오브젝트가 너무 많아지면 GC가 트리를 탐색하는 비용이 늘어난다.
C#에선 Reflection 문법을 통해 멤버변수를 정밀하게 분석할 수 있다
하지만 C++에서 Reflection이 없으니 구현하기가 어렵다.
그래서 연결된 멤버변수들을 알려줘야 한다.
-> Reflection과 메모리가 엮여있는 것이다.
*C++의 스마트 포인터와 달리 참조횟수가 아닌 "참조 유무"를 트리구조를 통해 관리하고 있고,
GC가 주기적으로 탐색해서 끊어진 부분을 소거한다.
AliceActor에서 UAliceObject를 멤버변수로 선언하고, BeginPlay에서
Obj1 = NewObject<UAliceObject>();
만들어주고,
GEngine->ForceGarbageCollection(true);
GC를 강제로 실행시켜 보자
그 후, Tick()내 에서 Obj1이 nullptr인지 체크해 보면, nullptr은 아니지만 UObject의 멤버 변수 값들이 0으로 밀려있게 된다.
→ nullptr로도 체크하지 못하는 댕글링 포인터의 위험이 생긴다 (메모리 오염)
→ GC가 Root set 내부에서 UAliceObject를 찾지 못했다는 뜻이 된다.위에서 말했던 C#의 Reflection 기능이 없으니 언리얼 C++에서 수동으로 정보를 알려줘야한다.
private:
UPROPERTY()
UAliceObject* Obj1;
클래스에서도 UCLASS() 매크로를 통해 클래스의 정보를 보여주고,
내부에서도 보여주고 싶은 부분을 UPROPERTY() 매크로를 통해 보여줘야 엔진에 의해 분석이 된다.
함수도 관찰대상이 되고 싶다면 UFUNCTION() 사용
UPROPERTY()를 붙여주지 않으면 엔진이 변수 감지를 못해 nullptr이 아님에도 유효하지않은 오브젝트가 되는 것.
이런 키워드 매크로를 통해 정보(메타데이터)를 남기고, 탐색을한다.
이제 엔진이 런타임에 클래스와 변수, 함수의 정보를 Runtime에 알 수 있게 된다.
UCLASS() 매크로가 붙게되면 이 클래스 객체 하나가 만들어지고, 실제로 관리되고 있는 그 객체도
샘플(ClassDefaultObject) 하나가 만들어져서 그 두 가지가 런타임 메모리에 있다.
*Actor 같은 경우 레벨에 배치한다면 Root에 알아서 추가된다.
[ GUObjectArray ]
GC를 위한 전역 변수가 있는데, 이게 바로 GUObjectArray 이다.
GUObjectArray의 각 요소에는 Flag가 설정되어있다.
DEPRECATED된 PendingKill을 포함해서, Natve, Garbage, RootSet 등등 자주 사용되는 것들이 존재하는걸 볼 수 있다.
Flag 확인하는 방법
GUObjectArray.IndexToObject() 에서 UObject마다 고유하게 갖는 인덱스인 InternalIndex를 통해 접근, 그후 HasAnyFlags를 통해 Flag를 갖고 있는지 확인.
'UE5' 카테고리의 다른 글
애니메이션 리타겟팅 루트모션관련 에러 (0) | 2024.07.12 |
---|---|
[UE5] Package Build에 Asset 경로 포함 (0) | 2024.07.03 |
[UE5] UWidgetAnimation (0) | 2024.04.06 |
[UE5] LineTraceMultiByChannel (0) | 2024.04.05 |
[UE5] OnComponentOverlap Vs OnComponentHit (1) | 2024.04.04 |