OLS/Source/ols/Private/Systems/OLSAssetManager.cpp
2025-01-16 12:05:19 -07:00

298 lines
9.1 KiB
C++

// © 2024 Long Ly. All rights reserved. Any unauthorized use, reproduction, or distribution of this trademark is strictly prohibited and may result in legal action.
#include "Systems/OLSAssetManager.h"
#include "OLSLog.h"
#include "DataAssets/OLSGameDataAsset.h"
#include "Misc/App.h"
#include "Stats/StatsMisc.h"
#include "Misc/ScopedSlowTask.h"
#include "Engine/Engine.h"
#include "Systems/OLSAssetManagerStartupJob.h"
#include "DataAssets/OLSPawnDataAsset.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(OLSAssetManager)
DEFINE_LOG_CATEGORY(LogOLSAssetManager);
const FName FOLSBundles::Equipped("Equipped");
//////////////////////////////////////////////////////////////////////
static FAutoConsoleCommand CVarDumpLoadedAssets(
TEXT("OLS.DumpLoadedAssets"),
TEXT("Shows all assets that were loaded via the asset manager and are currently in memory."),
FConsoleCommandDelegate::CreateStatic(UOLSAssetManager::DumpLoadedAssets)
);
//////////////////////////////////////////////////////////////////////
#define STARTUP_JOB_WEIGHTED(jobFunc, jobWeight) StartupJobs.Add(FOLSAssetManagerStartupJob(#jobFunc, [this](const FOLSAssetManagerStartupJob& startupJob, TSharedPtr<FStreamableHandle>& loadHandle){jobFunc;}, jobWeight))
#define STARTUP_JOB(jobFunc) STARTUP_JOB_WEIGHTED(jobFunc, 1.f)
//////////////////////////////////////////////////////////////////////
UOLSAssetManager::UOLSAssetManager()
{
DefaultPawnData = nullptr;
}
UOLSAssetManager& UOLSAssetManager::Get()
{
check(GEngine);
if (UOLSAssetManager* Singleton = Cast<UOLSAssetManager>(GEngine->AssetManager))
{
return *Singleton;
}
OLS_LOG_NO_WORLD(LogOLSAssetManager, Fatal, TEXT("Invalid AssetManagerClassName in DefaultEngine.ini. It must be set to LyraAssetManager!"));
// Fatal error above prevents this from being called.
return *NewObject<UOLSAssetManager>();
}
void UOLSAssetManager::DumpLoadedAssets()
{
OLS_LOG_NO_WORLD(LogOLSAssetManager, Log, TEXT("========== Start Dumping Loaded Assets =========="));
for (const UObject* loadedAsset : Get().LoadedAssets)
{
OLS_LOG_NO_WORLD(LogOLSAssetManager, Log, TEXT(" %s"), GET_UOBJECT_NAME(loadedAsset));
}
OLS_LOG_NO_WORLD(LogOLSAssetManager, Log, TEXT("... %d assets in loaded pool"), Get().LoadedAssets.Num());
OLS_LOG_NO_WORLD(LogOLSAssetManager, Log, TEXT("========== Finish Dumping Loaded Assets =========="));
}
const UOLSGameDataAsset& UOLSAssetManager::GetGameData()
{
return GetOrLoadTypedGameData<UOLSGameDataAsset>(OLSGameDataPath);
}
const UOLSPawnDataAsset* UOLSAssetManager::GetDefaultPawnData() const
{
return GetAsset(DefaultPawnData);
}
template <typename GameDataClass>
const GameDataClass& UOLSAssetManager::GetOrLoadTypedGameData(const TSoftObjectPtr<GameDataClass>& dataPath)
{
if (const TObjectPtr<UPrimaryDataAsset>* 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()));
}
UObject* UOLSAssetManager::SynchronousLoadAsset(const FSoftObjectPath& assetPath)
{
if (assetPath.IsValid())
{
TUniquePtr<FScopeLogTime> logTimePtr;
if (ShouldLogAssetLoads())
{
logTimePtr = MakeUnique<FScopeLogTime>(*FString::Printf(TEXT("Synchronously loaded asset [%s]"), *assetPath.ToString()), nullptr, FScopeLogTime::ScopeLog_Seconds);
}
if (UAssetManager::IsInitialized())
{
return UAssetManager::GetStreamableManager().LoadSynchronous(assetPath, false);
}
// Use LoadObject if asset manager isn't ready yet.
return assetPath.TryLoad();
}
return nullptr;
}
bool UOLSAssetManager::ShouldLogAssetLoads()
{
static bool shouldLogAssetLoads = FParse::Param(FCommandLine::Get(), TEXT("LogAssetLoads"));
return shouldLogAssetLoads;
}
void UOLSAssetManager::AddLoadedAsset(const UObject* Asset)
{
if (ensureAlways(Asset))
{
FScopeLock LoadedAssetsLock(&LoadedAssetsCritical);
LoadedAssets.Add(Asset);
}
}
void UOLSAssetManager::StartInitialLoading()
{
SCOPED_BOOT_TIMING("UOLSAssetManager::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();
}
#if WITH_EDITOR
void UOLSAssetManager::PreBeginPIE(bool shouldStartSimulate)
{
Super::PreBeginPIE(shouldStartSimulate);
{
FScopedSlowTask slowTask(0, NSLOCTEXT("OLSEditor", "BeginLoadingPIEData", "Loading PIE Data"));
const bool shouldShowCancelButton = false;
const bool shouldAllowInPIE = true;
slowTask.MakeDialog(shouldShowCancelButton, shouldAllowInPIE);
const UOLSGameDataAsset& LocalGameDataCommon = GetGameData();
// Intentionally after GetGameData to avoid counting GameData time in this timer
SCOPE_LOG_TIME_IN_SECONDS(TEXT("PreBeginPIE asset preloading complete"), nullptr);
// You could add preloading of anything else needed for the experience we'll be using here
// (e.g., by grabbing the default experience from the world settings + the experience override in developer settings)
}
}
#endif
UPrimaryDataAsset* UOLSAssetManager::LoadGameDataOfClass(
TSubclassOf<UPrimaryDataAsset> dataClass,
const TSoftObjectPtr<UPrimaryDataAsset>& dataClassPath,
FPrimaryAssetType primaryAssetType)
{
UPrimaryDataAsset* asset = nullptr;
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Loading GameData Object"), STAT_GameData, STATGROUP_LoadTime);
if (!dataClassPath.IsNull())
{
#if WITH_EDITOR
FScopedSlowTask slowTask(0, FText::Format(NSLOCTEXT("OLSEditor", "BeginLoadingGameDataTask", "Loading GameData {0}"), FText::FromName(dataClass->GetFName())));
const bool shouldShowCancelButton = false;
const bool shouldAllowInPIE = true;
slowTask.MakeDialog(shouldShowCancelButton, shouldAllowInPIE);
#endif
OLS_LOG(LogOLSAssetManager, Log, TEXT("Loading GameData: %%s ..."), *dataClassPath.ToString());
SCOPE_LOG_TIME_IN_SECONDS(TEXT(" ... GameData loaded!"), nullptr);
// This can be called recursively in the editor because it is called on demand from PostLoad so force a sync load for primary asset and async load the rest in that case
if (GIsEditor)
{
asset = dataClassPath.LoadSynchronous();
LoadPrimaryAssetsWithType(primaryAssetType);
}
else
{
TSharedPtr<FStreamableHandle> handle = LoadPrimaryAssetsWithType(primaryAssetType);
if (handle.IsValid())
{
handle->WaitUntilComplete(0.0f, false);
// This should always work
asset = Cast<UPrimaryDataAsset>(handle->GetLoadedAsset());
}
}
}
if (asset)
{
GameDataMap.Add(dataClass, asset);
}
else
{
// It is not acceptable to fail to load any GameData asset. It will result in soft failures that are hard to diagnose.
OLS_LOG(LogOLSAssetManager, Fatal,
TEXT(
"Failed to load GameData asset at %s. Type %s. This is not recoverable and likely means you do not have the correct data to run %s."
), *dataClassPath.ToString(), *primaryAssetType.ToString(), FApp::GetProjectName());
}
return asset;
}
void UOLSAssetManager::DoAllStartupJobs()
{
SCOPED_BOOT_TIMING("UOLSAssetManager::DoAllStartupJobs");
const double allStartupJobsStartTime = FPlatformTime::Seconds();
if (IsRunningDedicatedServer())
{
// No need for periodic progress updates, just run the jobs
for (const FOLSAssetManagerStartupJob& startupJob : StartupJobs)
{
startupJob.DoJob();
}
}
else
{
if (StartupJobs.Num() > 0)
{
float totalJobValue = 0.0f;
for (const FOLSAssetManagerStartupJob& startupJob : StartupJobs)
{
totalJobValue += startupJob.JobWeight;
}
float accumulatedJobValue = 0.0f;
for (FOLSAssetManagerStartupJob& startupJob : StartupJobs)
{
const float jobValue = startupJob.JobWeight;
startupJob.SubstepProgressDelegate.BindLambda([This = this, accumulatedJobValue, jobValue, totalJobValue](float NewProgress)
{
const float SubstepAdjustment = FMath::Clamp(NewProgress, 0.0f, 1.0f) * jobValue;
const float OverallPercentWithSubstep = (accumulatedJobValue + SubstepAdjustment) / totalJobValue;
This->UpdateInitialGameContentLoadPercent(OverallPercentWithSubstep);
});
startupJob.DoJob();
startupJob.SubstepProgressDelegate.Unbind();
accumulatedJobValue += jobValue;
UpdateInitialGameContentLoadPercent(accumulatedJobValue / totalJobValue);
}
}
else
{
UpdateInitialGameContentLoadPercent(1.0f);
}
}
StartupJobs.Empty();
OLS_LOG(LogOLSAssetManager, Display, TEXT("All startup jobs took %.2f seconds to complete"),
FPlatformTime::Seconds() - allStartupJobsStartTime);
}
void UOLSAssetManager::InitializeGameplayCueManager()
{
SCOPED_BOOT_TIMING("UOLSAssetManager::InitializeGameplayCueManager");
// @TODO: Implement this after implementing ULyraGameplayCueManager.
// ULyraGameplayCueManager* GCM = ULyraGameplayCueManager::Get();
// check(GCM);
// GCM->LoadAlwaysLoadedCues();
}
void UOLSAssetManager::UpdateInitialGameContentLoadPercent(float gameContentPercent)
{
// @TODO: Implement this function.
// Could route this to the early startup loading screen
}