// © 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 "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) 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& 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(GEngine->AssetManager)) { return *Singleton; } // @Todo replace this our custom log. // UE_LOG(LogLyra, Fatal, TEXT("Invalid AssetManagerClassName in DefaultEngine.ini. It must be set to LyraAssetManager!")); // Fatal error above prevents this from being called. return *NewObject(); } template AssetType* UOLSAssetManager::GetAsset(const TSoftObjectPtr& assetPointer, bool shouldKeepInMemory) { AssetType* loadedAsset = nullptr; const FSoftObjectPath& assetPath = assetPointer.ToSoftObjectPath(); if (assetPath.IsValid()) { loadedAsset = assetPointer.Get(); if (!loadedAsset) { loadedAsset = Cast(SynchronousLoadAsset(assetPath)); ensureAlwaysMsgf(loadedAsset, TEXT("Failed to load asset [%s]"), *assetPointer.ToString()); } if (loadedAsset && shouldKeepInMemory) { // Added to loaded asset list. Get().AddLoadedAsset(Cast(loadedAsset)); } } return loadedAsset; } template TSubclassOf UOLSAssetManager::GetSubclass(const TSoftClassPtr& assetPointer, bool shouldKeepInMemory) { TSubclassOf loadedSubclass = nullptr; const FSoftObjectPath& assetPath = assetPointer.ToSoftObjectPath(); if (assetPath.IsValid()) { loadedSubclass = assetPointer.Get(); if (!loadedSubclass) { loadedSubclass = Cast(SynchronousLoadAsset(assetPath)); ensureAlwaysMsgf(loadedSubclass, TEXT("Failed to load asset class [%s]"), *assetPointer.ToString()); } if (loadedSubclass && shouldKeepInMemory) { // Added to loaded asset list. Get().AddLoadedAsset(Cast(loadedSubclass)); } } return loadedSubclass; } void UOLSAssetManager::DumpLoadedAssets() { } const UOLSPawnDataAsset* UOLSAssetManager::GetDefaultPawnData() const { return GetAsset(DefaultPawnData); } template const GameDataClass& UOLSAssetManager::GetOrLoadTypedGameData(const TSoftObjectPtr& dataPath) { if (const TObjectPtr* pResult = GameDataMap.Find(GameDataClass::StaticClass())) { return *CastChecked(*pResult); } // Does a blocking load if needed return *CastChecked( LoadGameDataOfClass(GameDataClass::StaticClass(), dataPath, GameDataClass::StaticClass()->GetFName())); } UObject* UOLSAssetManager::SynchronousLoadAsset(const FSoftObjectPath& assetPath) { if (assetPath.IsValid()) { TUniquePtr logTimePtr; if (ShouldLogAssetLoads()) { logTimePtr = MakeUnique(*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 // @Todo uncomment this after implementing GetGameData(). // 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); // @Todo unlock this comment. // const ULyraGameData& 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 dataClass, const TSoftObjectPtr& 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 // @Todo replace this log with our custom. // UE_LOG(LogLyra, 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 handle = LoadPrimaryAssetsWithType(primaryAssetType); if (handle.IsValid()) { handle->WaitUntilComplete(0.0f, false); // This should always work asset = Cast(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. // @Todo replace this log with our custom. // UE_LOG(LogLyra, 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("ULyraAssetManager::DoAllStartupJobs"); const double AllStartupJobsStartTime = FPlatformTime::Seconds(); // if (IsRunningDedicatedServer()) // { // // No need for periodic progress updates, just run the jobs // for (const FLyraAssetManagerStartupJob& StartupJob : StartupJobs) // { // StartupJob.DoJob(); // } // } // else // { // if (StartupJobs.Num() > 0) // { // float TotalJobValue = 0.0f; // for (const FLyraAssetManagerStartupJob& StartupJob : StartupJobs) // { // TotalJobValue += StartupJob.JobWeight; // } // // float AccumulatedJobValue = 0.0f; // for (FLyraAssetManagerStartupJob& 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(); // @Todo replace this with our custom log. // UE_LOG(LogLyra, Display, TEXT("All startup jobs took %.2f seconds to complete"), FPlatformTime::Seconds() - AllStartupJobsStartTime); } void UOLSAssetManager::InitializeGameplayCueManager() { SCOPED_BOOT_TIMING("UOLSAssetManager::InitializeGameplayCueManager"); // ULyraGameplayCueManager* GCM = ULyraGameplayCueManager::Get(); // check(GCM); // GCM->LoadAlwaysLoadedCues(); } void UOLSAssetManager::UpdateInitialGameContentLoadPercent(float gameContentPercent) { // Could route this to the early startup loading screen }