// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= DerivedDataCacheCommandlet.cpp: Commandlet for DDC maintenence =============================================================================*/ #include "Commandlets/DerivedDataCacheCommandlet.h" #include "Algo/RemoveIf.h" #include "Algo/StableSort.h" #include "Algo/Transform.h" #include "AssetCompilingManager.h" #include "CollectionManagerModule.h" #include "CollectionManagerTypes.h" #include "CookOnTheSide/CookOnTheFlyServer.h" #include "DerivedDataCacheInterface.h" #include "DistanceFieldAtlas.h" #include "Editor.h" #include "EditorWorldUtils.h" #include "Engine/Texture.h" #include "GlobalShader.h" #include "ICollectionContainer.h" #include "ICollectionManager.h" #include "Interfaces/ITargetPlatform.h" #include "Interfaces/ITargetPlatformManagerModule.h" #include "LevelInstance/LevelInstanceSubsystem.h" #include "MeshCardRepresentation.h" #include "Misc/ConfigCacheIni.h" #include "Misc/PackageAccessTrackingOps.h" #include "Misc/PackageName.h" #include "Misc/RedirectCollector.h" #include "PackageHelperFunctions.h" #include "Settings/ProjectPackagingSettings.h" #include "ShaderCompiler.h" #include "TextureEncodingSettings.h" #include "UObject/CoreRedirects.h" #include "UObject/Package.h" #include "UObject/UObjectHash.h" #include "UObject/UObjectIterator.h" #include "WorldPartition/WorldPartition.h" #include "WorldPartition/WorldPartitionHelpers.h" #include "WorldPartition/WorldPartitionSubsystem.h" #include "WorldPartition/WorldPartitionActorDescInstance.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(DerivedDataCacheCommandlet) DEFINE_LOG_CATEGORY_STATIC(LogDerivedDataCacheCommandlet, Log, All); class UDerivedDataCacheCommandlet::FObjectReferencer : public FGCObject { public: FObjectReferencer(TMap, FCachingData>& InReferencedObjects) : ReferencedObjects(InReferencedObjects) { } private: void AddReferencedObjects(FReferenceCollector& Collector) override { Collector.AllowEliminatingReferences(false); Collector.AddReferencedObjects(ReferencedObjects); Collector.AllowEliminatingReferences(true); } FString GetReferencerName() const override { return TEXT("UDerivedDataCacheCommandlet"); } FString ReferencerName; TMap, FCachingData>& ReferencedObjects; }; class UDerivedDataCacheCommandlet::FPackageListener : public FUObjectArray::FUObjectCreateListener, public FUObjectArray::FUObjectDeleteListener { public: FPackageListener() { GUObjectArray.AddUObjectDeleteListener(this); GUObjectArray.AddUObjectCreateListener(this); // We might be late to the party, check if some UPackage already have been created for (TObjectIterator PackageIter; PackageIter; ++PackageIter) { NewPackages.Add(*PackageIter); } } ~FPackageListener() { GUObjectArray.RemoveUObjectDeleteListener(this); GUObjectArray.RemoveUObjectCreateListener(this); } TSet& GetNewPackages() { return NewPackages; } virtual SIZE_T GetAllocatedSize() const override { return NewPackages.GetAllocatedSize(); } private: void NotifyUObjectCreated(const class UObjectBase* Object, int32 Index) override { if (Object->GetClass() == UPackage::StaticClass()) { NewPackages.Add(const_cast(static_cast(Object))); } } void NotifyUObjectDeleted(const class UObjectBase* Object, int32 Index) override { if (Object->GetClass() == UPackage::StaticClass()) { NewPackages.Remove(const_cast(static_cast(Object))); } } void OnUObjectArrayShutdown() override { GUObjectArray.RemoveUObjectDeleteListener(this); GUObjectArray.RemoveUObjectCreateListener(this); } TSet NewPackages; }; UDerivedDataCacheCommandlet::UDerivedDataCacheCommandlet(FVTableHelper& Helper) : Super(Helper) { } UDerivedDataCacheCommandlet::~UDerivedDataCacheCommandlet() { } UDerivedDataCacheCommandlet::UDerivedDataCacheCommandlet(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { LogToConsole = false; } void UDerivedDataCacheCommandlet::MaybeMarkPackageAsAlreadyLoaded(UPackage* Package) { if (ProcessedPackages.Contains(Package->GetFName())) { UE_LOG(LogDerivedDataCacheCommandlet, Verbose, TEXT("Marking %s already loaded."), *Package->GetName()); Package->SetPackageFlags(PKG_ReloadingForCooker); } } namespace UE::Private::DerivedDataCacheCommandlet { static void TickCookObjects(bool bCookComplete = false) { constexpr double TickPeriod = 0.1; static double LastTickTime = FPlatformTime::Seconds(); const double CurrentTime = FPlatformTime::Seconds(); if (bCookComplete || LastTickTime + TickPeriod <= CurrentTime) { TRACE_CPUPROFILER_EVENT_SCOPE(TickCookObjects); FTickableCookObject::TickObjects(static_cast(CurrentTime - LastTickTime), bCookComplete); LastTickTime = CurrentTime; } } static void WaitForCompilationToFinish(bool& bInOutHadActivity) { auto LogStatus = [](IAssetCompilingManager* CompilingManager) { int32 AssetCount = CompilingManager->GetNumRemainingAssets(); if (AssetCount > 0) { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("Waiting for %d %s to finish."), AssetCount, *FText::Format(CompilingManager->GetAssetNameFormat(), FText::AsNumber(AssetCount)).ToString()); } else { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("Done waiting for %s to finish."), *FText::Format(CompilingManager->GetAssetNameFormat(), FText::AsNumber(100)).ToString()); } }; while (FAssetCompilingManager::Get().GetNumRemainingAssets() > 0) { TickCookObjects(); for (IAssetCompilingManager* CompilingManager : FAssetCompilingManager::Get().GetRegisteredManagers()) { int32 CachedAssetCount = CompilingManager->GetNumRemainingAssets(); if (CachedAssetCount) { bInOutHadActivity = true; LogStatus(CompilingManager); int32 NumCompletedAssetsSinceLastLog = 0; while (CompilingManager->GetNumRemainingAssets() > 0) { const int32 CurrentAssetCount = CompilingManager->GetNumRemainingAssets(); NumCompletedAssetsSinceLastLog += (CachedAssetCount - CurrentAssetCount); CachedAssetCount = CurrentAssetCount; if (NumCompletedAssetsSinceLastLog >= 1000) { LogStatus(CompilingManager); NumCompletedAssetsSinceLastLog = 0; } // Process any asynchronous Asset compile results that are ready, limit execution time FAssetCompilingManager::Get().ProcessAsyncTasks(true); } LogStatus(CompilingManager); } } } } } // UE::Private::DerivedDataCacheCommandlet void UDerivedDataCacheCommandlet::CacheLoadedPackages(UPackage* CurrentPackage, uint8 PackageFilter, TSet& OutNewProcessedPackages) { TRACE_CPUPROFILER_EVENT_SCOPE(UDerivedDataCacheCommandlet::CacheLoadedPackages); const double BeginCacheTimeStart = FPlatformTime::Seconds(); // We will only remove what we process from the list to avoid unprocessed package being forever forgotten. TSet& NewPackages = PackageListener->GetNewPackages(); TArray ObjectsWithOuter; for (auto NewPackageIt = NewPackages.CreateIterator(); NewPackageIt; ++NewPackageIt) { UPackage* NewPackage = *NewPackageIt; const FName NewPackageName = NewPackage->GetFName(); if (!ProcessedPackages.Contains(NewPackageName)) { if ((PackageFilter & NORMALIZE_ExcludeEnginePackages) != 0 && NewPackage->GetName().StartsWith(TEXT("/Engine"))) { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("Skipping %s as Engine package"), *NewPackageName.ToString()); // Add it so we don't convert the FName to a string everytime we encounter this package ProcessedPackages.Add(NewPackageName); NewPackageIt.RemoveCurrent(); } else if (NewPackage == CurrentPackage || !PackagesToProcess.Contains(NewPackageName)) { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("Processing %s"), *NewPackageName.ToString()); ProcessedPackages.Add(NewPackageName); OutNewProcessedPackages.Add(NewPackageName); NewPackageIt.RemoveCurrent(); ObjectsWithOuter.Reset(); GetObjectsWithOuter(NewPackage, ObjectsWithOuter, true /* bIncludeNestedObjects */, RF_ClassDefaultObject /* ExclusionFlags */); UE_TRACK_REFERENCING_PACKAGE_SCOPED(NewPackage, PackageAccessTrackingOps::NAME_CookerBuildObject); for (UObject* Object : ObjectsWithOuter) { FCachingData& CachingData = CachingObjects.FindOrAdd(Object); if (!CachingData.ObjectValidityReference.IsValid()) { CachingData = FCachingData(); CachingData.ObjectValidityReference = Object; } // For texture ddc fills, we want to stagger the platforms so that the base texture is only encoded // once with shared linear texture encoding. If we kick everything off at the same time then all worker // tasks ask for the linear encoding at the same time. If we queue the other platforms after the first one // is done, then the subsequent platforms can retrieve that encoded texture and avoid a lot of work. // // This only actually helps if some of the platforms you're filling have tiling - otherwise // you're delaying the other platforms for no reason. When I did some measuring this had no visible effect // on ddc fill times - there's enough work to fill up the space, so rather than add a complicated system // for determining if the platform will utilize it, we just always do it. if (bSharedLinearTextureEncodingEnabled && Platforms.Num() > 1 && Object->IsA()) { CachingData.bFirstPlatformIsSolo = true; } for (auto Platform : Platforms) { Object->BeginCacheForCookedPlatformData(Platform); if (CachingData.bFirstPlatformIsSolo) { // We will start the other platforms later. break; } } CachingData.PlatformIsComplete.Init(false, Platforms.Num()); } } } else { NewPackageIt.RemoveCurrent(); } } BeginCacheTime += FPlatformTime::Seconds() - BeginCacheTimeStart; ProcessCachingObjects(); } bool UDerivedDataCacheCommandlet::ProcessCachingObjects() { TRACE_CPUPROFILER_EVENT_SCOPE(UDerivedDataCacheCommandlet::ProcessCachingObjects); bool bHadActivity = false; if (CachingObjects.Num() > 0) { FAssetCompilingManager::Get().ProcessAsyncTasks(true); double CurrentTime = FPlatformTime::Seconds(); for (auto It = CachingObjects.CreateIterator(); It; ++It) { // Call IsCachedCookedPlatformDataLoaded once a second per object since it can be quite expensive FCachingData& CachingData = It->Value; if (CurrentTime - CachingData.LastTimeTested > 1.0) { if (!It->Value.ObjectValidityReference.IsValid()) { It.RemoveCurrent(); continue; } UObject* Object = It->Key; bool bIsFinished = true; const IInterface_AsyncCompilation* Interface_AsyncCompilation = Cast(Object); if (Interface_AsyncCompilation && Interface_AsyncCompilation->IsCompiling()) { bIsFinished = false; } { UE_TRACK_REFERENCING_PACKAGE_SCOPED(Object, PackageAccessTrackingOps::NAME_CookerBuildObject); for (int32 PlatformIndex = 0; PlatformIndex < Platforms.Num(); ++PlatformIndex) { // IsCachedCookedPlatformDataLoaded can be quite slow for some objects // Do not call it if bIsFinished is already false // Also, by the contract of IsCachedCookedPlatformDataLoaded, we are not allowed to call it // again once it returns true if (bIsFinished && !CachingData.PlatformIsComplete[PlatformIndex]) { const ITargetPlatform* Platform = Platforms[PlatformIndex]; if (Object->IsCachedCookedPlatformDataLoaded(Platform)) { CachingData.PlatformIsComplete[PlatformIndex] = true; bHadActivity = true; if (CachingData.bFirstPlatformIsSolo) { bIsFinished = false; CachingData.bFirstPlatformIsSolo = false; // Start the remaining platforms now. We only launched PlatformIndex == 0. check(PlatformIndex == 0); check(Platforms.Num() > 1); // we shouldn't be here for only 1 platform for (int32 AddingPlatformIndex = 1; AddingPlatformIndex < Platforms.Num(); AddingPlatformIndex++) { Object->BeginCacheForCookedPlatformData(Platforms[AddingPlatformIndex]); } } } else { bIsFinished = false; } } } } if (bIsFinished) { bHadActivity = true; Object->WillNeverCacheCookedPlatformDataAgain(); Object->ClearAllCachedCookedPlatformData(); It.RemoveCurrent(); } else { CachingData.LastTimeTested = CurrentTime; } } } } return bHadActivity; } void UDerivedDataCacheCommandlet::FinishCachingObjects() { // Timing variables double DDCCommandletMaxWaitSeconds = 60. * 10.; GConfig->GetDouble(TEXT("CookSettings"), TEXT("DDCCommandletMaxWaitSeconds"), DDCCommandletMaxWaitSeconds, GEditorIni); const double FinishCacheTimeStart = FPlatformTime::Seconds(); double LastActivityTime = FinishCacheTimeStart; while (CachingObjects.Num() > 0) { UE::Private::DerivedDataCacheCommandlet::TickCookObjects(); bool bHadActivity = ProcessCachingObjects(); double CurrentTime = FPlatformTime::Seconds(); if (!bHadActivity) { UE::Private::DerivedDataCacheCommandlet::WaitForCompilationToFinish(bHadActivity); } if (!bHadActivity) { if (CurrentTime - LastActivityTime >= DDCCommandletMaxWaitSeconds) { UObject* Object = CachingObjects.CreateIterator()->Value.ObjectValidityReference.Get(); UE_LOG(LogDerivedDataCacheCommandlet, Warning, TEXT("Timed out for %.2lfs waiting for %d objects to finish caching. First object: %s."), DDCCommandletMaxWaitSeconds, CachingObjects.Num(), Object ? *Object->GetFullName() : TEXT("Unknown deleted object")); break; } else { const double WaitingForCacheSleepTime = 0.050; FPlatformProcess::Sleep(WaitingForCacheSleepTime); } } else { LastActivityTime = CurrentTime; } } FinishCacheTime += FPlatformTime::Seconds() - FinishCacheTimeStart; } void UDerivedDataCacheCommandlet::CacheWorldPackages(UWorld* World, uint8 PackageFilter, TSet& OutNewProcessedPackages) { if (!UWorld::IsPartitionedWorld(World)) { return; } // Setup the world UWorld::InitializationValues IVS; IVS.RequiresHitProxies(false); IVS.ShouldSimulatePhysics(false); IVS.EnableTraceCollision(false); IVS.CreateNavigation(false); IVS.CreateAISystem(false); IVS.AllowAudioPlayback(false); IVS.CreatePhysicsScene(true); FScopedEditorWorld EditorWorld(World, IVS); // If the world is partitioned // Ensure the world has a valid world partition. UWorldPartition* WorldPartition = World->GetWorldPartition(); check(WorldPartition); FWorldPartitionHelpers::ForEachActorWithLoading(WorldPartition, [this, PackageFilter, &OutNewProcessedPackages](const FWorldPartitionActorDescInstance* ActorDescInstance) { if (AActor* Actor = ActorDescInstance->GetActor()) { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("Loaded actor %s"), *Actor->GetName()); CacheLoadedPackages(Actor->GetPackage(), PackageFilter, OutNewProcessedPackages); } return true; }); } int32 UDerivedDataCacheCommandlet::Main( const FString& Params ) { // Avoid putting those directly in the constructor because we don't // want the CDO to have a second copy of these being active. PackageListener = MakeUnique(); ObjectReferencer = MakeUnique(CachingObjects); TArray Tokens, Switches; ParseCommandLine(*Params, Tokens, Switches); bool bFillCache = Switches.Contains("FILL"); // do the equivalent of a "loadpackage -all" to fill the DDC bool bStartupOnly = Switches.Contains("STARTUPONLY"); // regardless of any other flags, do not iterate packages const bool bDryRun = Switches.Contains("DRYRUN"); // build a list of stuff to process but don't start loading any packages // Subsets for parallel processing uint32 SubsetMod = 0; uint32 SubsetTarget = MAX_uint32; FParse::Value(*Params, TEXT("SubsetMod="), SubsetMod); FParse::Value(*Params, TEXT("SubsetTarget="), SubsetTarget); bool bDoSubset = SubsetMod > 0 && SubsetTarget < SubsetMod; if (FResolvedTextureEncodingSettings::Get().Project.bSharedLinearTextureEncoding) { bSharedLinearTextureEncodingEnabled = true; } double FindProcessedPackagesTime = 0.0; double GCTime = 0.0; FinishCacheTime = 0.; BeginCacheTime = 0.; ITargetPlatformManagerModule* TPM = GetTargetPlatformManager(); Platforms.Reset(); Platforms.Append(TPM->GetActiveTargetPlatforms()); if (!bStartupOnly && bFillCache) { FCoreUObjectDelegates::PackageCreatedForLoad.AddUObject(this, &UDerivedDataCacheCommandlet::MaybeMarkPackageAsAlreadyLoaded); Tokens.Empty(2); FString MapList; if(FParse::Value(*Params, TEXT("Map="), MapList)) { for(int StartIdx = 0; StartIdx < MapList.Len();) { int EndIdx = StartIdx; while(EndIdx < MapList.Len() && MapList[EndIdx] != '+') { EndIdx++; } Tokens.Add(MapList.Mid(StartIdx, EndIdx - StartIdx) + FPackageName::GetMapPackageExtension()); StartIdx = EndIdx + 1; } } // support MapIniSection parameter { TArray MapIniSections; FString SectionStr; if (FParse::Value(*Params, TEXT("MAPINISECTION="), SectionStr)) { if (SectionStr.Contains(TEXT("+"))) { TArray Sections; SectionStr.ParseIntoArray(Sections, TEXT("+"), true); for (int32 Index = 0; Index < Sections.Num(); Index++) { MapIniSections.Add(Sections[Index]); } } else { MapIniSections.Add(SectionStr); } TArray MapsFromIniSection; for (const FString& MapIniSection : MapIniSections) { GEditor->LoadMapListFromIni(*MapIniSection, MapsFromIniSection); } Tokens += MapsFromIniSection; } } TArray CommandLinePackageNames; // Allow adding collections to the list of packages to process if (FString CollectionArg; FParse::Value(*Params, TEXT("COLLECTION="), CollectionArg)) { ICollectionManager& CollectionManager = FModuleManager::LoadModuleChecked("CollectionManager").Get(); TArray Collections; CollectionArg.ParseIntoArray(Collections, TEXT("+")); for (const FString& Collection : Collections) { TSharedPtr CollectionContainer; FName CollectionName; ECollectionShareType::Type ShareType = ECollectionShareType::CST_All; if (!CollectionManager.TryParseCollectionPath(Collection, &CollectionContainer, &CollectionName, &ShareType) || !CollectionContainer->CollectionExists(CollectionName, ShareType)) { UE_LOG(LogDerivedDataCacheCommandlet, Error, TEXT("Found no collections for command line argument %s"), *Collection); continue; } TArray FoundAssets; CollectionContainer->GetAssetsInCollection(CollectionName, ShareType, FoundAssets, ECollectionRecursionFlags::SelfAndChildren); Tokens.Reserve(Tokens.Num() + FoundAssets.Num()); for (const FSoftObjectPath& AssetPath : FoundAssets) { CommandLinePackageNames.Add(AssetPath.GetLongPackageName()); } } } // Add defaults if we haven't specifically found anything on the command line if (Tokens.IsEmpty() && CommandLinePackageNames.IsEmpty()) { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("Adding default search tokens for all assets and maps")); Tokens.Add(FString("*") + FPackageName::GetAssetPackageExtension()); Tokens.Add(FString("*") + FPackageName::GetMapPackageExtension()); } uint8 PackageFilter = NORMALIZE_DefaultFlags; if ( Switches.Contains(TEXT("MAPSONLY")) ) { PackageFilter |= NORMALIZE_ExcludeContentPackages; } if ( Switches.Contains(TEXT("PROJECTONLY")) ) { PackageFilter |= NORMALIZE_ExcludeEnginePackages; } if ( !Switches.Contains(TEXT("DEV")) ) { PackageFilter |= NORMALIZE_ExcludeDeveloperPackages; } if ( !Switches.Contains(TEXT("NOREDIST")) ) { PackageFilter |= NORMALIZE_ExcludeNoRedistPackages; } // assume the first token is the map wildcard/pathname TSet FilesInPath; TArray Unused; TArray TokenFiles; for ( int32 TokenIndex = 0; TokenIndex < Tokens.Num(); TokenIndex++ ) { TokenFiles.Reset(); if ( !NormalizePackageNames( Unused, TokenFiles, Tokens[TokenIndex], PackageFilter) ) { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("No packages found for parameter %i: '%s'"), TokenIndex, *Tokens[TokenIndex]); continue; } FilesInPath.Append(TokenFiles); } TArray> PackagePaths; PackagePaths.Reserve(FilesInPath.Num()); for (FString& Filename : FilesInPath) { FString PackageName; FString FailureReason; if (!FPackageName::TryConvertFilenameToLongPackageName(Filename, PackageName, &FailureReason)) { UE_LOG(LogDerivedDataCacheCommandlet, Error, TEXT("Unable to resolve filename %s to package name because: %s"), *Filename, *FailureReason); continue; } PackagePaths.Emplace(MoveTemp(Filename), FName(*PackageName)); } if (!CommandLinePackageNames.IsEmpty()) { if (!NormalizePackageNames(CommandLinePackageNames, Unused, TEXT(""), PackageFilter)) { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("Failed to normalize command line package names")); } else { for( const FString& PackageName : CommandLinePackageNames ) { FString Filename; if (FPackageName::DoesPackageExist(PackageName, &Filename)) { PackagePaths.Emplace(MoveTemp(Filename), FName(*PackageName)); } else { UE_LOG(LogDerivedDataCacheCommandlet, Warning, TEXT("Unable to resolve filename from package name %s"), *PackageName); continue; } } } } // Respect settings that instruct us not to enumerate some paths TArray LocalDirsToNotSearch; const UProjectPackagingSettings* const PackagingSettings = GetDefault(); for (const FDirectoryPath& DirToNotSearch : PackagingSettings->TestDirectoriesToNotSearch) { FString LocalPath; if (FPackageName::TryConvertGameRelativePackagePathToLocalPath(DirToNotSearch.Path, LocalPath)) { LocalDirsToNotSearch.Add(LocalPath); } else { UE_LOG(LogCook, Warning, TEXT("'ProjectSettings -> Project -> Packaging -> Test directories to not search' has invalid element '%s'"), *DirToNotSearch.Path); } } TArray LocalFilenamesToSkip; if (FPackageName::FindPackagesInDirectories(LocalFilenamesToSkip, LocalDirsToNotSearch)) { TSet PackageNamesToSkip; Algo::Transform(LocalFilenamesToSkip, PackageNamesToSkip, [](const FString& Filename) { FString PackageName; if (FPackageName::TryConvertFilenameToLongPackageName(Filename, PackageName)) { return FName(*PackageName); } return FName(NAME_None); }); int32 NewNum = Algo::StableRemoveIf(PackagePaths, [&PackageNamesToSkip](const TPair& PackagePath) { return PackageNamesToSkip.Contains(PackagePath.Get<1>()); }); PackagePaths.SetNum(NewNum); } if (PackagePaths.Num() == 0) { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("No packages found to load from command line arguments.")); } else { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("%d packages to load from command line arguments"), PackagePaths.Num()); for( int32 Index=0; Index < PackagePaths.Num(); ++Index) { const TPair& Pair = PackagePaths[Index]; UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT(" %d) %s"), Index + 1, *Pair.Get<1>().ToString()); } } for (int32 Index = 0; Index < Platforms.Num(); Index++) { TArray DesiredShaderFormats; Platforms[Index]->GetAllTargetedShaderFormats(DesiredShaderFormats); for (int32 FormatIndex = 0; FormatIndex < DesiredShaderFormats.Num(); FormatIndex++) { const EShaderPlatform ShaderPlatform = ShaderFormatToLegacyShaderPlatform(DesiredShaderFormats[FormatIndex]); // Kick off global shader compiles for each target platform. Note that shader platform alone is not sufficient to distinguish between WindowsEditor and WindowsClient, which after UE 4.25 have different DDC CompileGlobalShaderMap(ShaderPlatform, Platforms[Index], false); // Block to wait for global shaders for this platform to complete before submitting new jobs. // Only a single platform/target's global shader jobs can be in progress at the same time (otherwise these conflict with each other) // so block after each call to CompileGlobalShaderMap. GShaderCompilingManager->ProcessAsyncResults(false, true); check(GGlobalShaderMap[ShaderPlatform]->IsComplete(Platforms[Index])); } } const int32 GCInterval = 100; int32 NumProcessedSinceLastGC = 0; bool bLastPackageWasMap = false; // Mark command-line packages as already discovered so we don't double-add from soft refs and can avoid loading packages on other shards PackagesToProcess.Empty(PackagePaths.Num()); for (int32 PackageIndex = PackagePaths.Num() - 1; PackageIndex >= 0; PackageIndex--) { PackagesToProcess.Add(PackagePaths[PackageIndex].Get<1>()); } // Add all soft object references from no asset in particular to the packages to be processed, before filtering in the case of distributed work { int32 StartingPackageCount = PackagePaths.Num(); TSet SoftReferencedPackages; GRedirectCollector.ProcessSoftObjectPathPackageList(NAME_None, false, SoftReferencedPackages); for (FName SoftRefName : SoftReferencedPackages) { if (PackagesToProcess.Contains(SoftRefName)) { continue; } FString SoftRefFilename; if (FPackageName::DoesPackageExist(SoftRefName.ToString(), &SoftRefFilename)) { PackagePaths.Push(TPair(SoftRefFilename, SoftRefName)); PackagesToProcess.Add(SoftRefName); } } if (StartingPackageCount == PackagePaths.Num()) { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("No packages found to load from startup soft references.")); } else { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("%d packages to load from startup soft references"), PackagePaths.Num() - StartingPackageCount); for (int32 Index = StartingPackageCount; Index < PackagePaths.Num(); ++Index) { const TPair& Pair = PackagePaths[Index]; UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT(" %d) %s"), Index + 1 - StartingPackageCount, *Pair.Get<1>().ToString()); } } } // Sort maps to the end of the list of packages to process to maximize the chance of sharded instances populating the DDC from individual packages. Algo::StableSortBy(PackagePaths, [](TPair& Pair){ return Pair.Get<0>().EndsWith(FPackageName::GetMapPackageExtension()); }, TLess() ); // If work is distributed, skip packages that are meant to be process by other machines // Do this before the main loop so that we don't filter soft refs that we enqueue if (bDoSubset) { PackagePaths.RemoveAll([SubsetMod, SubsetTarget](const TPair& P) { FName PackageFName = P.Value; FString PackageName = PackageFName.ToString(); if (FCrc::StrCrc_DEPRECATED(*PackageName.ToUpper()) % SubsetMod != SubsetTarget) { return true; } return false; }); if (PackagePaths.Num() == 0) { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("No packages to process after subset split!")); } else { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("%d packages to load after subset split"), PackagePaths.Num()); for (int32 Index = 0; Index < PackagePaths.Num(); ++Index) { const TPair& Pair = PackagePaths[Index]; UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT(" %d) %s"), Index + 1, *Pair.Get<1>().ToString()); } } } if (bDryRun) { PackagePaths.Empty(); } // Process each package int32 PackageOrder = 0; while( PackagePaths.Num()) { TTuple PackagePath = PackagePaths.Pop(); const FString& Filename = PackagePath.Get<0>(); FName PackageFName = PackagePath.Get<1>(); if (ProcessedPackages.Contains(PackageFName)) { // Soft refs may be queued, then processed as a hard ref from something else. continue; } UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("Loading (%d) %s"), ++PackageOrder, *Filename); UPackage* Package = LoadPackage(NULL, *Filename, LOAD_None); if (Package == NULL) { UE_LOG(LogDerivedDataCacheCommandlet, Error, TEXT("Error loading %s!"), *Filename); bLastPackageWasMap = false; } else { bLastPackageWasMap = Package->ContainsMap(); NumProcessedSinceLastGC++; } // Find any new packages and cache all the objects in each package TSet NewProcessedPackages; CacheLoadedPackages(Package, PackageFilter, NewProcessedPackages); // Ensure we load maps to process all their referenced packages in case they are using world partition. if (bLastPackageWasMap) { if (UWorld* World = UWorld::FindWorldInPackage(Package)) { CacheWorldPackages(World, PackageFilter, NewProcessedPackages); } } // Queue up soft references of each package we just processed NewProcessedPackages.Add(NAME_None); // Always check for more references from non-asset systems each step for( FName NewProcessedPackage : NewProcessedPackages) { TSet SoftReferencedPackages; GRedirectCollector.ProcessSoftObjectPathPackageList(NewProcessedPackage, false, SoftReferencedPackages); for (FName SoftRefName : SoftReferencedPackages) { // Packages may already be enqueued on this or another machine if (!PackagesToProcess.Contains(SoftRefName) && !ProcessedPackages.Contains(SoftRefName)) { PackagesToProcess.Add(SoftRefName); FString SoftRefFilename; FCoreRedirectObjectName CoreRedirectName; CoreRedirectName.PackageName = SoftRefName; // Packages that are specifically identified by PackageRedirects as removed need to be skipped. if (FCoreRedirects::IsKnownMissing(ECoreRedirectFlags::Type_Package, CoreRedirectName)) { continue; } if (FPackageName::DoesPackageExist(SoftRefName.ToString(), &SoftRefFilename)) { UE_LOG(LogDerivedDataCacheCommandlet, Log, TEXT("Package '%s' queueing soft reference '%s' for later processing"), *NewProcessedPackage.ToString(), *SoftRefName.ToString()); PackagePaths.Push(TPair(SoftRefFilename, SoftRefName)); } else { UE_LOG(LogDerivedDataCacheCommandlet, Warning, TEXT("Package '%s' failed to find soft reference '%s'"), *NewProcessedPackage.ToString(), *SoftRefName.ToString()); } } else { UE_LOG(LogDerivedDataCacheCommandlet, Verbose, TEXT("Package '%s' skipping soft reference '%s': %s, %s "), *NewProcessedPackage.ToString(), *SoftRefName.ToString(), PackagesToProcess.Contains(SoftRefName) ? TEXT("ALREADY QUEUED") : TEXT("NOT QUEUED"), ProcessedPackages.Contains(SoftRefName) ? TEXT("ALREADY PROCESSED") : TEXT("NOT PROCESSED") ); } } } UE::Private::DerivedDataCacheCommandlet::TickCookObjects(); // Perform a GC if conditions are met if (NumProcessedSinceLastGC >= GCInterval || PackagePaths.IsEmpty() || bLastPackageWasMap) { const double StartGCTime = FPlatformTime::Seconds(); if (NumProcessedSinceLastGC >= GCInterval || PackagePaths.IsEmpty()) { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("GC (Full)...")); CollectGarbage(RF_NoFlags); NumProcessedSinceLastGC = 0; } else { UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("GC...")); CollectGarbage(RF_Standalone); } GCTime += FPlatformTime::Seconds() - StartGCTime; bLastPackageWasMap = false; } } } FinishCachingObjects(); UE::Private::DerivedDataCacheCommandlet::TickCookObjects(/*bCookComplete*/ true); GetDerivedDataCacheRef().WaitForQuiescence(true); UE_LOG(LogDerivedDataCacheCommandlet, Display, TEXT("BeginCacheTime=%.2lfs, FinishCacheTime=%.2lfs, GCTime=%.2lfs."), BeginCacheTime, FinishCacheTime, GCTime); return 0; }