// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Containers/Array.h" #include "Containers/Map.h" #include "Containers/StringFwd.h" #include "HAL/CriticalSection.h" #include "HAL/ThreadSingleton.h" #include "Templates/Function.h" #include "UObject/NameTypes.h" #include "UObject/Object.h" class FCbFieldView; class FCbWriter; class UPackage; namespace UE { class FLogRecord; } struct FEDLCookCheckerThreadState; namespace UE::Cook { /** * Data describing an object's name in its outer hierarchy. Used so that we can persistently represent all the names in * a tree of object outers in a list that does not duplicate the string data between an object and * its outer. */ struct FImportExportNode { public: FName ObjectName; int32 ParentId = -1; void Save(FCbWriter& Writer) const; bool TryLoad(const FCbFieldView& Field); private: friend FCbWriter& operator<<(FCbWriter& Writer, const FImportExportNode& Node) { Node.Save(Writer); return Writer; } friend bool LoadFromCompactBinary(const FCbFieldView& Field, FImportExportNode& Node) { return Node.TryLoad(Field); } }; /** * Data about imports and exports from a package that can be stored in the oplog for incremental cooks for replay into * the CookImportsChecker when a package is skipped. */ struct FImportsCheckerData { public: TArray Imports; TArray Exports; bool IsEmpty() const; void Save(FCbWriter& Writer) const; bool TryLoad(const FCbFieldView& Field); static FImportsCheckerData FromObjectLists(TConstArrayView Imports, TConstArrayView Exports); static TArray ObjectListToNodeList(TConstArrayView Objects); private: friend FCbWriter& operator<<(FCbWriter& Writer, const FImportsCheckerData& Data) { Data.Save(Writer); return Writer; } friend bool LoadFromCompactBinary(const FCbFieldView& Field, FImportsCheckerData& Data) { return Data.TryLoad(Field); } }; } // namespace UE::Cook /** * Helper struct used during cooking to validate EDL dependencies */ struct FEDLCookChecker { void SetActiveIfNeeded(); void Reset(); void AddImport(TObjectPtr Import, UPackage* ImportingPackage); void AddImports(TConstArrayView Imports, FName ImportingPackageName); void AddExport(UObject* Export); void AddExports(TConstArrayView Exports); void Add(UE::Cook::FImportsCheckerData& ImportsCheckerData, FName PackageName); void AddArc(UObject* DepObject, bool bDepIsSerialize, UObject* Export, bool bExportIsSerialize); void AddPackageWithUnknownExports(FName LongPackageName); static void StartSavingEDLCookInfoForVerification(); static void Verify(const TFunction& MessageCallback, bool bFullReferencesExpected); static void MoveToCompactBinaryAndClear(FCbWriter& Writer, bool& bOutHasData); static bool AppendFromCompactBinary(FCbFieldView Field); private: static FEDLCookChecker AccumulateAndClear(); void WriteToCompactBinary(FCbWriter& Writer); bool ReadFromCompactBinary(FCbFieldView Field); typedef uint32 FEDLNodeID; static const FEDLNodeID NodeIDInvalid = static_cast(-1); struct FEDLNodeData; public: // FEDLNodeHash is public only so that GetTypeHash can be defined enum class EObjectEvent : uint8 { Create, Serialize, Max = Serialize, }; /** * Wrapper around an FEDLNodeData (or around a UObject when searching for an FEDLNodeData corresponding to the UObject) * that provides the hash-by-objectpath to lookup the FEDLNodeData for an objectpath. */ struct FEDLNodeHash { FEDLNodeHash(); // creates an uninitialized node; only use this to provide as an out parameter FEDLNodeHash(const TArray* InNodes, FEDLNodeID InNodeID, EObjectEvent InObjectEvent); FEDLNodeHash(const TArray* InNodes, FEDLNodeID InParentNodeID, FName InObjectName, EObjectEvent InObjectEvent); FEDLNodeHash(TObjectPtr InObject, EObjectEvent InObjectEvent); FEDLNodeHash(const FEDLNodeHash& Other); bool operator==(const FEDLNodeHash& Other) const; FEDLNodeHash& operator=(const FEDLNodeHash& Other); friend uint32 GetTypeHash(const FEDLNodeHash& A); FName GetName() const; bool TryGetParent(FEDLNodeHash& Parent) const; EObjectEvent GetObjectEvent() const; void SetNodes(const TArray* InNodes); private: static uint32 GetTypeHashInternal(const FEDLNodeHash& A); struct FNodeTypeData { /** * The array of nodes from the FEDLCookChecker; this is how we lookup the node for the NodeID. * Because the FEDLNodeData are elements in an array which can resize and therefore reallocate the nodes, we cannot store the pointer to the node. */ const TArray* Nodes; /** The identifier for the FEDLNodeData this hash is wrapping. */ FEDLNodeID NodeID; }; struct FNameTypeData { FName ObjectName; /** The array of nodes from the FEDLCookChecker, the same as used in FNodeTypeData. */ const TArray* Nodes; FEDLNodeID ParentID; }; struct FObjectTypeData { TObjectPtr Object; }; enum class ENodeHashType : uint8 { Node, Object, NameAndParentNode, }; union { FNodeTypeData NodeTypeData; FObjectTypeData ObjectTypeData; FNameTypeData NameTypeData; }; ENodeHashType NodeHashType; EObjectEvent ObjectEvent; }; 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 traversing parent. */ 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 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() { /* Fields are uninitialized */ } FEDLNodeData(FEDLNodeID InID, FEDLNodeID InParentID, FName InName, EObjectEvent InObjectEvent); FEDLNodeData(FEDLNodeID InID, FEDLNodeID InParentID, FName InName, FEDLNodeData&& Other); FEDLNodeHash GetNodeHash(const FEDLCookChecker& Owner) const; FString ToString(const FEDLCookChecker& Owner) const; void AppendPathName(const FEDLCookChecker& Owner, FStringBuilderBase& Result) const; FName GetPackageName(const FEDLCookChecker& Owner) const; void Merge(FEDLNodeData&& Other); }; FEDLNodeID FindOrAddNode(const FEDLNodeHash& NodeLookup); FEDLNodeID FindOrAddNode(FEDLNodeData&& NodeData, const FEDLCookChecker& OldOwnerOfNode, FEDLNodeID ParentIDInThis, bool& bNew); FEDLNodeID FindNode(const FEDLNodeHash& NodeHash); template void AddImportExportNodeList(TConstArrayView NodeList, AddNodeType&& AddNode); void RecordImportFromPackage(FEDLNodeID NodeId, FName ImportingPackageName); void Merge(FEDLCookChecker&& Other); bool CheckForCyclesInner(TSet& Visited, TSet& 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 until the checker is reset. */ TArray Nodes; /** A map to lookup the node for a UObject or for the corresponding node in another thread's FEDLCookChecker. */ TMap NodeHashToNodeID; /** The graph of dependencies between nodes. */ TMultiMap NodePrereqs; /** * Packages that were cooked with LegacyIterative and therefore have an unknown set of exports. * We suppress warnings for exports missing from these packages. */ TSet PackagesWithUnknownExports; /** True if the EDLCookChecker should be active; it is turned off if the runtime will not be using EDL. */ bool bIsActive = false; /** 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 CookCheckerInstances; friend FEDLCookCheckerThreadState; }; /** Per-thread accessor for writing EDL dependencies to global FEDLCookChecker storage. */ struct FEDLCookCheckerThreadState : public TThreadSingleton { FEDLCookCheckerThreadState(); void AddImport(TObjectPtr Import, UPackage* ImportingPackage) { Checker.AddImport(Import, ImportingPackage); } void AddImports(TConstArrayView Imports, FName ImportingPackageName) { Checker.AddImports(Imports, ImportingPackageName); } void AddExport(UObject* Export) { Checker.AddExport(Export); } void AddExports(TConstArrayView Exports) { Checker.AddExports(Exports); } void Add(UE::Cook::FImportsCheckerData& ImportsCheckerData, FName PackageName) { Checker.Add(ImportsCheckerData, PackageName); } void AddArc(UObject* DepObject, bool bDepIsSerialize, UObject* Export, bool bExportIsSerialize) { Checker.AddArc(DepObject, bDepIsSerialize, Export, bExportIsSerialize); } void AddPackageWithUnknownExports(FName LongPackageName) { Checker.AddPackageWithUnknownExports(LongPackageName); } private: FEDLCookChecker Checker; friend TThreadSingleton; friend FEDLCookChecker; }; /////////////////////////////////////////////////////// // Inline implementations /////////////////////////////////////////////////////// namespace UE::Cook { inline bool FImportsCheckerData::IsEmpty() const { return Imports.IsEmpty() && Exports.IsEmpty(); } } // namespace UE::Cook