Files
UnrealEngine/Engine/Source/Developer/CookMetadata/Public/CookMetadata.h
2025-05-18 13:04:45 +08:00

418 lines
16 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Containers/Array.h"
#include "Containers/Map.h"
#include "Containers/UnrealString.h"
#include "CoreTypes.h"
#include "Memory/MemoryFwd.h"
#include "Misc/TVariant.h"
#include "UObject/NameTypes.h"
#define UE_API COOKMETADATA_API
namespace UE::Cook
{
enum class ECookMetadataStateVersion : uint8
{
InvalidVersion = 0,
PluginHierarchy = 1,
PostWritebackHash = 2,
FixSerialization = 3,
AddedCustomFields = 4,
// Note: This is a lie, the shader data wasn't actually serializing to file, use ActualAddShaderPseudoHierarchy
AddedShaderPseudoHierarchy = 5,
AddedPluginEntryType = 6,
ActualAddShaderPseudoHierarchy = 7,
AdjustCustomFieldLayout = 8,
// Add new versions above this.
VersionCount,
LatestVersion = VersionCount - 1
};
/**
* We classify various bits of the plugin data based on how it will be delivered to the end user in order to
* more appropriately track the user's experience.
*
* Each iostore chunk (note: NOT pak chunk!) gets compressed during staging and unreal pak will update the size
* for the plugin based on how the chunk gets deployed.
*/
enum class EPluginSizeTypes : uint8
{
// The iostore chunks will be deployed to servers for downloading on-demand by the game using the Individual Asset Streaming
// system.
Streaming,
// The iostore chunks are written to normal iostore containers that are expected to be distributed with the game. This
// includes the required global iostore container.
Installed,
// The iostore chunks are written to a separate container that isn't required to be distributed with the game. This is where e.g.
// OptionalMips go for textures. They appear as .uptnl files in the Cooked directory (if cooking to Loose Files),
// The CopyBuildToStagingDirectory script manually assigns the iostore chunk to a corresponding pak chunk with the name
// ending in "optional", e.g. pakChunk0optional.pak/ucas/utoc/sig. The only way to catch this in UnrealPak
// is to parse the filename.
Optional,
// These are sidecar files for distributing EditorOnly data for Cooked Editor builds. When cooking to loose files
// they will contain ".o" inside their filename, e.g. "myasset.o.ubulk". They are never intended to be shipped with
// a game.
OptionalSegment,
COUNT
};
constexpr uint8 EPluginSizeTypesCount = (uint8)EPluginSizeTypes::COUNT;
struct FPluginSizeInfo
{
uint64 Sizes[EPluginSizeTypesCount] = {};
void Zero() { FMemory::Memzero(this, sizeof(*this)); }
void AddSizes(uint64 SizesPerType[EPluginSizeTypesCount])
{
for (uint8 Type = 0; Type < EPluginSizeTypesCount; Type++)
{
Sizes[Type] += SizesPerType[Type];
}
}
void Add(const FPluginSizeInfo& Other)
{
for (uint8 Type = 0; Type < EPluginSizeTypesCount; Type++)
{
Sizes[Type] += Other.Sizes[Type];
}
}
uint64 TotalSize() const
{
uint64 Total = 0;
for (uint8 Type = 0; Type < EPluginSizeTypesCount; Type++)
{
Total += Sizes[Type];
}
return Total;
}
friend FArchive& operator<<(FArchive& Ar, FPluginSizeInfo& SizeInfo)
{
for (uint8 i = 0; i < EPluginSizeTypesCount; i++)
{
Ar << SizeInfo.Sizes[i];
}
return Ar;
}
uint64& operator[](EPluginSizeTypes InType) { return Sizes[(uint8)InType]; }
const uint64& operator[](EPluginSizeTypes InType) const { return Sizes[(uint8)InType]; }
};
/*
* Unrealpak doesn't compress the data for all platforms. In those cases, the data written back
* during staging isn't compressed and is not representative of the final sizes that occurs after
* the corresponding SDK tools process them for deployment.
*/
enum class ECookMetadataSizesPresent
{
// staging has not occured, or writeback wasn't enabled in project packaing settings.
NotPresent,
// unrealpak compressed the iostore chunks and the data we have is compressed sizes
Compressed,
// the selected platform isn't compressed by unrealpak, or package compression was disabled.
Uncompressed,
Count
};
enum class ECookMetadataPluginType
{
// For sanity tracking. All types _should_ be assigned when they are added.
Unassigned,
Normal,
// Root plugins are used to separate and classify game "modes" within a single project.
Root,
// For assets under /Engine
EnginePseudo,
// For assets under /Game
GamePseudo,
// When a shader is referenced by multiple plugins then it has no natural home for assigning
// its size. Instead we create a set of shader pseudo plugins based on the set of root plugins
// referencing the shader, including possibly an "Unrooted" plugin.
ShaderPseudo,
Count
};
enum class ECookMetadataCustomFieldType : uint8
{
Unknown, // This only happens in the upgrade from an older cook metadata
// when there's nowhere to get the information from. This also means
// that there are no plugins that use this field so you can safely ignore it.
Bool,
String
};
/** The name and dependency information for a plugin that was enabled during cooking. */
struct FCookMetadataPluginEntry
{
FString Name;
ECookMetadataPluginType Type = ECookMetadataPluginType::Unassigned;
// These contain values pulled from the uplugin json file and hold fields that are not
// part of the engine FPluginDescriptor. They are for per-project values. The keys for the maps
// are indices in to FCookMetadataPluginHierarchy::CustomFieldEntries, where you'll also find what the type
// is. See the comment for CustomFieldEntries.
typedef TVariant<bool, FString> CustomFieldVariantType;
TMap<uint8, CustomFieldVariantType> CustomFields;
// The dependencies are stored in the FCookMetadataPluginHierarchy::PluginDependencies array,
// and this is an index into it. From there you can get a further index into PluginsEnabledAtCook
// to get the plugin information.
// Example:
// for (uint32 DependencyIndex = Plugin->DependencyIndexStart; DependencyIndex < Plugin->DependencyIndexEnd; DependencyIndex++)
// {
// const UE::Cook::FCookMetadataPluginEntry& DependentPlugin = PluginHierarchy.PluginsEnabledAtCook[PluginHierarchy.PluginDependencies[DependencyIndex]];
// }
//
uint32 DependencyIndexStart = 0;
uint32 DependencyIndexEnd = 0;
//
// Theses sizes are set during staging by unrealpak if the option in project packaging is set.
// To determine if they are set, check FCookMetadataState::GetSizesPresent(). Inclusive contains
// the size of the plugin and all of its dependencies listed in its uplugin file.
//
FPluginSizeInfo InclusiveSizes;
FPluginSizeInfo ExclusiveSizes;
uint32 DependencyCount() const { return DependencyIndexEnd - DependencyIndexStart; }
UE_API FText GetPluginTypeAsText() const;
// !!! If you edit this, be sure to update the upgrade paths in CookMetadata.cpp
friend FArchive& operator<<(FArchive& Ar, FCookMetadataPluginEntry& Entry)
{
Ar << Entry.Name << Entry.DependencyIndexStart << Entry.DependencyIndexEnd;
Ar << Entry.InclusiveSizes << Entry.ExclusiveSizes;
Ar << Entry.CustomFields << Entry.Type;
return Ar;
}
};
struct FCookMetadataPluginHierarchy
{
// The list of plugins that were enabled during the cook that generated the FCookMetadataState
TArray<FCookMetadataPluginEntry> PluginsEnabledAtCook;
// The list of plugin dependencies. FCookMetadataPluginEntry::DependencyIndexStart indexes into this
// array.
TArray<uint16> PluginDependencies;
// The list of root plugins for the project as defined by the Editor.ini file.
TArray<uint16> RootPlugins;
// The list of custom field names. The values stored on an entry index in to this for the name.
// These values are copied from the plugins' json descriptor to allow for carrying through project
// specific data in to the metadata.
//
// To enable custom field replication, add the CookMetadataCustomPluginFields section to the relevant Editor.ini file
// and add to the follow arrays, as desired:
//
// BoolFields, StringFields, PerPlatformBoolFields, PerPlatformStringFields
//
// Bool and String refer to the json type, and will emit as bool or FString here. The PerPlatform arrays
// will check in the uplugin json for a field named PerPlatform<ArrayValue>. That field is expected to be
// an array of override objects. Each object is required to have the platform name and override value. E.g.:
//
// [CookMetadataCustomPluginFields]
// +PerPlatformBoolFields = ExampleProjectBool
// +StringField = ExampleProjectString
//
// Will look in the uplugin file for this:
//
// "ExampleProjectBool": true
// "PerPlatformExampleProjectBool": [
// {
// "Platform": "Windows",
// "Value": false
// }
// ]
// "ExampleProjectString": "AProjectString"
//
// Note that in the per platform case, the base value is not required (i.e. ExampleProjectBool above), however it
// provides a default value for unlisted platforms. If it does not exist, false is used for bool fields, and an empty string
// for string fields.
struct FCustomFieldEntry
{
FString Name;
ECookMetadataCustomFieldType Type;
friend FArchive& operator<<(FArchive& Ar, FCustomFieldEntry& Entry)
{
return Ar << Entry.Name << Entry.Type;
}
};
TArray<FCustomFieldEntry> CustomFieldEntries;
// !!! If you edit this, be sure to update the upgrade paths in CookMetadata.cpp
friend FArchive& operator<<(FArchive& Ar, FCookMetadataPluginHierarchy& Hierarchy)
{
Ar << Hierarchy.PluginsEnabledAtCook << Hierarchy.PluginDependencies;
Ar << Hierarchy.RootPlugins << Hierarchy.CustomFieldEntries;
return Ar;
}
};
/**
* After staging when sizes are written back we assign the sizes of shaders
* to these fake assets and we expose a dependency list for "normal" packages
* that use them. This can be used to determine an inclusive size for packages
* that also considers shader sizes.
*/
struct FCookMetadataShaderPseudoAsset
{
// This is artificially generated based on the chunk the shader belongs to
// and its hash. It should be consistent across builds. The plugin the shader
// is assigned to is based on the packages that reference the shader. If the
// shader is only referenced by packages within a single plugin, the shader asset
// will be assigned to that plugin. Otherwise, it will be assigned to a shader
// pseudo plugin.
FString Name;
// Shaders are always compressed, independent of what GetSizesPresent() says.
uint32 CompressedSize = 0;
friend FArchive& operator<<(FArchive& Ar, FCookMetadataShaderPseudoAsset& PseudoAsset)
{
Ar << PseudoAsset.Name << PseudoAsset.CompressedSize;
return Ar;
}
};
struct FCookMetadataShaderPseudoHierarchy
{
TArray<FCookMetadataShaderPseudoAsset> ShaderAssets;
// Entries in this are indices in to ShaderAssets. Use PackageShaderDependencyMap to
// get a package's shaders.
TArray<int32> DependencyList;
// Keyed off a PackageName in the project, returns a [start, end) pair of indices
// into DependencyList for the shaders that package depends on.
TMap<FName, TPair<int32, int32>> PackageShaderDependencyMap;
friend FArchive& operator<<(FArchive& Ar, FCookMetadataShaderPseudoHierarchy& PseudoHierarchy)
{
Ar << PseudoHierarchy.ShaderAssets << PseudoHierarchy.DependencyList << PseudoHierarchy.PackageShaderDependencyMap;
return Ar;
}
};
/**
* Structure serialized to disk to contain non-asset related metadata about a cook. This should always
* exist alongside a Development Asset Registry, and to ensure that the pair is not out of sync, users
* should validate the development asset registry they are using with GetAssociatedDevelopmentAssetRegistryHash().
*/
class FCookMetadataState
{
public:
FCookMetadataState() = default;
FCookMetadataState(const FCookMetadataState&) = default;
FCookMetadataState(FCookMetadataState&& Rhs) = default;
~FCookMetadataState() = default;
FCookMetadataState& operator=(const FCookMetadataState&) = default;
FCookMetadataState& operator=(FCookMetadataState&& O) = default;
bool IsValid() const { return Version == ECookMetadataStateVersion::LatestVersion; }
void Reset() { *this = FCookMetadataState(); }
UE_API bool Serialize(FArchive& Ar);
UE_API bool ReadFromFile(const FString& FilePath);
UE_API bool SaveToFile(const FString& FilePath);
// Plugin hierarchy information
void SetPluginHierarchyInfo(FCookMetadataPluginHierarchy&& InPluginHierarchy) { PluginHierarchy = MoveTemp(InPluginHierarchy); }
const FCookMetadataPluginHierarchy& GetPluginHierarchy() const { return PluginHierarchy; }
// So that unrealpak can update the sizes.
FCookMetadataPluginHierarchy& GetMutablePluginHierarchy() { return PluginHierarchy; }
/**
* Associated DevAR Hash.
*
* This is computed by reading the development asset registry file into a memory buffer and calling
* ComputeHashOfDevelopmentAssetRegistry on the data.
*
* Use this to ensure that the files you are working with were produced by the same cook and didn't
* get out of sync somehow.
*
* *IMPORTANT* If asset registry writeback is enabled during staging, then the hash of the development
* asset registry changes, and you'll need to check against GetAssociatedDevelopmentAssetRegistryHashPostWriteback.
* If you don't know which one you have, check both - they are both valid.
*
* e.g.
* uint64 CheckHash = GetAssociatedDevelopmentAssetRegistryHash();
* bValidDevAr = ComputeHashOfDevelopmentAssetRegistry(MakeMemoryView(SerializedAssetRegistry)) == CheckHash;
*/
void SetAssociatedDevelopmentAssetRegistryHash(uint64 InHash) { AssociatedDevelopmentAssetRegistryHash = InHash; }
void SetAssociatedDevelopmentAssetRegistryHashPostWriteback(uint64 InHash) { AssociatedDevelopmentAssetRegistryHashPostWriteback = InHash; }
uint64 GetAssociatedDevelopmentAssetRegistryHash() const { return AssociatedDevelopmentAssetRegistryHash; }
uint64 GetAssociatedDevelopmentAssetRegistryHashPostWriteback() const { return AssociatedDevelopmentAssetRegistryHashPostWriteback; }
void SetPlatformAndBuildVersion(const FString& InPlatform, const TCHAR* InBuildVersion) { Platform = InPlatform; BuildVersion = FString(InBuildVersion); }
const FString& GetPlatform() const { return Platform; }
const FString GetBuildVersion() const { return BuildVersion; }
void SetHordeJobId(FString&& InHordeJobId) { HordeJobId = MoveTemp(InHordeJobId); }
const FString& GetHordeJobId() const { return HordeJobId; }
static UE_API uint64 ComputeHashOfDevelopmentAssetRegistry(FMemoryView InSerializedDevelopmentAssetRegistry);
// Returns what size information is present in FCookMetadataPluginEntry. This varies based on the platform
// and settings.
UE_API FText GetSizesPresentAsText() const;
ECookMetadataSizesPresent GetSizesPresent() const { return SizesPresent; }
void SetSizesPresent(ECookMetadataSizesPresent InSizesPresent) { SizesPresent = InSizesPresent; }
void SetShaderPseudoHieararchy(FCookMetadataShaderPseudoHierarchy&& InHierarchy) { ShaderPseudoHierarchy = MoveTemp(InHierarchy); }
const FCookMetadataShaderPseudoHierarchy& GetShaderPseudoHierarchy() const { return ShaderPseudoHierarchy; }
private:
ECookMetadataStateVersion Version = ECookMetadataStateVersion::InvalidVersion;
FCookMetadataPluginHierarchy PluginHierarchy;
FCookMetadataShaderPseudoHierarchy ShaderPseudoHierarchy;
uint64 AssociatedDevelopmentAssetRegistryHash = 0;
// Asset registry size writeback changes the AR, so we have a separate hash for that DevAR that this
// also matches.
uint64 AssociatedDevelopmentAssetRegistryHashPostWriteback = 0;
FString Platform;
// BUILD_VERSION definition from definitions.h for the cook.
FString BuildVersion;
// If cooked on Horde, this is the job id that cooked it.
FString HordeJobId;
// Updated by unrealpak when plugin size information is added.
ECookMetadataSizesPresent SizesPresent = ECookMetadataSizesPresent::NotPresent;
};
COOKMETADATA_API const FString& GetCookMetadataFilename();
}; // namespace
#undef UE_API