// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #if WITH_EDITOR #include "AssetRegistry/IAssetRegistry.h" #include "Containers/Array.h" #include "Containers/ArrayView.h" #include "Containers/List.h" #include "Containers/StringView.h" #include "Containers/UnrealString.h" #if UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_6 #include "CoreMinimal.h" #endif #include "Hash/Blake3.h" #include "HAL/PlatformCrt.h" #include "HAL/PreprocessorHelpers.h" #include "Misc/Optional.h" #include "UObject/NameTypes.h" #include "UObject/Object.h" #include "UObject/ObjectMacros.h" class UClass; class UObject; class UPackage; struct FAssetDependency; template class TFunctionRef; namespace UE::Cook { class FCookDependency; } namespace UE::Cook::CookPackageSplitter { struct FPopulateContextData; } /** * This class is used for packages that need to be split into multiple runtime packages. * It provides the instructions to the cooker for how to split the package. */ class ICookPackageSplitter { public: // Static API functions - these static functions are referenced by REGISTER_COOKPACKAGE_SPLITTER // before creating an instance of the class. /** * Return whether IsCachedCookedPlatformDataLoaded needs to return true for all UObjects in the * generator package before ShouldSplit or GetGenerateList can be called. If true this slows down * our ability to parallelize the cook of the generated packages. */ static bool RequiresCachedCookedPlatformDataBeforeSplit() { return false; } /** Return whether the CookPackageSplitter subclass should handle the given SplitDataClass instance. */ static bool ShouldSplit(UObject* SplitData) { return false; } /** Return DebugName for this SplitterClass in cook log messages. */ static FString GetSplitterDebugName() { return TEXT(""); } // Virtual API functions - functions called from the cooker after creating the splitter. virtual ~ICookPackageSplitter() { } enum class ETeardown { Complete, Canceled, }; /** Do teardown actions after all packages have saved, or when the cook is cancelled. Always called before destruction. */ virtual void Teardown(ETeardown Status) { } /** * If true, this splitter forces the Generator package objects it needs to remain referenced, and the cooker * should expect them to still be in memory after a garbage collect so long as the splitter is alive. */ virtual bool UseInternalReferenceToAvoidGarbageCollect() { return false; } /** * An ICookPackageSplitter for a single generator package normally is constructed only once and handles * all generated packages for that generator, but during MPCook in cases of load balancing between CookWorkers, * it is possible that the original splitter is destructed but then recreated later. This is guaranteed not * to happen without a GarbageCollection pass in between, but that GarbageCollection may fail to destruct the * generator package if it is still referenced from other packages or systems. Depending on the ICookPackageSplitter's * implemenation, this failure to GC might cause an error, because changes made from the previous splitter are not * handled in the next splitter. If RequiresGeneratorPackageDestructBeforeResplit is true, the cooker will log this failure * to GC the generator package as an error. */ virtual bool RequiresGeneratorPackageDestructBeforeResplit() { return false; } /** * Return value for the DoesGeneratedRequireGenerator function. All levels behave correctly, but provide * different tradeoffs of guarantees to the splitter versus performance. */ enum class EGeneratedRequiresGenerator : uint8 { /** * GetGenerateList will be called before PopulateGeneratedPackage. PopulateGenerator and PreSaveGenerator * might or might not be called before. OutKeepReferencedPackages from PopulateGenerator will not be kept * referenced after PostSaveGenerator. Best for performance. */ None, /** * GetGenerateList and PopulateGenerator will be called before PopulateGeneratedPackage. * OutKeepReferencedPackages from PopulateGenerator will be kept referenced until all generated and generator * packages call PostSave or until the splitter is destroyed. Performance cost: Possible extra calls to * PopulateGeneratedPackage, possible unnecessary memory increase due to OutKeepReferencedPackages. */ Populate, /** * GetGenerateList PopulateGenerator, PreSaveGenerator, and PostSaveGenerator will be called before * PopulateGeneratedPackage. Performance cost: Progress on generated packages will be delayed until generator * finishes saving. Possible unnecessary memory increase due to OutKeepReferencedPackages. Retraction is not * possible in MPCook for the generated packages; they must all be saved on the same CookWorker that saves the * generator. */ Save, Count, }; /** * Return capability setting which indicates which splitter functions acting on the parent generator package must * be called on the splitter before splitter functions acting on the generated packages can be called. Also impacts * the lifetime of memory guarantees for the generator functions. @see EGeneratedRequiresGenerator. Default is * EGeneratedRequiresGenerator::None, which provides the best performance but the fewest guarantees. * * Examples of dependencies and what capability level should be used: * ShouldSplit call reads data that is written by BeginCacheForCookedPlatformData: * EGeneratedRequiresGenerator::Save * PopulateGeneratedPackage or PreSaveGeneratedPackage read data that is written by PopulateGeneratorPackage: * EGeneratedRequiresGenerator::Populate */ virtual EGeneratedRequiresGenerator DoesGeneratedRequireGenerator() { return EGeneratedRequiresGenerator::None; } /** Data sent to the cooker to describe each desired generated package */ struct FGeneratedPackage { /** Parent path for the generated package. If empty, uses the generator's package path. */ FString GeneratedRootPath; /** Generated package relative to /_Generated_. */ FString RelativePath; UE_DEPRECATED(5.3, "Write to PackageDependencies instead") TArray Dependencies; /** * Source packages outside of the generator package that will be incorporated into the generated * package (e.g. ExternalActor packages). These are used to construct the PackageSavedHash for the generated * package. Some objects use the PackageSavedHash during derived data construction as a change marker, adding * the source packages here is important for those types to work. * * These packages are also recorded as dependencies in the AssetRegistry generated by the cook. * * During incrementalcook, changes to these packages cause the recook of the generated package, but that * invalidation can also be accomplished without the other effects, and with more types of dependencies, using * PopulateContext.ReportSaveDependency during PopulateGeneratedPackage and PreSaveGeneratedPackage. */ TArray PackageDependencies; /** * Hash of the data used to construct the generated package that is not covered by the dependencies. * Changes to this hash will cause invalidation of the package during incremental cooks. */ FBlake3Hash GenerationHash; /* GetGenerateList must specify true if the package will be a map (.umap, contains a UWorld or ULevel), else false */ void SetCreateAsMap(bool bInCreateAsMap) { bCreateAsMap = bInCreateAsMap; } const TOptional& GetCreateAsMap() const { return bCreateAsMap; } private: TOptional bCreateAsMap; }; /** Return the list of packages to generate. */ virtual TArray GetGenerateList(const UPackage* OwnerPackage, const UObject* OwnerObject) = 0; /** Representation of generated packages prepared by the cooker. */ struct FGeneratedPackageForPopulate { /** RelativePath returned from GetGenerateList */ FString RelativePath; /** Root returned from GetGenerateList */ FString GeneratedRootPath; /** * Non-null UPackage. Possibly an empty placeholder package, but may contain modifications * that were made during PopulateGeneratorPackage. Provided so that the generator package * can create import references to objects that will be stored in the generated package. */ UPackage* Package = nullptr; /** *GetCreateAsMap returned from GetGenerateList. The package filename extension has already been set based on this. */ bool bCreatedAsMap = false; }; /** * Context passed into Populate, PreSave, and PostSave functions, on the Generator package and on the Generated * packages. Some functions are only applicable in certain calls, see the description of each function. */ struct FPopulateContext { public: explicit FPopulateContext(UE::Cook::CookPackageSplitter::FPopulateContextData& InData); /** The generator package being split. */ UNREALED_API UPackage* GetOwnerPackage() const; /** The SplitDataClass instance that this CookPackageSplitter instance was created for. */ UNREALED_API UObject* GetOwnerObject() const; /** * Placeholder UPackage and relative path information for all packages that will be generated. * * This function is only available in Populate and PreSave calls. It returns an empty array during PostSave. * This function is only available in calls on the generator package. It returns an empty array during calls to * generated packages. */ UNREALED_API TConstArrayView& GetGeneratedPackages(); /** * Returns true during calls on the generator package (e.g. PopulateGeneratorPackage). Returns false during * calls on the generated packages (e.g. PopulateGeneratedPackage). */ UNREALED_API bool IsCalledOnGenerator() const; /** * Returns the UPackage for which the event is being called. Returns the OwnerPackage if IsCalledOnGenerator, * returns the targetgenerated package if !IsCalledOnGenerator. * Guaranteed to not return nullptr. */ UNREALED_API UPackage* GetTargetPackage() const; /** * Returns the FGeneratedPackageForPopulate for the package for which the event is being called. * * Guaranteed to return non-null if !IsCalledOnGenerator(). * Returns null if IsCalledOnGenerator(). */ UNREALED_API const FGeneratedPackageForPopulate* GetTargetGeneratedPackage() const; /** * Report objects that will be moved into the Generator or Generated package during its save. * This is optional - these reported objects are processed (BeginCacheForCookPlatformData) asynchronously * instead of synchronously during save. * * This callback is only valid during Populate functions. It is ignored during PreSave and PostSave functions. */ UNREALED_API void ReportObjectToMove(UObject* Object); UNREALED_API void ReportObjectsToMove(TConstArrayView Objects); /** * Report a package to keep referenced until the generator/generated package finishes save. * When called for a Generator, if DoesGeneratedRequireGenerator() >= Populate, these will also be kept * referenced until all generated packages finish saving or the splitter is destroyed. * * This is partially optional; the CookPackageSplitter can also manage the lifetime of the objects * internally. But allowing objects necessary for the save to be garbage collected will cause performance * problems and possibly errors, so either this method or some other internal method must be used. * * This callback is only valid during Populate and PreSave functions. It is ignored during PostSave functions. */ UNREALED_API void ReportKeepReferencedPackage(UPackage* Package); UNREALED_API void ReportKeepReferencedPackages(TConstArrayView Packages); /** * Add the given FCookDependency to the build dependencies for the TargetGeneratedPackage. Incremental cooks * will invalidate the package and recook it if the CookDependency changes. * * This callback is only valid during Populate and PreSave functions. It is ignored during PostSave functions. * This callback is only valid in calls on generated packages. It is ignored during calls to the generator. */ UNREALED_API void ReportSaveDependency(UE::Cook::FCookDependency CookDependency); private: UE::Cook::CookPackageSplitter::FPopulateContextData& Data; }; /** * Called before presaving the parent generator package, to give the generator a chance to inform the cooker which * objects will be moved into the generator package that are not already present in it. * * PopulateGeneratorPackage is guaranteed to not be called again until the splitter has been destroyed and the * generator package has been garbage collected. * * @return True if successfully populated, false on error (this will cause a cook error). */ virtual bool PopulateGeneratorPackage(FPopulateContext& PopulateContext) { return true; } /** * Called before saving the parent generator package, after PopulateGeneratorPackage but before PopulateGeneratedPackage for any * generated packages. Make any required adjustments to the parent package before it is saved into the target domain. * @return True if successfully presaved, false on error (this will cause a cook error). */ virtual bool PreSaveGeneratorPackage(FPopulateContext& PopulateContext) { return true; } /** * Called after saving the parent generator package. Undo any required adjustments to the parent package that * were made in PreSaveGeneratorPackage, so that the package is once again ready for use in the editor or in * future GetGenerateList or PreSaveGeneratedPackage calls */ virtual void PostSaveGeneratorPackage(FPopulateContext& PopulateContext) { } /** * Try to populate a generated package. * * Receive an empty UPackage generated from an element in GetGenerateList and populate it. * Return a list of all the objects that will be moved into the Generated package during its save, so the cooker * can call BeginCacheForCookedPlatformData on them before the move * After returning, the given package will be queued for saving into the TargetDomain * * PopulateGeneratedPackage is guaranteed to not be called again on the same generated package until the splitter * has been destroyed and the generator package has been garbage collected. * * @return True if successfully populated, false on error (this will cause a cook error). */ virtual bool PopulateGeneratedPackage(FPopulateContext& PopulateContext) { return true; } /** * Called before saving a generated package, after PopulateGeneratedPackage. Make any required adjustments to the * generated package before it is saved into the target domain. * * @return True if successfully presaved, false on error (this will cause a cook error). */ virtual bool PreSaveGeneratedPackage(FPopulateContext& PopulateContext) { return true; }; /** * Called after saving a generated package. Undo any required adjustments to the parent package that * were made in PreSaveGeneratedPackage, so that the parent package is once again ready for use in the editor or in * future PreSaveGeneratedPackage calls. */ virtual void PostSaveGeneratedPackage(FPopulateContext& PopulateContext) { } /** Called when the Owner package needs to be reloaded after a garbage collect in order to populate a generated package. */ virtual void OnOwnerReloaded(UPackage* OwnerPackage, UObject* OwnerObject) {} // Utility functions for Splitters /** The name of the _Generated_ subdirectory that is the parent directory of a splitter's generated packages. */ UNREALED_API static const TCHAR* GetGeneratedPackageSubPath(); /** Return true if the given path is a _Generated_ directory, or a subpath under it. */ UNREALED_API static bool IsUnderGeneratedPackageSubPath(FStringView FileOrLongPackagePath); /** * Return the full packagename that will be used for a GeneratedPackage, based on the GeneratorPackage's name and * on the RelPath and optional GeneratedRootPath that the splitter provides in the FGeneratedPackage it returns from * GetGenerateList. */ UNREALED_API static FString ConstructGeneratedPackageName(FName OwnerPackageName, FStringView RelPath, FStringView GeneratedRootOverride = FStringView()); // FGeneratedPackageForPreSave and FGeneratedPackageForPopulate were two different structs that were so far identical, // given separate identical definitions because we wanted to reserve the ability to change one but not the other. // But to simplify the API we have given up on that flexibility, and now use FGeneratedPackageForPopulate everywhere. UE_DEPRECATED(5.6, "Use FGeneratedPackageForPopulate instead.") typedef FGeneratedPackageForPopulate FGeneratedPackageForPreSave; UE_DEPRECATED(5.6, "Implement version that takes an FPopulateContext instead.") UNREALED_API virtual bool PopulateGeneratorPackage(UPackage* OwnerPackage, UObject* OwnerObject, const TArray& GeneratedPackages, TArray& OutObjectsToMove, TArray& OutKeepReferencedPackages); UE_DEPRECATED(5.6, "Implement version that takes an FPopulateContext instead.") UNREALED_API virtual bool PreSaveGeneratorPackage(UPackage* OwnerPackage, UObject* OwnerObject, const TArray& PlaceholderPackages, TArray& OutKeepReferencedPackages); UE_DEPRECATED(5.6, "Implement version that takes an FPopulateContext instead.") UNREALED_API virtual void PostSaveGeneratorPackage(UPackage* OwnerPackage, UObject* OwnerObject); UE_DEPRECATED(5.6, "Implement version that takes an FPopulateContext instead.") UNREALED_API virtual bool PopulateGeneratedPackage(UPackage* OwnerPackage, UObject* OwnerObject, const FGeneratedPackageForPopulate& GeneratedPackage, TArray& OutObjectsToMove, TArray& OutKeepReferencedPackages); UE_DEPRECATED(5.6, "Implement version that takes an FPopulateContext instead.") UNREALED_API virtual bool PreSaveGeneratedPackage(UPackage* OwnerPackage, UObject* OwnerObject, const FGeneratedPackageForPopulate& GeneratedPackage, TArray& OutKeepReferencedPackages); UE_DEPRECATED(5.6, "Implement version that takes an FPopulateContext instead.") UNREALED_API virtual void PostSaveGeneratedPackage(UPackage* OwnerPackage, UObject* OwnerObject, const FGeneratedPackageForPopulate& GeneratedPackage); UE_DEPRECATED(5.6, "Deprecation support, do not call outside of cooker code.") UNREALED_API void WarnIfDeprecatedVirtualNotCalled(const TCHAR* FunctionName); private: UE_DEPRECATED(5.6, "Deprecation support, do not read/write outside of cooker code.") bool bDeprecatedVirtualCalledAsExpected = false; }; namespace UE::Cook::Private { /** Interface for internal use only (used by REGISTER_COOKPACKAGE_SPLITTER to register an ICookPackageSplitter for a class) */ class FRegisteredCookPackageSplitter { public: UNREALED_API FRegisteredCookPackageSplitter(); UNREALED_API virtual ~FRegisteredCookPackageSplitter(); virtual UClass* GetSplitDataClass() const = 0; virtual bool RequiresCachedCookedPlatformDataBeforeSplit() const = 0; virtual bool ShouldSplitPackage(UObject* Object) const = 0; virtual ICookPackageSplitter* CreateInstance(UObject* Object) const = 0; virtual FString GetSplitterDebugName() const = 0; static UNREALED_API void ForEach(TFunctionRef Func); private: static UNREALED_API TLinkedList*& GetRegisteredList(); TLinkedList GlobalListLink; }; } /** * Used to Register an ICookPackageSplitter for a class * * Example usage: * * // In header or cpp * class FMyCookPackageSplitter : public ICookPackageSplitter { ... } * * // In cpp * REGISTER_COOKPACKAGE_SPLITTER(FMyCookPackageSplitter, UMySplitDataClass); */ #define DEFINE_COOKPACKAGE_SPLITTER(SplitterClass, SplitDataClass) \ friend class PREPROCESSOR_JOIN(PREPROCESSOR_JOIN(SplitterClass, SplitDataClass), _Register) #define REGISTER_COOKPACKAGE_SPLITTER(SplitterClass, SplitDataClass) \ class PREPROCESSOR_JOIN(PREPROCESSOR_JOIN(SplitterClass, SplitDataClass), _Register) \ : public UE::Cook::Private::FRegisteredCookPackageSplitter \ { \ virtual UClass* GetSplitDataClass() const override \ { \ return SplitDataClass::StaticClass(); \ } \ virtual bool RequiresCachedCookedPlatformDataBeforeSplit() const override \ { \ return SplitterClass::RequiresCachedCookedPlatformDataBeforeSplit(); \ } \ virtual bool ShouldSplitPackage(UObject* Object) const override \ { \ return SplitterClass::ShouldSplit(Object); \ } \ virtual ICookPackageSplitter* CreateInstance(UObject* SplitData) const override \ { \ return new SplitterClass(); \ } \ virtual FString GetSplitterDebugName() const override \ { \ return SplitterClass::GetSplitterDebugName(); \ } \ }; \ namespace PREPROCESSOR_JOIN(SplitterClass, SplitDataClass) \ { \ static PREPROCESSOR_JOIN(PREPROCESSOR_JOIN(SplitterClass, SplitDataClass), _Register) DefaultObject; \ } #endif