1일1알

LyraAssetManager 본문

언리얼/Lyra 프로젝트 분석

LyraAssetManager

영춘권의달인 2024. 7. 5. 16:00

AssetManager란 이름 그대로 애셋, 데이터들을 관리하는 클래스이다.

에디터의 Project Settings에서 AssetManager로 사용할 클래스를 지정하면 자동으로 이 클래스가 엔진의 AssetManager로 등록된다.

 

등록한 클래스는 DefaultEngine.ini파일에 기록된다.

 

/**
 * ULyraAssetManager
 *
 *	Game implementation of the asset manager that overrides functionality and stores game-specific types.
 *	It is expected that most games will want to override AssetManager as it provides a good place for game-specific loading logic.
 *	This class is used by setting 'AssetManagerClassName' in DefaultEngine.ini.
 */
UCLASS(Config = Game)
class ULyraAssetManager : public UAssetManager
{
	GENERATED_BODY()

public:

	ULyraAssetManager();

	// Returns the AssetManager singleton object.
	static ULyraAssetManager& Get();

	// Returns the asset referenced by a TSoftObjectPtr.  This will synchronously load the asset if it's not already loaded.
	template<typename AssetType>
	static AssetType* GetAsset(const TSoftObjectPtr<AssetType>& AssetPointer, bool bKeepInMemory = true);

	// Returns the subclass referenced by a TSoftClassPtr.  This will synchronously load the asset if it's not already loaded.
	template<typename AssetType>
	static TSubclassOf<AssetType> GetSubclass(const TSoftClassPtr<AssetType>& AssetPointer, bool bKeepInMemory = true);

	// Logs all assets currently loaded and tracked by the asset manager.
	static void DumpLoadedAssets();

	const ULyraGameData& GetGameData();
	const ULyraPawnData* GetDefaultPawnData() const;

protected:
	template <typename GameDataClass>
	const GameDataClass& GetOrLoadTypedGameData(const TSoftObjectPtr<GameDataClass>& DataPath)
	{
		if (TObjectPtr<UPrimaryDataAsset> const * pResult = GameDataMap.Find(GameDataClass::StaticClass()))
		{
			return *CastChecked<GameDataClass>(*pResult);
		}

		// Does a blocking load if needed
		return *CastChecked<const GameDataClass>(LoadGameDataOfClass(GameDataClass::StaticClass(), DataPath, GameDataClass::StaticClass()->GetFName()));
	}


	static UObject* SynchronousLoadAsset(const FSoftObjectPath& AssetPath);
	static bool ShouldLogAssetLoads();

	// Thread safe way of adding a loaded asset to keep in memory.
	void AddLoadedAsset(const UObject* Asset);

	//~UAssetManager interface
	virtual void StartInitialLoading() override;
#if WITH_EDITOR
	virtual void PreBeginPIE(bool bStartSimulate) override;
#endif
	//~End of UAssetManager interface

	UPrimaryDataAsset* LoadGameDataOfClass(TSubclassOf<UPrimaryDataAsset> DataClass, const TSoftObjectPtr<UPrimaryDataAsset>& DataClassPath, FPrimaryAssetType PrimaryAssetType);

protected:

	// Global game data asset to use.
	UPROPERTY(Config)
	TSoftObjectPtr<ULyraGameData> LyraGameDataPath;

	// Loaded version of the game data
	UPROPERTY(Transient)
	TMap<TObjectPtr<UClass>, TObjectPtr<UPrimaryDataAsset>> GameDataMap;

	// Pawn data used when spawning player pawns if there isn't one set on the player state.
	UPROPERTY(Config)
	TSoftObjectPtr<ULyraPawnData> DefaultPawnData;

private:
	// Flushes the StartupJobs array. Processes all startup work.
	void DoAllStartupJobs();

	// Sets up the ability system
	void InitializeGameplayCueManager();

	// Called periodically during loads, could be used to feed the status to a loading screen
	void UpdateInitialGameContentLoadPercent(float GameContentPercent);

	// The list of tasks to execute on startup. Used to track startup progress.
	TArray<FLyraAssetManagerStartupJob> StartupJobs;

private:
	
	// Assets loaded and tracked by the asset manager.
	UPROPERTY()
	TSet<TObjectPtr<const UObject>> LoadedAssets;

	// Used for a scope lock when modifying the list of load assets.
	FCriticalSection LoadedAssetsCritical;
};

 

LyraAssetManager.h이다. 메서드, 변수들을 하나씩 살펴보자면

 

Get() : 엔진에 등록된 LyraAssetManager의 싱글톤 객체를 가져오는 메서드이다. static이기때문에 LyraAssetManager의 객체에 접근할 때 사용하면 될 것 같다.

 

GetAsset(const TSoftObjectPtr<AssetType>& AssetPointer, bool bKeepInMemory) : TSoftObjectPtr로 참조하는 애셋을 로드하는데, 아직 로드되지 않은 애셋이었다면 동기적으로 로드하고 LoadedAssets에 저장한다.

 

