// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "AssetRegistry/AssetData.h" #include "AssetRegistry/IAssetRegistry.h" #include "Containers/Array.h" #include "Containers/ArrayView.h" #include "Containers/Map.h" #include "Containers/UnrealString.h" #include "Cooker/BuildResultDependenciesMap.h" #include "Cooker/CookPackageData.h" #include "Cooker/CookTypes.h" #include "Cooker/MPCollector.h" #include "CookPackageSplitter.h" #include "Hash/Blake3.h" #include "Interfaces/ITargetPlatform.h" #include "IO/IoHash.h" #include "Misc/Optional.h" #include "Templates/RefCounting.h" #include "Templates/UniquePtr.h" #include "UObject/NameTypes.h" #include "UObject/Object.h" #include "UObject/ObjectPtr.h" #include "UObject/Package.h" #include "UObject/WeakObjectPtr.h" class ITargetPlatform; class UCookOnTheFlyServer; namespace UE::Cook { class FCookGCDiagnosticContext; } namespace UE::Cook { struct FGenerationHelper; } namespace UE::EditorDomain { struct FPackageDigest; } namespace UE::Cook::CookPackageSplitter { /** Data necessary to support the ICookPackageSplitter::FPopulateContext interface. */ struct FPopulateContextData { TArray KeepReferencedPackages; TArray ObjectsToMove; UE::Cook::FBuildResultDependenciesMap BuildResultDependencies; TConstArrayView GeneratedPackages; UPackage* OwnerPackage = nullptr; UObject* OwnerObject = nullptr; const ICookPackageSplitter::FGeneratedPackageForPopulate* TargetGeneratedPackage = nullptr; }; } // namespace UE::Cook::CookPackageSplitter::Private namespace UE::Cook { /** * Extra information about CachedObjectsInOuter that a GenerationHelper needs to know for diagnostics. * The GenerationHelper constructs an associated array (aka TMap) of these structures when it takes * over the CachedObjectsInOuter. */ struct FCachedObjectInOuterGeneratorInfo { public: /** Object->GetFullName() before it was deleted. */ FString FullName; /** Has Initialize been called on *this. */ bool bInitialized = false; /** Object->GetFlags() had RF_Public when the info was initialized. */ bool bPublic = false; /** bMovedRoot is true, or this is a child object of such a moved object. */ bool bMoved = false; /** Splitter informed us that object was moved into this package from another package. */ bool bMovedRoot = false; public: void Initialize(UObject* Object); }; /** * Struct used with ICookPackageSplitter, which contains the state information for the save of either the * Generator package or one of its Generated packages. */ struct FCookGenerationInfo { public: FCookGenerationInfo(FGenerationHelper& GenerationHelper, FPackageData& InPackageData, bool bInGenerator); bool IsCreateAsMap() const; void SetIsCreateAsMap(bool bValue); bool HasCreatedPackage() const; void SetHasCreatedPackage(bool bValue); bool HasSavedEveryPlatform() const; bool HasTakenOverCachedCookedPlatformData() const; void SetHasTakenOverCachedCookedPlatformData(bool bValue); bool HasIssuedUndeclaredMovedObjectsWarning() const; void SetHasIssuedUndeclaredMovedObjectsWarning(bool bValue); bool IsGenerator() const; void SetIsGenerator(bool bValue); bool HasCalledPopulate() const; void SetHasCalledPopulate(bool bValue); /** * Steal the list of cached objects to call BeginCacheForCookedPlatformData on from the PackageData, * and add them to this. Also add NewObjects reported by the splitter that will be moved into the package. */ void TakeOverCachedObjectsAndAddMoved(FGenerationHelper& GenerationHelper, TArray& CachedObjectsInOuter, TArray& MovedObjects); /** * Fetch all the objects currently in the package and add them to the list of objects that need * BeginCacheForCookedPlatformData. * Reports whether new objects were found. If DemotionState is not ESaveSubState::Last, * will SetSaveSubState back to DemotionState * if new objects were found, and will error exit if this demotion has happened too many times. */ EPollStatus RefreshPackageObjects(FGenerationHelper& GenerationHelper, UPackage* Package, bool& bOutFoundNewObjects, ESaveSubState DemotionState); void AddKeepReferencedPackages(FGenerationHelper& GenerationHelper, TArray& InKeepReferencedPackages); /** Create the hash for this generated package, based on dependencies and GenerationHash. */ void CreatePackageHash(); /** * If the package has legacyiterative results from a previous cook that were not invalidated by dependency changes, * test whether they need to be invalidated now and clear the results if so. Only called in legacyiterative; * incremental cooks handle invalidation by querying the TargetDomainDigest during the RequestCluster. */ void LegacyIterativeCookValidateOrClear(FGenerationHelper& GeneratorHelper, const ITargetPlatform* TargetPlatform, const FIoHash& PreviousHash, bool& bOutLegacyIterativeUnmodified); TConstArrayView GetDependencies() const; /** Return the packagename, for use in debug messages. Handles PackageData==nullptr by returning RelativePath. */ FString GetPackageName() const; /** * Reset this info to a state appropriate for an uninitialized GenerationHelper; all references to UObjects * are dropped, but information necessary even when in the uninitialized GenerationHelper state is kept. */ void Uninitialize(); public: // When adding a new variable, add it to Uninitialize as well FIoHash PackageHash; FString RelativePath; FString GeneratedRootPath; FBlake3Hash GenerationHash; TArray PackageDependencies; FBuildResultDependenciesMap BuildResultDependencies; /** Cannot be null, set in constructor */ FPackageData* PackageData; TArray> KeepReferencedPackages; TMap CachedObjectsInOuterInfo; FWorkerId SavedOnWorker = FWorkerId::Invalid(); private: struct FPlatformData { TOptional AssetPackageData; bool bHasSaved : 1; bool bIncrementallySkipped : 1; FPlatformData(); bool IsIncrementallySkipped() const; void SetIncrementallySkipped(bool bValue); bool HasSaved() const; void SetHasSaved(FGenerationHelper& GenerationHelper, FCookGenerationInfo& Info, const ITargetPlatform* TargetPlatform, bool bValue, FWorkerId SourceWorkerId); }; private: FPlatformData& FindCheckedPlatformData(const ITargetPlatform* TargetPlatform); private: TMap PlatformDatas; bool bCreateAsMap : 1; bool bHasCreatedPackage : 1; bool bTakenOverCachedCookedPlatformData : 1; bool bIssuedUndeclaredMovedObjectsWarning : 1; bool bGenerator : 1; bool bHasCalledPopulate : 1; friend UE::Cook::FGenerationHelper; }; /** * Helper that wraps an ICookPackageSplitter, gets/caches packages to generate, and is a reference-counted * collection of cached data and helper functions to save and list the generated packages. * * TODO: MultiplatformCook: We do not yet support Packages that use a GenerationHelper being cooked for some platforms * in a multiplatformcook and not others. And if the package is not incrementally skipped for one platform then it is * not incrementally skipped for any platform. We don't support per-platform incremental cooks because allowing * per-platform results would require extra tests throughout PumpSaves and the events on GenerationHelper to check for * which platforms are enabled, and we don't have those yet. We don't support per-platform reachability for the same * reason, and additionally because reachability might be discovered for one platform after the save already started * for another platform, and we would need to handle the state transition to restart the generation with the new * platform accommodated. We will eventually need to implement both per-platform incremental cooks and per-platform * reachability to fully support per-platform customization of the cook graph. See TODO_COOKGENERATIONHELPER for * code blocks where we implement the workarounds for this todo. */ struct FGenerationHelper : public FThreadSafeRefCountedObject { public: /** Store the provided CookPackageSplitter and prepare the packages to generate. */ FGenerationHelper(UE::Cook::FPackageData& InOwner); ~FGenerationHelper(); /** * Early exits if already initialized. Otherwise, loads the package if not loaded and searches it for a * splitter and creates the splitter. * If load/search/creation fails, the GenerationHelper will be set to invalid @see IsValid. * * Unless otherwise stated, all public functions call Initialize if not already initialized. */ void Initialize(); /** * Version of Initialize that receives the splitterobject from the caller rather than needing to search * for it, as an optimization for callers that have it already. If already initialized, the input Splitter * will be destroyed and the function will early exit. */ void Initialize(const UObject* InSplitDataObject, UE::Cook::Private::FRegisteredCookPackageSplitter* InRegisteredSplitterType, TUniquePtr&& InSplitter); /** Version of Initialize that sets IsValid=false. */ void InitializeAsInvalid(); /** * Clear all self references which keep this GenerationHelper from destructing. These could have been set by the * SetKeep... functions. The Owner PackageData will keep a pointer to this until all references from self, from * child generated PackageDatas, and other, are released. Note this means that we assert in the owner PackageData's * destructor if any references remain; PackageDatas can not be deleted until all such references are cleared. * Does not call Initialize. */ void ClearSelfReferences(); /** * GenerationHelpers can be created uninitialized at the start of cook, for incremental cooks. We initialize * them on demand, which requires loading the package if not already loaded. * IsInitialized reports whether initialization has been attempted. @see IsValid. * Does not call Initialize. */ bool IsInitialized() const; /** * Since GenerationHelpers can be created before we load the package (@see IsInitialized) we might incorrectly * create one for a package that does not have a splitter. If so, when we load the package and discover that, * we set status to invalid. When in this state, all public functions are still valid to call, but will be * noops and will return empty data. */ bool IsValid(); /** Accessor for the packages to generate, will be empty if invalid or if TryGenerateList was not yet called. */ TArrayView GetPackagesToGenerate(); /** Return the GenerationInfo used to save the generator package's UPackage. Does not call Initialize. */ FCookGenerationInfo& GetOwnerInfo(); const FCookGenerationInfo& GetOwnerInfo() const; /** Return owner FPackageData. Does not call Initialize. */ FPackageData& GetOwner(); /** Return the GenerationInfo for the given PackageData, or null if not found. */ FCookGenerationInfo* FindInfo(const FPackageData& PackageData); const FCookGenerationInfo* FindInfo(const FPackageData& PackageData) const; /** Return CookPackageSplitter. Will return null if !IsValid.*/ ICookPackageSplitter* GetCookPackageSplitterInstance() const; /** Return RegisteredSplitterType. Will return null if !IsValid. */ UE::Cook::Private::FRegisteredCookPackageSplitter* GetRegisteredSplitterType() const; /** Return the SplitDataObject's FullObjectPath. Will return NAME_None if !IsValid. */ const FName GetSplitDataObjectName() const; /** Return the Splitter's value for virtual bool UseInternalReferenceToAvoidGarbageCollect(). */ bool IsUseInternalReferenceToAvoidGarbageCollect() const; /** * Return the Splitter's value for virtual bool RequiresGeneratorPackageDestructBeforeResplit(). Returns false if not * initialized. Does not call Initialize. */ bool IsRequiresGeneratorPackageDestructBeforeResplit() const; /** Return the Splitter's value for virtual bool DoesGeneratedRequireGenerator(). */ ICookPackageSplitter::EGeneratedRequiresGenerator DoesGeneratedRequireGenerator() const; /** Return the pointer to the SplitDataObject. Returns null if not in memory or marked as garbage. */ UObject* GetWeakSplitDataObject(); /** * Return our cached pointer to the SplitDataObject if set. If not set, load the package and find it. Can still * return null if !IsValid or if not found even after loading the package. */ UObject* FindOrLoadSplitDataObject(); /** Return the ObjectsToMove that were returned for the generator package. Does not call initialize. */ TConstArrayView GetOwnerObjectsToMove() const; /** Find the OwnerPackage in memory, returns null if invalid or not already loaded. Does not call Initialize. */ UPackage* GetOwnerPackage(); /** Load the OwnerPackage if not already loaded. Can still return null if invalid or package fails to load. */ UPackage* FindOrLoadOwnerPackage(UCookOnTheFlyServer& COTFS); /** * Return a reference to ExternalActors that were discovered during save and stored on this. * Does not call initialize. */ TConstArrayView GetExternalActorDependencies(); /** * Return the ExternalActors that were discovered during save and stored on this, and clear the values. * Does not call initialize. */ TArray ReleaseExternalActorDependencies(); /** Call the Splitter's GetGenerateList and create the PackageDatas. Logs errors and returns false on failure. */ bool TryGenerateList(); /** * Call the Splitter's PopulateGeneratorPackage if not yet called. Assumes GenerateList has been called. Logs * errors and returns false on failure. */ bool TryCallPopulateGeneratorPackage( TArray& InOutGeneratedPackagesForPresave); /** * Call the Splitter's PopulateGeneratedPackage if not yet called. Assumes GenerateList has been called. Logs * errors and returns false on failure. */ bool TryCallPopulateGeneratedPackage(UE::Cook::FCookGenerationInfo& Info, TArray& OutObjectsToMove); /** * Mark that the SavePackage of the Owner is starting. Keeps a reference to keep the generator alive until save * is finished. */ void StartOwnerSave(); /** Update state before we queue the generated packages, e.g. mark whether packages are incrementally skippable. */ void StartQueueGeneratedPackages(UCookOnTheFlyServer& COTFS); /** * Mark that we have started queuing packages. Automatically called from StartQueueGeneratedPackages, but also * is called on the director in response to a discovered generated package from a CookWorker. It sets the * WorkerId so that it is available during assignment of the discovered generated packages. */ void NotifyStartQueueGeneratedPackages(UCookOnTheFlyServer& COTFS, FWorkerId SourceWorkerId); /** Update state after we queue generated packages; e.g. register to stay alive until the packages are assigned. */ void EndQueueGeneratedPackages(UCookOnTheFlyServer& COTFS); /** Does not call Initialize. */ void EndQueueGeneratedPackagesOnDirector(UCookOnTheFlyServer& COTFS, FWorkerId SourceWorkerId); /** * Called on Director when the RequestFence added from SetKeepForIncremental or EndQueueGeneratedPackages has passed. * Calls the proper followup events locally and on all Workers. Does not call Initialize. */ void OnRequestFencePassed(UCookOnTheFlyServer& COTFS); /** * Called on Directors and Workers when the RequestFence added from EndQueueGeneratedPackages has passed. * Does not call Initialize. */ void OnQueuedGeneratedPackagesFencePassed(UCookOnTheFlyServer& COTFS); /** Call CreatePackage and set package header data; or empty it and normalize it if it already exists. */ UPackage* TryCreateGeneratedPackage(FCookGenerationInfo& GenerationInfo, bool bResetToEmpty); /** * Called when a generator package is in FSaveCookedPackageContext::FinishPlatformSave. * Calculates AssetRegistryData. */ void FinishGeneratorPlatformSave(FPackageData& PackageData, bool bFirstPlatform, TArray& OutPackageDependencies); /** * Called when a generated package is in FSaveCookedPackageContext::FinishPlatformSave. * Calculates AssetRegistryData. */ void FinishGeneratedPlatformSave(FPackageData& PackageData, const ITargetPlatform* TargetPlatform, FAssetPackageData& OutAssetPackageData, TArray& OutARDependencies, FBuildResultDependenciesMap& OutBuildResultDependencies); /** * Return the AssetPackageData that was created for the given generated PackageName, or loaded for it from a * previous cook. Will return nullptr if not loaded from a previous incremental cook and it has not yet been saved. * Returns the AssetPackageData for the given platform, or for the shared data between platforms if nullptr. * Does not call initialize. */ const FAssetPackageData* GetAssetPackageData(FName PackageName, const ITargetPlatform* TargetPlatform); /** * Returns GetAssetPackageData for each known platform, returning the first non-null value, or null if no values * found. */ const FAssetPackageData* GetAssetPackageDataAnyPlatform(FName PackageName); /** * Calculate the PackageDigest for the given generated packagename and given platform based on its * AssetPackageData (e.g. list of classes in the package) in a previous cook. * TargetPlatform specifies which platform to look for results, or nullptr to look up results recorded for the * shared data between platforms. Returns empty digest if not loaded from a previous incremental cook and it has not * yet been saved. * Does not call initialize. */ UE::EditorDomain::FPackageDigest GetPackageDigest(FName PackageName, const ITargetPlatform* TargetPlatform); /** * Clear any data that should only be held when an FPackageData is in the save state, for the given Info. The given * Info might specify the generator package or one of the generated packages. */ void ResetSaveState(FCookGenerationInfo& Info, UPackage* Package, EStateChangeReason ReleaseSaveReason, EPackageState NewState); /** * Called when a package is retracted from a CookWorker, or from the Director's local worker. Default behavior * for a retracted pacakge is to demote the package to idle, but if the state of the package is too advanced to * recreate without a garbage collect, then the GenerationHelper instead needs to stall the package in case it * is assigned back to this worker later. */ bool ShouldRetractionStallRatherThanDemote(FPackageData& PackageData); /** * Record all dependencies from the generator package that are ExternalActor dependencies and * store them on this. */ void FetchExternalActorDependencies(); /** Incremental cook: store the list of generated packages from the last cook. Does not call Initialize. */ void SetPreviousGeneratedPackages(const ITargetPlatform* TargetPlatform, TMap&& Packages); /** Return the information set by SetPreviousGeneratedPackages if not yet cleared. Does not call Initialize. */ const TMap& GetPreviousGeneratedPackages(const ITargetPlatform* TargetPlatform) const; /** * Callback during garbage collection. Does not call initialize. Caller must pass in a refcount to show * a guarantee that clearing internal references will not delete before function return. */ void PreGarbageCollect(const TRefCountPtr& RefcountHeldByCaller, FPackageData& PackageData, TArray>& GCKeepObjects, TArray& GCKeepPackages, TArray& GCKeepPackageDatas, bool& bOutShouldDemote); /** * Callback during garbage collection. Does not call initialize. Caller must pass in a refcount to show * a guarantee that clearing internal references will not delete before function return. */ void PostGarbageCollect(const TRefCountPtr& RefcountHeldByCaller, FCookGCDiagnosticContext& Context); /** * Called from PackageData function of the same name to decide whether to demote the package out of save. * Does not call Initialize. */ void UpdateSaveAfterGarbageCollect(const FPackageData& PackageData, bool& bInOutDemote); // Self-references that keep this GenerationHelper in memory and referenced from the packages that use it until // the self-references are cleared. These Set/Clear functions do not call initialize. void SetKeepForIncremental(const ITargetPlatform* TargetPlatform); void SetKeepForIncrementalAllPlatforms(); void ClearKeepForIncremental(const ITargetPlatform* TargetPlatform); void ClearKeepForIncrementalAllPlatforms(); void SetKeepForQueueResults(); void ClearKeepForQueueResults(); bool IsWaitingForQueueResults() const; void SetKeepForGeneratorSave(const ITargetPlatform* TargetPlatform); void SetKeepForGeneratorSaveAllPlatforms(); void ClearKeepForGeneratorSave(const ITargetPlatform* TargetPlatform); void ClearKeepForGeneratorSaveAllPlatforms(); void SetKeepForAllSavedOrGC(); void ClearKeepForAllSavedOrGC(); void SetKeepForCompletedAllSavesMessage(); void ClearKeepForCompletedAllSavesMessage(); /** * Helper for assignment of generated packages in MPCook. Return the id of the CookWorker that saved the * generator, to decide where to assign the generated packages. */ FWorkerId GetWorkerIdThatSavedGenerator() const; /** * Helper for assignment of generated packages in MPCook. A counter for assigned generated packages that is * needed by some assignment schemes. */ int32& GetMPCookNextAssignmentIndex(); /** * Called for each of the generated packages that were discovered when TryGenerateList was called on a * remote CookWorker. Does not call Initialize. */ void TrackGeneratedPackageListedRemotely(UCookOnTheFlyServer& COTFS, FPackageData& PackageData, const FIoHash& CurrentPackageHash); /** * Called on the director when the generator or one of the generated packages was saved on a remote worker. * Used to manage KeepForGCOrAllSaved lifetime. Does not call Initialize. */ void MarkPackageSavedRemotely(UCookOnTheFlyServer& COTFS, FPackageData& PackageData, const ITargetPlatform* TargetPlatform, FWorkerId SourceWorkerId); /** Called when a Package is demoted to idle, to update the saved count for it. Does not call Initialize. */ void SetAllPlatformsSaved(FPackageData& PackageData, FCookGenerationInfo* Info); /** Scope object that blocks calls to OnAllSavesCompleted. Checks and calls it if necessary when destructed. */ struct FScopeDeferEvents { public: FScopeDeferEvents(const TRefCountPtr& InGenerationHelper); ~FScopeDeferEvents(); private: TRefCountPtr GenerationHelper; bool bOldDeferEvents = false; }; /** * Called on the director when the generator or one of the generated packages was found to be incrementally skipped * and marked already cooked in an incremental cook. Does not call Initialize. */ void MarkPackageIncrementallySkipped(FPackageData& PackageData, const ITargetPlatform* TargetPlatform, bool bIncrementallySkipped); /** * Called on the director and every CookWorker when all saves have been completed; this indicates that * some of our contract points are complete and the splitter can be destroyed. Does not call Initialize. */ void OnAllSavesCompleted(UCookOnTheFlyServer& COTFS); /** Diagnostics for shutdown errors. Report why the GenerationHelper is still referenced. */ void DiagnoseWhyNotShutdown(); /** * Helper for shutdown errors. Force the GenerationHelper to uninitialize so that the CookPackageSplitter * is shutdown correctly before the cooksession ends. */ void ForceUninitialize(); /** Helper function for Initialize and for TryCreateValidGenerationHelper. */ static void SearchForRegisteredSplitDataObject(UCookOnTheFlyServer& COTFS, FName PackageName, UPackage* Package, TOptional> CachedObjectsInOuter, UObject*& OutSplitDataObject, UE::Cook::Private::FRegisteredCookPackageSplitter*& OutRegisteredSplitterType, TUniquePtr& OutSplitterInstance, bool bCookedPlatformDataIsLoaded, bool& bOutNeedWaitForIsLoaded); /** Helper function for Initialize and for TryCreateValidGenerationHelper. */ static UPackage* FindOrLoadPackage(UCookOnTheFlyServer& COTFS, FPackageData& OwnerPackageData); /** If PackageData is in a stalled state, verify that its state is valid for CookDirector or CookWorker. */ static void ValidateSaveStalledState(UCookOnTheFlyServer& COTFS, FPackageData& PackageData, const TCHAR* Caller); /** Initialize settings from commandline and config files. */ static void SetBeginCookConfigSettings(); /** Debug setting that forces an otherwise unspecified order. Generator packages save before the generated do. */ static bool IsGeneratorSavedFirst(); /** Debug setting that forces an otherwise unspecified order. Generated packages save before their generator does. */ static bool IsGeneratedSavedFirst(); /** * An API to access data that's only available on the CookDirector (or SingleProcess cook). This is present for * readability at callsites, it just accesses data on the GenerationHelper. */ struct FDirectorAPI { public: /** Report whether NotifyStartQueueGeneratedPackages has been called. Returns false on CookWorkers. */ bool HasStartedQueueGeneratedPackages() const; private: explicit FDirectorAPI(FGenerationHelper& InGenerationHelper); FDirectorAPI(const FDirectorAPI&) = delete; FDirectorAPI(FDirectorAPI&&) = delete; FGenerationHelper& GenerationHelper; friend struct FGenerationHelper; }; FDirectorAPI GetDirectorAPI() const; private: enum class EInitializeStatus : uint8 { Uninitialized, Invalid, Valid, }; struct FPlatformData { TMap PreviousGeneratedPackages; TRefCountPtr ReferenceFromKeepForIncremental; TRefCountPtr ReferenceFromKeepForGeneratorSave; int32 NumSaved = 0; void SetKeepForIncremental(FGenerationHelper& GenerationHelper, const ITargetPlatform* TargetPlatform); void ClearKeepForIncremental(FGenerationHelper& GenerationHelper, const ITargetPlatform* TargetPlatform); void SetKeepForGeneratorSave(FGenerationHelper& GenerationHelper, const ITargetPlatform* TargetPlatform); void ClearKeepForGeneratorSave(FGenerationHelper& GenerationHelper, const ITargetPlatform* TargetPlatform); }; private: FPlatformData& FindCheckedPlatformData(const ITargetPlatform* TargetPlatform); const FPlatformData& FindCheckedPlatformData(const ITargetPlatform* TargetPlatform) const; void ConditionalInitialize() const; FCookGenerationInfo* FindInfoNoInitialize(const FPackageData& PackageData); FCookGenerationInfo* FindInfoNoInitialize(FName PackageName); void NotifyCompletion(ICookPackageSplitter::ETeardown Status); void PreGarbageCollectGCLifetimeData(); void PostGarbageCollectGCLifetimeData(FCookGCDiagnosticContext& Context); void Uninitialize(); void ModifyNumSaved(const ITargetPlatform* TargetPlatform, int32 Delta); void OnNumSavedUpdated(); void VerifyGeneratorPackageGarbageCollected(FCookGCDiagnosticContext& Context); void DemoteStalledPackages(UCookOnTheFlyServer& COTFS, bool bFromAllSavesCompleted); private: // When adding a new variable, add it to Uninitialize as well /** PackageData for the package that is being split */ FCookGenerationInfo OwnerInfo; FWeakObjectPtr SplitDataObject; /** Name of the object that prompted the splitter creation */ FName SplitDataObjectName; UE::Cook::Private::FRegisteredCookPackageSplitter* RegisteredSplitterType = nullptr; TUniquePtr CookPackageSplitterInstance; /** Recorded list of packages to generate from the splitter, and data we need about them */ TArray PackagesToGenerate; TWeakObjectPtr OwnerPackage; TMap PlatformDatas; TArray ExternalActorDependencies; TArray OwnerObjectsToMove; TRefCountPtr ReferenceFromKeepForQueueResults; TRefCountPtr ReferenceFromKeepForAllSavedOrGC; int32 MPCookNextAssignmentIndex = 0; EInitializeStatus InitializeStatus = EInitializeStatus::Uninitialized; ICookPackageSplitter::EGeneratedRequiresGenerator DoesGeneratedRequireGeneratorValue = ICookPackageSplitter::EGeneratedRequiresGenerator::None; bool bUseInternalReferenceToAvoidGarbageCollect = false; bool bRequiresGeneratorPackageDestructBeforeResplit = false; bool bGeneratedList = false; bool bCurrentGCHasKeptGeneratorPackage = false; bool bCurrentGCHasKeptGeneratorKeepPackages = false; bool bKeepForAllSavedOrGC = false; bool bKeepForCompletedAllSavesMessage = false; bool bNeedConfirmGeneratorPackageDestroyed = false; bool bHasFinishedQueueGeneratedPackages = false; bool bDeferEvents = false; bool bSentAllSavesCompleted = false; friend FCookGenerationInfo; }; /////////////////////////////////////////////////////// // Inline implementations /////////////////////////////////////////////////////// inline FCookGenerationInfo::FPlatformData& FCookGenerationInfo::FindCheckedPlatformData( const ITargetPlatform* TargetPlatform) { FPlatformData* PlatformData = PlatformDatas.Find(TargetPlatform); if (!PlatformData) { UE_LOG(LogCook, Error, TEXT("FCookGenerationInfo::FindCheckedPlatformData called for %s on %s, but doesn't already have it. This is a cook programming error."), *TargetPlatform->PlatformName(), *GetPackageName()); FDebug::DumpStackTraceToLog(ELogVerbosity::Warning); PlatformData = &PlatformDatas.FindOrAdd(TargetPlatform); } return *PlatformData; } inline bool FCookGenerationInfo::IsCreateAsMap() const { return bCreateAsMap; } inline void FCookGenerationInfo::SetIsCreateAsMap(bool bValue) { bCreateAsMap = bValue; } inline bool FCookGenerationInfo::HasCreatedPackage() const { return bHasCreatedPackage; } inline void FCookGenerationInfo::SetHasCreatedPackage(bool bValue) { bHasCreatedPackage = bValue; } inline bool FCookGenerationInfo::HasSavedEveryPlatform() const { bool bHasSaved = true; for (const TPair& InfoPlatformPair : PlatformDatas) { bHasSaved &= InfoPlatformPair.Value.bHasSaved; } return bHasSaved; } inline bool FCookGenerationInfo::HasTakenOverCachedCookedPlatformData() const { return bTakenOverCachedCookedPlatformData; } inline void FCookGenerationInfo::SetHasTakenOverCachedCookedPlatformData(bool bValue) { bTakenOverCachedCookedPlatformData = bValue; } inline bool FCookGenerationInfo::HasIssuedUndeclaredMovedObjectsWarning() const { return bIssuedUndeclaredMovedObjectsWarning; } inline void FCookGenerationInfo::SetHasIssuedUndeclaredMovedObjectsWarning(bool bValue) { bIssuedUndeclaredMovedObjectsWarning = bValue; } inline bool FCookGenerationInfo::IsGenerator() const { return bGenerator; } inline void FCookGenerationInfo::SetIsGenerator(bool bValue) { bGenerator = bValue; } inline bool FCookGenerationInfo::HasCalledPopulate() const { return bHasCalledPopulate; } inline void FCookGenerationInfo::SetHasCalledPopulate(bool bValue) { bHasCalledPopulate = bValue; } inline TConstArrayView FCookGenerationInfo::GetDependencies() const { return PackageDependencies; } inline FString FCookGenerationInfo::GetPackageName() const { return PackageData->GetPackageName().ToString(); } inline FCookGenerationInfo::FPlatformData::FPlatformData() : bHasSaved(false), bIncrementallySkipped(false) { } inline bool FCookGenerationInfo::FPlatformData::IsIncrementallySkipped() const { return bIncrementallySkipped; } inline void FCookGenerationInfo::FPlatformData::SetIncrementallySkipped(bool bValue) { bIncrementallySkipped = bValue; } inline bool FCookGenerationInfo::FPlatformData::HasSaved() const { return bHasSaved; } inline void FCookGenerationInfo::FPlatformData::SetHasSaved(FGenerationHelper& GenerationHelper, FCookGenerationInfo& Info, const ITargetPlatform* TargetPlatform, bool bValue, FWorkerId SourceWorkerId) { if (bValue != bHasSaved) { bHasSaved = bValue; GenerationHelper.ModifyNumSaved(TargetPlatform, bValue ? 1 : -1); if (bHasSaved) { Info.SavedOnWorker = SourceWorkerId; } } } inline FGenerationHelper::FPlatformData& FGenerationHelper::FindCheckedPlatformData( const ITargetPlatform* TargetPlatform) { FPlatformData* PlatformData = PlatformDatas.Find(TargetPlatform); if (!PlatformData) { UE_LOG(LogCook, Error, TEXT("FGenerationHelper::FindCheckedPlatformData called for %s on %s, but doesn't already have it. This is a cook programming error."), *TargetPlatform->PlatformName(), *GetOwner().GetPackageName().ToString()); FDebug::DumpStackTraceToLog(ELogVerbosity::Warning); PlatformData = &PlatformDatas.FindOrAdd(TargetPlatform); } return *PlatformData; } inline const FGenerationHelper::FPlatformData& FGenerationHelper::FindCheckedPlatformData( const ITargetPlatform* TargetPlatform) const { return const_cast(*this).FindCheckedPlatformData(TargetPlatform); } inline bool FGenerationHelper::IsInitialized() const { return InitializeStatus != EInitializeStatus::Uninitialized; } inline void FGenerationHelper::ConditionalInitialize() const { if (InitializeStatus == EInitializeStatus::Uninitialized) { // Use a const cast so we can call from getter functions that are otherwise const. const_cast(*this).Initialize(); } } inline bool FGenerationHelper::IsValid() { ConditionalInitialize(); return InitializeStatus == EInitializeStatus::Valid; } inline TArrayView FGenerationHelper::GetPackagesToGenerate() { ConditionalInitialize(); return PackagesToGenerate; } inline UE::Cook::FCookGenerationInfo& FGenerationHelper::GetOwnerInfo() { return OwnerInfo; } inline const UE::Cook::FCookGenerationInfo& FGenerationHelper::GetOwnerInfo() const { return OwnerInfo; } inline UE::Cook::FPackageData& FGenerationHelper::GetOwner() { return *OwnerInfo.PackageData; } inline ICookPackageSplitter* FGenerationHelper::GetCookPackageSplitterInstance() const { ConditionalInitialize(); return CookPackageSplitterInstance.Get(); } inline UE::Cook::Private::FRegisteredCookPackageSplitter* FGenerationHelper::GetRegisteredSplitterType() const { ConditionalInitialize(); return RegisteredSplitterType; } inline const FName FGenerationHelper::GetSplitDataObjectName() const { ConditionalInitialize(); return SplitDataObjectName; } inline bool FGenerationHelper::IsUseInternalReferenceToAvoidGarbageCollect() const { ConditionalInitialize(); return bUseInternalReferenceToAvoidGarbageCollect; } inline bool FGenerationHelper::IsRequiresGeneratorPackageDestructBeforeResplit() const { return bRequiresGeneratorPackageDestructBeforeResplit; } inline ICookPackageSplitter::EGeneratedRequiresGenerator FGenerationHelper::DoesGeneratedRequireGenerator() const { ConditionalInitialize(); return DoesGeneratedRequireGeneratorValue; } inline TConstArrayView FGenerationHelper::GetOwnerObjectsToMove() const { return OwnerObjectsToMove; } inline TConstArrayView FGenerationHelper::GetExternalActorDependencies() { return ExternalActorDependencies; } inline TArray FGenerationHelper::ReleaseExternalActorDependencies() { TArray Result = MoveTemp(ExternalActorDependencies); ExternalActorDependencies.Empty(); return Result; } inline const TMap& FGenerationHelper::GetPreviousGeneratedPackages( const ITargetPlatform* TargetPlatform) const { return FindCheckedPlatformData(TargetPlatform).PreviousGeneratedPackages; } inline void FGenerationHelper::FPlatformData::SetKeepForIncremental(FGenerationHelper& GenerationHelper, const ITargetPlatform* TargetPlatform) { if (!ReferenceFromKeepForIncremental) { UE_LOG(LogCookGenerationHelper, Verbose, TEXT("%s: SetKeepForIncremental for platform %s."), *WriteToString<256>(GenerationHelper.GetOwner().GetPackageName()), *TargetPlatform->PlatformName()); ReferenceFromKeepForIncremental = &GenerationHelper; } } inline void FGenerationHelper::SetKeepForIncremental(const ITargetPlatform* TargetPlatform) { FindCheckedPlatformData(TargetPlatform).SetKeepForIncremental(*this, TargetPlatform); } inline void FGenerationHelper::SetKeepForIncrementalAllPlatforms() { for (TPair& PlatformPair : PlatformDatas) { PlatformPair.Value.SetKeepForIncremental(*this, PlatformPair.Key); } } inline void FGenerationHelper::FPlatformData::ClearKeepForIncremental(FGenerationHelper& GenerationHelper, const ITargetPlatform* TargetPlatform) { if (ReferenceFromKeepForIncremental) { UE_LOG(LogCookGenerationHelper, Verbose, TEXT("%s: ClearKeepForIncremental for platform %s."), *WriteToString<256>(GenerationHelper.GetOwner().GetPackageName()), *TargetPlatform->PlatformName()); ReferenceFromKeepForIncremental.SafeRelease(); } } inline void FGenerationHelper::ClearKeepForIncremental(const ITargetPlatform* TargetPlatform) { FindCheckedPlatformData(TargetPlatform).ClearKeepForIncremental(*this, TargetPlatform); } inline void FGenerationHelper::ClearKeepForIncrementalAllPlatforms() { for (TPair& PlatformPair : PlatformDatas) { PlatformPair.Value.ClearKeepForIncremental(*this, PlatformPair.Key); } } inline void FGenerationHelper::SetKeepForQueueResults() { if (!ReferenceFromKeepForQueueResults) { UE_LOG(LogCookGenerationHelper, Verbose, TEXT("%s: SetKeepForQueueResults."), *WriteToString<256>(GetOwner().GetPackageName())); ReferenceFromKeepForQueueResults = this; } } inline void FGenerationHelper::ClearKeepForQueueResults() { if (ReferenceFromKeepForQueueResults) { UE_LOG(LogCookGenerationHelper, Verbose, TEXT("%s: ClearKeepForQueueResults."), *WriteToString<256>(GetOwner().GetPackageName())); ReferenceFromKeepForQueueResults.SafeRelease(); } } inline bool FGenerationHelper::IsWaitingForQueueResults() const { return ReferenceFromKeepForQueueResults.IsValid(); } inline void FGenerationHelper::FPlatformData::SetKeepForGeneratorSave(FGenerationHelper& GenerationHelper, const ITargetPlatform* TargetPlatform) { if (!ReferenceFromKeepForGeneratorSave) { UE_LOG(LogCookGenerationHelper, Verbose, TEXT("%s: SetKeepForGeneratorSave for platform %s."), *WriteToString<256>(GenerationHelper.GetOwner().GetPackageName()), *TargetPlatform->PlatformName()); ReferenceFromKeepForGeneratorSave = &GenerationHelper; } } inline void FGenerationHelper::SetKeepForGeneratorSave(const ITargetPlatform* TargetPlatform) { FindCheckedPlatformData(TargetPlatform).SetKeepForGeneratorSave(*this, TargetPlatform); } inline void FGenerationHelper::SetKeepForGeneratorSaveAllPlatforms() { for (TPair& PlatformPair : PlatformDatas) { PlatformPair.Value.SetKeepForGeneratorSave(*this, PlatformPair.Key); } } inline void FGenerationHelper::FPlatformData::ClearKeepForGeneratorSave(FGenerationHelper& GenerationHelper, const ITargetPlatform* TargetPlatform) { if (ReferenceFromKeepForGeneratorSave) { UE_LOG(LogCookGenerationHelper, Verbose, TEXT("%s: ClearKeepForGeneratorSave for platform %s."), *WriteToString<256>(GenerationHelper.GetOwner().GetPackageName()), *TargetPlatform->PlatformName()); ReferenceFromKeepForGeneratorSave.SafeRelease(); } } inline void FGenerationHelper::ClearKeepForGeneratorSave(const ITargetPlatform* TargetPlatform) { FindCheckedPlatformData(TargetPlatform).ClearKeepForGeneratorSave(*this, TargetPlatform); } inline void FGenerationHelper::ClearKeepForGeneratorSaveAllPlatforms() { for (TPair& PlatformPair : PlatformDatas) { PlatformPair.Value.ClearKeepForGeneratorSave(*this, PlatformPair.Key); } } inline void FGenerationHelper::SetKeepForAllSavedOrGC() { if (!bKeepForAllSavedOrGC) { UE_LOG(LogCookGenerationHelper, Verbose, TEXT("%s: SetKeepForAllSavedOrGC."), *WriteToString<256>(GetOwner().GetPackageName())); ReferenceFromKeepForAllSavedOrGC = this; bKeepForAllSavedOrGC = true; } else { // If bKeepForAllSavedOrGC, ReferenceFromKeepForAllSavedOrGC must be true as well. check(ReferenceFromKeepForAllSavedOrGC); } } inline void FGenerationHelper::ClearKeepForAllSavedOrGC() { if (bKeepForAllSavedOrGC) { UE_LOG(LogCookGenerationHelper, Verbose, TEXT("%s: ClearKeepForAllSavedOrGC."), *WriteToString<256>(GetOwner().GetPackageName())); bKeepForAllSavedOrGC = false; if (!bKeepForAllSavedOrGC && !bKeepForCompletedAllSavesMessage) { ReferenceFromKeepForAllSavedOrGC.SafeRelease(); } } else { // ReferenceFromKeepForAllSavedOrGC must only be set if at least one of bKeepForAllSavedOrGC or bKeepForCompletedAllSavesMessage is set check(bKeepForAllSavedOrGC || bKeepForCompletedAllSavesMessage || !ReferenceFromKeepForAllSavedOrGC); } } inline void FGenerationHelper::SetKeepForCompletedAllSavesMessage() { if (!bKeepForCompletedAllSavesMessage) { UE_LOG(LogCookGenerationHelper, Verbose, TEXT("%s: SetKeepForCompletedAllSavesMessage."), *WriteToString<256>(GetOwner().GetPackageName())); ReferenceFromKeepForAllSavedOrGC = this; bKeepForCompletedAllSavesMessage = true; } else { // If bKeepForCompletedAllSavesMessage, ReferenceFromKeepForAllSavedOrGC must be true as well. check(ReferenceFromKeepForAllSavedOrGC); } } inline void FGenerationHelper::ClearKeepForCompletedAllSavesMessage() { if (bKeepForCompletedAllSavesMessage) { UE_LOG(LogCookGenerationHelper, Verbose, TEXT("%s: ClearKeepForCompletedAllSavesMessage."), *WriteToString<256>(GetOwner().GetPackageName())); bKeepForCompletedAllSavesMessage = false; if (!bKeepForAllSavedOrGC && !bKeepForCompletedAllSavesMessage) { ReferenceFromKeepForAllSavedOrGC.SafeRelease(); } } else { // ReferenceFromKeepForAllSavedOrGC must only be set if at least one of bKeepForAllSavedOrGC or bKeepForCompletedAllSavesMessage is set check(bKeepForAllSavedOrGC || bKeepForCompletedAllSavesMessage || !ReferenceFromKeepForAllSavedOrGC); } } inline FWorkerId FGenerationHelper::GetWorkerIdThatSavedGenerator() const { return OwnerInfo.SavedOnWorker; } inline int32& FGenerationHelper::GetMPCookNextAssignmentIndex() { return MPCookNextAssignmentIndex; } inline void FGenerationHelper::SetAllPlatformsSaved(FPackageData& PackageData, FCookGenerationInfo* Info) { if (!Info) { // Note that we expect this Find to succeed for generated packages even without initialization. // In the non-incremental case, the GenerationHelper must have already initialized, // or the generated PackageData would never exist. In the incremental case, the Info was added // during PopulateCookedSandbox to prepare the generator package for incremental cook. Info = FindInfoNoInitialize(PackageData); } if (Info) { for (TPair& PlatformPair : Info->PlatformDatas) { const ITargetPlatform* TargetPlatform = PlatformPair.Key; FCookGenerationInfo::FPlatformData& InfoPlatformData = PlatformPair.Value; InfoPlatformData.SetHasSaved(*this, *Info, TargetPlatform, true /* bValue */, FWorkerId::Local()); } } } inline FGenerationHelper::FDirectorAPI FGenerationHelper::GetDirectorAPI() const { return FDirectorAPI(const_cast(*this)); } inline FGenerationHelper::FDirectorAPI::FDirectorAPI(FGenerationHelper& InGenerationHelper) : GenerationHelper(InGenerationHelper) { } } // namespace UE::Cook