EM_Task/CoreUObject/Private/UObject/SavePackage/SavePackageUtilities.h

369 lines
14 KiB
C
Raw Normal View History

2026-02-13 16:18:33 +08:00
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Containers/Array.h"
#include "Containers/Map.h"
#include "Containers/Set.h"
#include "Containers/StringFwd.h"
#include "HAL/ThreadSingleton.h"
#include "ProfilingDebugging/CookStats.h"
#include "Serialization/ArchiveObjectCrc32.h"
#include "Serialization/ArchiveStackTrace.h"
#include "Serialization/FileRegions.h"
#include "UObject/NameTypes.h"
#include "UObject/UObjectMarks.h"
// This file contains private utilities shared by UPackage::Save and UPackage::Save2
class FMD5;
class FSavePackageContext;
template <typename StateType>
class TAsyncWorkSequence;
DECLARE_LOG_CATEGORY_EXTERN(LogSavePackage, Log, All);
struct FLargeMemoryDelete
{
void operator()(uint8* Ptr) const
{
if (Ptr)
{
FMemory::Free(Ptr);
}
}
};
typedef TUniquePtr<uint8, FLargeMemoryDelete> FLargeMemoryPtr;
enum class EAsyncWriteOptions
{
None = 0,
WriteFileToDisk = 0x01,
ComputeHash = 0x02
};
ENUM_CLASS_FLAGS(EAsyncWriteOptions)
struct FScopedSavingFlag
{
FScopedSavingFlag(bool InSavingConcurrent);
~FScopedSavingFlag();
bool bSavingConcurrent;
};
struct FSavePackageDiffSettings
{
int32 MaxDiffsToLog;
bool bIgnoreHeaderDiffs;
bool bSaveForDiff;
FSavePackageDiffSettings(bool bDiffing);
};
struct FCanSkipEditorReferencedPackagesWhenCooking
{
bool bCanSkipEditorReferencedPackagesWhenCooking;
FCanSkipEditorReferencedPackagesWhenCooking();
FORCEINLINE operator bool() const { return bCanSkipEditorReferencedPackagesWhenCooking; }
};
/**
* Helper structure to encapsulate sorting a linker's export table alphabetically, taking into account conforming to other linkers.
* @note Save2 should not have to use this sorting long term
*/
struct FObjectExportSortHelper
{
private:
struct FObjectFullName
{
public:
FObjectFullName(const UObject* Object, const UObject* Root);
FObjectFullName(FObjectFullName&& InFullName);
FName ClassName;
TArray<FName> Path;
};
public:
FObjectExportSortHelper(): bUseFObjectFullName(false) {}
/**
* Sorts exports alphabetically. If a package is specified to be conformed against, ensures that the order
* of the exports match the order in which the corresponding exports occur in the old package.
*
* @param Linker linker containing the exports that need to be sorted
* @param LinkerToConformTo optional linker to conform against.
*/
void SortExports(FLinkerSave* Linker, FLinkerLoad* LinkerToConformTo = nullptr, bool InbUseFObjectFullName = false);
private:
/** Comparison function used by Sort */
bool operator()(const FObjectExport& A, const FObjectExport& B) const;
/** the linker that we're sorting exports for */
friend struct TDereferenceWrapper<FObjectExport, FObjectExportSortHelper>;
bool bUseFObjectFullName;
TMap<UObject*, FObjectFullName> ObjectToObjectFullNameMap;
/**
* Map of UObject => full name; optimization for sorting.
*/
TMap<UObject*, FString> ObjectToFullNameMap;
};
/**
* Helper struct used during cooking to validate EDL dependencies
*/
struct FEDLCookChecker: public TThreadSingleton<FEDLCookChecker>
{
void SetActiveIfNeeded();
void Reset();
void AddImport(UObject* Import, UPackage* ImportingPackage);
void AddExport(UObject* Export);
void AddArc(UObject* DepObject, bool bDepIsSerialize, UObject* Export, bool bExportIsSerialize);
// New public API for the director to process remote data
void AddImport_Path(FName ImportPath, FName ImportingPackageName);
void AddExport_Path(FName ExportPath);
void AddArc_Path(FName DepPath, bool bDepIsSerialize, FName ExpPath, bool bExpIsSerialize);
static void StartSavingEDLCookInfoForVerification();
static void Verify(bool bFullReferencesExpected);
private:
typedef uint32 FEDLNodeID;
static const FEDLNodeID NodeIDInvalid = static_cast<FEDLNodeID>(-1);
struct FEDLNodeData;
public: // FEDLNodeHash is public only so that GetTypeHash can be defined
enum class EObjectEvent : uint8
{
Create,
Serialize
};
/**
* Wrapper that provides a unified, path-based hash and equality for different sources:
* 1. A live UObject* (for local cook operations).
* 2. An FName representing a full object path (for remote data from workers).
* 3. An existing node in the Nodes array (for internal path walking).
*/
struct FEDLNodeHash
{
friend struct FEDLCookChecker;
public:
FEDLNodeHash(); // creates an uninitialized node; only use this to provide as an out parameter
// Constructors for the three modes
FEDLNodeHash(const UObject* InObject, EObjectEvent InObjectEvent);
FEDLNodeHash(FName InPath, EObjectEvent InObjectEvent);
FEDLNodeHash(const TArray<FEDLNodeData>* InNodes, FEDLNodeID InNodeID, EObjectEvent InObjectEvent);
bool operator==(const FEDLNodeHash& Other) const;
friend uint32 GetTypeHash(const FEDLNodeHash& A);
// Gets the object's name (the last part of the path)
FName GetName() const;
// Tries to create a new FEDLNodeHash representing the parent of the current one.
bool TryGetParent(FEDLNodeHash& Parent) const;
EObjectEvent GetObjectEvent() const;
private:
/** The identifier for the FEDLNodeData this hash is wrapping. Only used if HashMode is Node. */
FName GetPathAsName() const;
void AppendPath(TStringBuilder<1024>& Builder) const;
enum class EHashMode : uint8
{
Object, // Based on a UObject*
Path, // Based on an FName path
Node // Based on an existing node in the 'Nodes' array
};
union
{
/**
* The array of nodes from the FEDLCookChecker; this is how we lookup the node for the FEDLNodeData.
* Because the FEDLNodeData are elements in an array which can resize and therefore reallocate the nodes, we cannot store the pointer to the node.
* Only used if bIsNode is true.
*/
const TArray<FEDLNodeData>* Nodes;
/** Pointer to the Object we are looking up, if this hash was created during lookup-by-objectpath for an object */
const UObject* Object;
};
/** The identifier for the FEDLNodeData this hash is wrapping. Only used if HashMode is Node. */
/** Full object path used in Path mode, or as a cache */
FName PathName;
/** Index into the Nodes array, used only in Node mode */
FEDLNodeID NodeID;
/** The specific loading event this node represents */
EObjectEvent ObjectEvent;
/** Discriminator for the union and path resolution logic */
EHashMode HashMode;
};
private:
/**
* Node representing either the Create event or Serialize event of a UObject in the graph of runtime dependencies between UObjects.
*/
struct FEDLNodeData
{
// Note that order of the variables is important to reduce alignment waste in the size of FEDLNodeData.
/** Name of the UObject represented by this node; full objectpath name is obtainable by combining parent data with the name. */
FName Name;
/** Index of this node in the FEDLCookChecker's Nodes array. This index is used to provide a small-memory-usage identifier for the node. */
FEDLNodeID ID;
/**
* Tracks references to this node's UObjects from other packages (which is the reverse of the references from each node that we track in NodePrereqs.)
* We only need this information from each package, so we track by package name instead of node id.
*/
TArray<FName> ImportingPackagesSorted;
/**
* ID of the node representing the UObject parent of this node's UObject. NodeIDInvalid if the UObject has no parent.
* The ParentID always refers to the node for the Create event of the parent UObject.
*/
uint32 ParentID;
/** True if this node represents the Serialize event on the UObject, false if it represents the Create event. */
EObjectEvent ObjectEvent;
/** True if the UObject represented by this node has been exported by a SavePackage call; used to verify that the imports requested by packages are present somewhere in the cook. */
bool bIsExport;
FEDLNodeData(FEDLNodeID InID, FEDLNodeID InParentID, FName InName, EObjectEvent InObjectEvent);
FEDLNodeData(FEDLNodeID InID, FEDLNodeID InParentID, FName InName, FEDLNodeData&& Other);
FString ToString(const FEDLCookChecker& Owner) const;
void AppendPathName(const FEDLCookChecker& Owner, FStringBuilderBase& Result) const;
void Merge(FEDLNodeData&& Other);
};
enum class EInternalConstruct
{
Type
};
FEDLCookChecker();
FEDLCookChecker(EInternalConstruct);
FEDLNodeID FindOrAddNode(const FEDLNodeHash& NodeLookup);
FEDLNodeID FindOrAddNode(FEDLNodeData&& NodeData, const FEDLCookChecker& OldOwnerOfNode, FEDLNodeID ParentIDInThis, bool& bNew);
FEDLNodeID FindOrAddNodeFromPath(FName Path, EObjectEvent ObjectEvent);
FEDLNodeID FindNode(const FEDLNodeHash& NodeHash);
void Merge(FEDLCookChecker&& Other);
bool CheckForCyclesInner(TSet<FEDLNodeID>& Visited, TSet<FEDLNodeID>& Stack, const FEDLNodeID& Visit, FEDLNodeID& FailNode);
void AddDependency(FEDLNodeID SourceID, FEDLNodeID TargetID);
/**
* All the FEDLNodeDatas that have been created for this checker. These are allocated as elements of an array rather than pointers to reduce cputime and
* memory due to many small allocations, and to provide index-based identifiers. Nodes are not deleted during the lifetime of the checker.
*/
TArray<FEDLNodeData> Nodes;
/** A map to lookup the node for a UObject or for the corresponding node in another thread's FEDLCookChecker. */
TMap<FEDLNodeHash, FEDLNodeID> NodeHashToNodeID;
/** A map to lookup a node directly from its full path string. Used on the Director. */
TMap<FName, FEDLNodeID> PathToNodeID;
/** The graph of dependencies between nodes. */
TMultiMap<FEDLNodeID, FEDLNodeID> NodePrereqs;
/** True if the EDLCookChecker should be active; it is turned off if the runtime will not be using EDL. */
bool bIsActive;
/** When cooking with concurrent saving, each thread has its own FEDLCookChecker, and these are merged after the cook is complete. */
static FCriticalSection CookCheckerInstanceCritical;
static TArray<FEDLCookChecker*> CookCheckerInstances;
friend TThreadSingleton<FEDLCookChecker>;
};
#if WITH_EDITORONLY_DATA
/**
* Archive to calculate a checksum on an object's serialized data stream, but only of its non-editor properties.
*/
class FArchiveObjectCrc32NonEditorProperties: public FArchiveObjectCrc32
{
using Super = FArchiveObjectCrc32;
public:
FArchiveObjectCrc32NonEditorProperties()
: EditorOnlyProp(0)
{
}
virtual FString GetArchiveName() const
{
return TEXT("FArchiveObjectCrc32NonEditorProperties");
}
virtual void Serialize(void* Data, int64 Length);
private:
int32 EditorOnlyProp;
};
#else
class COREUOBJECT_API FArchiveObjectCrc32NonEditorProperties: public FArchiveObjectCrc32
{
};
#endif
// Utility functions used by both UPackage::Save and/or UPackage::Save2
namespace SavePackageUtilities
{
extern const FName NAME_World;
extern const FName NAME_Level;
extern const FName NAME_PrestreamPackage;
void GetBlueprintNativeCodeGenReplacement(UObject* InObj, UClass*& ObjClass, UObject*& ObjOuter, FName& ObjName, const ITargetPlatform* TargetPlatform);
void IncrementOutstandingAsyncWrites();
void DecrementOutstandingAsyncWrites();
void SaveThumbnails(UPackage* InOuter, FLinkerSave* Linker, FStructuredArchive::FSlot Slot);
void SaveBulkData(FLinkerSave* Linker, const UPackage* InOuter, const TCHAR* Filename, const ITargetPlatform* TargetPlatform,
FSavePackageContext* SavePackageContext, const bool bTextFormat, const bool bDiffing, const bool bComputeHash, TAsyncWorkSequence<FMD5>& AsyncWriteAndHashSequence, int64& TotalPackageSizeUncompressed);
void SaveWorldLevelInfo(UPackage* InOuter, FLinkerSave* Linker, FStructuredArchive::FRecord Record);
EObjectMark GetExcludedObjectMarksForTargetPlatform(const class ITargetPlatform* TargetPlatform);
bool HasUnsaveableOuter(UObject* InObj, UPackage* InSavingPackage);
void CheckObjectPriorToSave(FArchiveUObject& Ar, UObject* InObj, UPackage* InSavingPackage);
void ConditionallyExcludeObjectForTarget(UObject* Obj, EObjectMark ExcludedObjectMarks, const ITargetPlatform* TargetPlatform);
void FindMostLikelyCulprit(TArray<UObject*> BadObjects, UObject*& MostLikelyCulprit, const FProperty*& PropertyRef);
void AddFileToHash(FString const& Filename, FMD5& Hash);
void WriteToFile(const FString& Filename, const uint8* InDataPtr, int64 InDataSize);
void AsyncWriteFile(TAsyncWorkSequence<FMD5>& AsyncWriteAndHashSequence, FLargeMemoryPtr Data, const int64 DataSize, const TCHAR* Filename, EAsyncWriteOptions Options, TArrayView<const FFileRegion> InFileRegions);
void AsyncWriteFileWithSplitExports(TAsyncWorkSequence<FMD5>& AsyncWriteAndHashSequence, FLargeMemoryPtr Data, const int64 DataSize, const int64 HeaderSize, const TCHAR* Filename, EAsyncWriteOptions Options, TArrayView<const FFileRegion> InFileRegions);
void GetCDOSubobjects(UObject* CDO, TArray<UObject*>& Subobjects);
} // namespace SavePackageUtilities
#if ENABLE_COOK_STATS
struct FSavePackageStats
{
static int32 NumPackagesSaved;
static double SavePackageTimeSec;
static double TagPackageExportsPresaveTimeSec;
static double TagPackageExportsTimeSec;
static double FullyLoadLoadersTimeSec;
static double ResetLoadersTimeSec;
static double TagPackageExportsGetObjectsWithOuter;
static double TagPackageExportsGetObjectsWithMarks;
static double SerializeImportsTimeSec;
static double SortExportsSeekfreeInnerTimeSec;
static double SerializeExportsTimeSec;
static double SerializeBulkDataTimeSec;
static double AsyncWriteTimeSec;
static double MBWritten;
static TMap<FName, FArchiveDiffStats> PackageDiffStats;
static int32 NumberOfDifferentPackages;
static FCookStatsManager::FAutoRegisterCallback RegisterCookStats;
static void AddSavePackageStats(FCookStatsManager::AddStatFuncRef AddStat);
static void MergeStats(const TMap<FName, FArchiveDiffStats>& ToMerge);
};
#endif