DumpLoadedAssets() : 이름만 봤을때는 로드된 애셋들을 데이터에서 날리는 줄 알았는데 LoadedAssets에 있는 로드된 애셋들을 순회하면서 로그를 남기는 메서드이다.

 

StartInitialLoading() : 엔진이 시작될때 호출되는 메서드이다. 

#define STARTUP_JOB_WEIGHTED(JobFunc, JobWeight) StartupJobs.Add(FLyraAssetManagerStartupJob(#JobFunc, [this](const FLyraAssetManagerStartupJob& StartupJob, TSharedPtr<FStreamableHandle>& LoadHandle){JobFunc;}, JobWeight))
#define STARTUP_JOB(JobFunc) STARTUP_JOB_WEIGHTED(JobFunc, 1.f)

void ULyraAssetManager::StartInitialLoading()
{
	SCOPED_BOOT_TIMING("ULyraAssetManager::StartInitialLoading");

	// This does all of the scanning, need to do this now even if loads are deferred
	Super::StartInitialLoading();

	STARTUP_JOB(InitializeGameplayCueManager());

	{
		// Load base game data asset
		STARTUP_JOB_WEIGHTED(GetGameData(), 25.f);
	}

	// Run all the queued up startup jobs
	DoAllStartupJobs();
}

내부에서는 STARTUP_JOB_WEIGHTED라는 매크로를 사용하는데, FLyraAssetManagerStartupJob라는 구조체를 이용해 Functor를 만들어서 StartupJobs에 넣어두고 DoAllStartupJobs()를 호출해 StartupJobs 내부의 일감들을 처리하도록 하고 있다. 

DoAllStartupJobs() 내부를 살펴보면 데디케이티드 서버에서 실행되는 코드일 경우에는 그냥 순차적으로 일을 처리하고, 리슨서버나 클라이언트라면 STARTUP_JOB_WEIGHTED매크로로 등록한 일감의 JobWeight의 값을 이용해서 UpdateInitialGameContentLoadPercent함수를 호출하고 있다.

UpdateInitialGameContentLoadPercent내부를 살펴보면 

void ULyraAssetManager::UpdateInitialGameContentLoadPercent(float GameContentPercent)
{
	// Could route this to the early startup loading screen
}

이런식으로 되어있는데, 자세히는 모르겠지만 아마도 클라이언트에서 로딩바같은 부분을 구현할 때 사용하라고 만들어 둔 기능인 것 같다.

 

GetGameData() : 위의 StartInitialLoading함수에서 STARTUP_JOB_WEIGHTED를 통해 Functor로 만들어져서 실행되는 함수이다.

const ULyraGameData& ULyraAssetManager::GetGameData()
{
	return GetOrLoadTypedGameData<ULyraGameData>(LyraGameDataPath);
}

GetOrLoadTypedGameData<>함수를 호출해 LyraGameDataPath를 넘겨주고 LoadGameDataOfClass가 호출된다. LoadGameDataOfClass의 첫번째 인자가 TSubclassOf<UPrimaryDataAsset>이고, 내부적으로 ULyraGameData::StaticClass로 전달되기 때문에 ULyraGameData은 UPrimaryDataAsset를 상속받은 클래스여야 한다.

LoadGameDataOfClass에서는 LyraGameDataPath경로를 이용해 애셋을 저장한다.

GameData같은 경우에는 LoadedAssets가 아닌 GameDataMap에 저장된다.

그런데 LyraGameDataPath에는 값을 어디에서 넣어주는 것일까?

UPROPERTY(Config)로 되어있다면 ini파일에서 읽어올 수 있다.

DefaultGame.ini 파일을 살펴보면 LyraGameDataPath를 찾을 수 있다.

 

정리하자면

1. AssetManager로 사용할 클래스를 UAssetManager를 상속받아서 구현해 ULyraAssetManager를 만들어서 사용한다.

2. 에디터의 Project Settings에서 AssetManager를 LyraAssetManager로 지정한다.

3. ::Get()함수를 통해 싱글톤 인스턴스에 접근할 수 있다.

4. 엔진 구동시 StartInitialLoading()가 호출되고 GetGameData()를 Functor형태로 만들어서 실행하는데, 여기서 UPrimaryDataAsset를 상속받은 게임 데이터를 로드한다.

5. 로드된 애셋들은 LoadedAssets에 저장되고, GetAsset을 통해 로드된 애셋을 가져오거나 로드되지 않았다면 로드해서 LoadedAssets에 저장한다.

'언리얼 > Lyra 프로젝트 분석' 카테고리의 다른 글

Lyra 분석 - IGameFrameworkInitStateInterface  (0) 2024.07.10
Lyra분석 - LyraGameMode, experience  (0) 2024.07.08
Input  (0) 2024.07.06