Game Develop

[UE5] 언리얼에서의 객체선언 (with GC) 본문

UnrealEngine5/이것저것

[UE5] 언리얼에서의 객체선언 (with GC)

MaxLevel 2023. 1. 28. 01:07

나같은 경우 플레이어캐릭터의 애니메이션 이벤트그래프는 일단 c++코드로 작성하고 애님그래프쪽만 블루프린트로 처리

해보려 한다. 일단은 이벤트그래프쪽은 c++코드로 처리하려고는 하는데 언제 어떻게 비중이 바뀔지는 아직 모르긴한다..

 

어쨌든 AnimInstance 클래스에 아래와 같이 플레이어캐릭터 포인터 변수를 선언해주었다.

 

class AMainPlayer* m_MainPlayer;

 

하지만 그냥 C++로만 하듯이 이렇게 선언해서 사용하면 안된다.

언리얼엔진에는 가비지컬렉터가 존재한다는것을 반드시 인지하고 있어야 한다.

언리얼 엔진의 가비지컬렉터에 관한 내용은 매우 방대하기 때문에 전부를 다루는것은 어렵고, 개발하면서 부딪치는 문제를 해결할 때마다 관련 경험에 대한 글을 반드시 작성하는게 좋을 것 같다. (본 글처럼)

 

기본적으로 언리얼에서는 '모든 UObject'의 참조개수를 카운팅한다. 

엔진은 하나의 Reference Graph, 즉 참조그래프를 만들어서 어떤 UObject가 사용되고 있는지, 어떤것이 참조되고 참조되지 않는지 계속 확인한다.

이렇게 어떻게 서로 참조되는지 알 수 있는 이유는 리플렉션 시스템을 사용하기 때문이다.

엔진이 객체와 속성들을 알수 있고, 그렇기 때문에 더이상 사용되지 않아 삭제해도 괜찮은 객체들을 구분할 수 있다.

런타임에 알수있다는 점에서 C++의 RTTI 개념이 떠오를 수 있는데, 엔진의 리플렉션 기능은 좀 더 고성능을 지닌 RTTI라고 생각해도 괜찮을듯하다. (객체의 메모리를 참조하여 객체에 대한 정보를 가져오는 기능)

 

이 참조그래프에는 'Root Set'이라는 지정된 오브젝트 세트가 존재하며,이 세트에 포함된 객체들은 GC의 메모리해제대상에서 제외된다. 어떠한 언리얼오브젝트도 특정함수 호출을 통해 이 세트에 포함시키는게 가능하다. 

AddReferencedObjects()랑 AddToRoot() 두 함수가 관련된 기능을 하는 것 같은데, 정확히 두 함수의 차이점은 아직 잘 모르겠다.

 

 

다시 처음으로 돌아가서

class AMainPlayer* m_MainPlayer; <-- 단순히 이렇게 선언하면 어떻게 될까?

 

이 m_MainPlayer라는 UObject는 이미 GC의 관리대상이다. 그렇기 때문에 참조되고 있다는것을 명시해줘야 한다.

위 경우처럼 로우포인터로 선언하면 그걸 알 수가 없다. 참조되어지는지 알 수 없기 때문에 GC가 발동할 때 사라져버릴 수 있다. (GC입장에서는 참조되지 않고있는것을 없애지 않을 이유가 없기 때문)

 

심지어 더 위험한것은, 사라져버렸다는걸 알 수 없다는 것이다.

GC에 의해 메모리해제가 되었더라도 m_MainPlayer라는 변수에는 여전히 해당메모리를 가리키고 있기 때문에 바로 크래쉬가 날 것이다. (댕글링포인터)

그렇기 때문에 임의로 없애기 전까지 사라지면 안될 변수가 있다면 반드시 UPROPERTY를 붙여줘서 GC의 루트셋에 추가시키자.

 

그렇다고 GC에 제거되지 않게하기 위해 마구잡이로 참조그래프의 루트셋에 추가시키는 건 좋지 않다.

결국 이러한 행위들도 자원을 소모하는것이기 때문이다. 

현재 내 경우처럼, 특정클래스안에서만 사용하는 경우(즉 관리하기 쉬운경우?)라면 그냥 TWeakObjectPtr을 사용하는게 더 좋을 것 같다.

TWeakObjectPtr같은 경우는 루트셋에 추가하지는 않는다.

대신! 어디서든 Destroy가 된다면 바로 해당 객체에 null값을 넣어주기 때문에 댕글링포인터를 방지할 수 있다.

레퍼런스카운트를 증가시키지 않으면서 Destroy가 된다면 바로 알아챌 수 있으니 매우 유용하다.

 

https://algorfati.tistory.com/75

 

[Unreal] 언리얼 메모리 관리 시스템 (Smart Pointer, GC)

언리얼은 스마트 포인터와 가비지 컬렉터(GC)를 모두 사용하여 메모리를 관리한다. 일반적으로 네이티브한 부분에서는 스마트 포인터를 사용하고, UObject와 관련된 부분에서는 GC를 사용한다. Smar

algorfati.tistory.com