[UE5] Hard Reference, Soft Reference
프로젝트를 진행하면서 사용하는 애셋들과 기능을 테스트하기 위해 구조를 고려하지 않고 작성한 코드, 클래스들이 많아져 간단한 테스트 Level도 로드하는데 시간이 꽤 걸리는 것을 확인하였다.
Size Map으로 Level의 사이즈와 구성 요소들을 확인하고 Hard Reference를 Soft Reference로 변경하여 참조하는 객체를 미리 메모리에 로드하지 않고 동적으로 로드하도록 하였고 이에 대해 포스팅해보려 한다.
Size Map
Size Map은 애셋들의 메모리 사용량을 시각적으로 확인할 수 있는 언리얼 에디터에서 제공하는 기능이다. 로드 시간, 메모리 사용량을 관리할 때 문제가 되는 애셋을 바로 확인할 수 있어 매우 유용하다.
Level을 시작할 때 로드시간 문제를 해결하기 위해 Content Drawer에서 Level에 우클릭 후 Size Map을 클릭하자.
Level에 설정된 GameMode의 Controller, HUD, Pawn, GameState를 Size Map에서 볼 수 있으며 Level에 배치된 오브젝트도 확인할 수 있다.
2GB 중 Level의 게임이 시작하자마자 Spawn, Possess 하는 기본 Pawn 클래스인 BP_Gunner가 약 550MB를 차지하고 있었다.
PlayerController가 무려 1.4GB를 차지하고 있었다. 최적화를 아직 고려하지 않고 작업 중이라 캐릭터 클래스가 매우 무거운 것은 알고 있었으나 PlayerController에 문제가 있다는 것을 알게 되었다.
Reference Viewer
Reference Viewer도 상속, 의존 관계 파악, 메모리 관리에 도움 되는 기능으로 Reference, Dependencies Depth를 조정하여 원하는 부분을 모두 확인할 수 있다.
PlayerController 클래스가 두 캐릭터 클래스를 참조하고 있는 것을 확인할 수 있다. (Soft Reference로 바꾼 뒤 캡처해서 자주색 줄로 이어져있다)
Hard Reference / Soft Reference
캐릭터를 스왑 하는 기능을 테스트해 보려고 PlayerController에 임의로 작성했던 코드가 문제였다.
멤버 변수로 Hard Reference가 포함되어 게임 실행 전 사용하지 않을 캐릭터를 모두 로드하는데 메모리가 사용되고 있었다.
// Class Hard Reference
UPROPERTY(EditDefaultsOnly)
TSubclassOf<class AASRCharacter> NewCharacterClass;
UPROPERTY(EditDefaultsOnly)
TSubclassOf<AASRCharacter> OriginalCharacterClass;
// Class Soft Reference
UPROPERTY(EditDefaultsOnly)
TSoftClassPtr<class AASRCharacter> NewCharacterClass;
UPROPERTY(EditDefaultsOnly)
TSoftClassPtr<AASRCharacter> OriginalCharacterClass;
이를 TSoftClassptr을 사용하여 Soft Reference로 변경하였다. 동적으로 객체를 생성할 때는 동기/비동기 방식을 모두 지원한다.
// Async
FStreamableManager& StreamableManager = UAssetManager::GetStreamableManager();
StreamableManager.RequestAsyncLoad(NewCharacterClass.ToSoftObjectPath(), FStreamableDelegate::CreateLambda([this, NewLocation, Rotation]()
{
UClass* LoadedClass = NewCharacterClass.Get();
if (LoadedClass != nullptr)
{
NextCharacter = GetWorld()->SpawnActor<AASRCharacter>(LoadedClass, NewLocation, Rotation);
if (NextCharacter != nullptr)
{
bSpawned = true;
Possess(NextCharacter);
}
}
}
));
// Sync
UClass* LoadedClass = NewCharacterClass.LoadSynchronous();
if (LoadedClass != nullptr)
{
NextCharacter = GetWorld()->SpawnActor<AASRCharacter>(LoadedClass, NewLocation, Rotation);
if (NextCharacter != nullptr)
{
Possess(NextCharacter);
bSpawned = true;
}
}
비동기로 처리하니 Spawn 이후 참조가 바로 필요하기 때문에 문제가 생겨 우선 동기로 처리하였다. 최적화가 되지 않은 상태의 캐릭터 클래스들이라 메모리에 올리고 동작하는데 까지 시간이 많이 걸리는 것이 모든 문제의 원인이다. 기본 기능 완성 후 캐릭터 클래스 초기화 관련 코드를 우선 최적화하려 한다.
다시 Size Map을 확인해 보면 기본 Pawn인 BP_Gunner를 제외하고 다른 두 캐릭터 클래스가 미리 로드되지 않는 것을 확인할 수 있다.
병행하고 있는 프로젝트인 개인 프로젝트, 팀 프로젝트 2개가 모두 바쁜 기간이라 포스팅을 자주 올리지 못하고 있다. Object, Class에서의 Soft Reference의 내부 코드와 Hard Reference와 비교해서 장단점까지 다루는 후속 포스팅을 시간이 되면 다음에 다뤄보려 한다.
Reference
[1] https://www.youtube.com/watch?v=aUG54KCP89M