Cooking refers to a step in the packaging process for Unreal Engine. Cooking determines which assets should be staged and transforms those assets from the format used in the editor to a more efficient runtime format. Transformations can be generic (for all asset types) or system specific (defined in C++ by the asset type’s author).
This document covers the C++ APIs that individual classes and structs use to declare their references, transform their data during cooking, assign their packages to install chunks, and tell the cooker what they need for accurate incremental cooks.
For more information about cooking and packaging, see Packaging and Cooking Games.
Declare Runtime References
Unless otherwise specified, references in this document mean UsedInGame package references.
UsedInGame package references are the package names of objects referenced by runtime objects in the source package. For example, in a level, a StaticMeshActor has a UsedInGame package reference to the package containing its StaticMesh.
The cooker starts with a RootSet of assets that should be cooked. It then does a graph search, where each package is a vertex and each package's references are directed edges.
Generic serialization gathers references from UPROPERTY fields on UCLASS es with instances within the package. System-specific API code can suppress those autogenerated references and add new ones.
Automatic References from Serialization
Generic Serialization
By default, each of the following types causes a runtime reference when declared as a UPROPERTY field:
UObject*TObjectPtrTSoftObjectPtrFSoftObjectPath
This applies whether the field is declared on a UObject saved into a package, or on a USTRUCT embedded within that UObject.
Suppress a Runtime Reference
To stop a field from adding its target to the cook, use one of the following:
| To | Use | Notes |
|---|---|---|
Keep the field out of the save entirely. |
or
| The field is not serialized during save, so no reference is created. |
Serialize the path, but keep the target out of the cook. | Meta keyword within | Applies only to |
Make the field editor-only. |
|
|
Fields That Bypass Generic Serialization
Generic serialization does not cover the following kinds of fields:
Native fields (which lack a
UPROPERTYdeclaration).UPROPERTYs on aUSTRUCTthat bypasses generic serialization.For more information, see Bypass Generic Serialization on a USTRUCT.
Bypassing generic serialization does not necessarily suppress a runtime reference; it depends on the calls made by the C++ serialization code that replaces it. For more information, see Native Serialization.
Bypass Generic Serialization on a USTRUCT
A USTRUCT bypasses generic serialization and takes responsibility for declaring its own references when it does all of the following:
Declares a
TStructOpsTypeTraits.Defines
WithSerializer = truewithin theTStructOpsTypeTraits.Defines the member function
bool Serialize(FArchive& Ar)on the struct, returningtrue.
Native Serialization
A UObject or USTRUCT can elect native serialization for some or all of its member fields. Runtime references are still autogenerated as a side effect of serializing FSoftObjectPath, UObject*, and some wrapper types related to them. The following lists show every type that causes a runtime reference:
Related to
UObject*UObject*FObjectPtrFWeakObjectPtrFLazyObjectPtr
Related to
FSoftObjectPathFSoftObjectPathFSoftObjectPtr
The FArchive API provides further functions to suppress or add runtime references. Any UObject* or FSoftObjectPath passed to operator<< during the SaveSerialization harvest phase is declared as a reference and added to the cook. For FSoftObjectPath, you can suppress this by wrapping the operator<< call in FSoftObjectPathSerializationScope with ESoftObjectPathCollectType::NeverCollect or ESoftObjectPathCollectType::EditorOnlyCollect. For UObject*, you cannot. To avoid adding the target UObject to the cook, you must skip the entire operator<< call in both the harvest phase and the write phase.
Only suppress or add runtime references during the harvest phase of SavePackage serialization.
For more information, see Save Serialization Phases and Cook Operations.
Manual References with AddCookRuntimeDependency
Two contexts expose AddCookRuntimeDependency, which adds references to the cook explicitly:
FObjectSavePackageSerializeContext: TheFArchivepassed into native Serialize functions provides access to anFObjectSavePackageSerializeContextviaGetSavePackageSerializeContext. This function returns non-null duringSerializecalls made by SavePackage, and null otherwise. CallingAddCookRuntimeDependencyon it outside the harvest phase is invalid and causes aSaveError. The context also provides more functionality for configuring the cook.For more information, see Manual Dependency Declaration, or the class and function comments on
FObjectSavePackageSerializeContext.FCookEventContext:OnCookEventprovides anFCookEventContext. WhenCookEvent == ECookEvent::PlatformCookDependencies, you can callAddCookRuntimeDependencyon it. For the signature and how to override it, see Declare Dependencies During Save.
AddCookRuntimeDependency has the same effect as calling operator<< on an FSoftObjectPath during the harvest phase
Editor Save Declarations
When you save a package in the editor, it records all of its UObject* and FSoftObjectPath references in the package header. The editor reads these references at startup (including CookCommandlet startup) and stores them in the AssetRegistry, which returns them when you query the package's references.
The cooker reads these references for each package it encounters and adds them as edges in its graph search — even if the final cooked package does not store them. Using only the references from the final cooked package would be more robust, but reading them ahead of time (without loading and saving each package) allows an up-front graph search that lets the cooker schedule its loads and saves more efficiently.
System-specific code can hide or add to these AssetRegistry references during editor save the same way it does during cook save. Mark these references as EditorOnly rather than hiding them completely because EditorOnly references do not contribute to runtime references during the cook. They remain available to editor operations that need to know which packages a package references.
Most methods for suppressing UsedInGame references during cook saves also work for declaring them as EditorOnly references during editor save, with these adjustments:
Method | Adjustment |
Generic Serialization | Wrap the |
Native Serialization | Serialize |
Declaring EditorOnly references during OnCookEvent is not supported in Unreal Engine.
Unexpected Loads During Cook
When a cook operation on a source package loads another package, the cooker's response depends on whether that load was expected.
Expected loads are declared to the AssetRegistry during the editor save of the source package, either as EditorOnly or UsedInGame. See Editor Save Declarations for more information. Expected loads never add packages to the cook. If the package was declared UsedInGame, the initial scan of the reference graph already added it, so the load itself does nothing further. If it was declared EditorOnly, the cooker ignores the load.
Unexpected loads are undeclared. These happen when system-specific code loads a package during a cook without having recorded it as an AssetRegistry reference during editor save. For example, when loading a package from a config string. By default, the cooker conservatively adds these to the cook as references from the package being cooked. That is often wrong: these packages are frequently needed only for editor operations and only to build the package.
To fix this, modify the system-specific code that calls LoadPackage:
Situation | Resolution |
The load is spurious during cook. | Skip it; for example, using |
You can't skip the load, but it should not become a runtime reference. | Wrap the |
The package should be added to the cook, and can't be declared during editor save. | Wrap it in |
FCookLoadScope(ECookLoadType::UsedInGame) exists for legacy support and is not robust; it can miss packages that are UsedInGame. Any package that’s loaded during cooking should be declared as a reference from the source package. For example, by serializing it as an FSoftObjectPath during the harvest phase — so the cooker knows about it up front and can schedule its load and save more efficiently.
Control Cooked Output
You can perform small transformations synchronously, without a cache, by executing them during PreSave and Serialize. Large transformations should be executed asynchronously, cached in DDC (DerivedDataCache), and fetched from DDC asynchronously, using the BeginCacheForCookedPlatformData API.
Transform Object Data Synchronously
You can execute synchronous uncached transformations in two places: during save serialization and during PreSave.
During Save Serialization
You can execute synchronous uncached transformations during save serialization in a class's override of UObject::Serialize(FArchive& Ar). Within Serialize, the FArchive provides:
Ar.IsSaving(): Whether this is a save serialization.Ar.IsCooking(): Whether this is a cook save serialization.Ar.CookingTarget(): The platform being cooked, or null if not cooking.Ar.GetCookContext(): Other parameters of the cook, or null if not cooking.
Common transformations during serialization include sorting data and clearing containers that the target platform does not need.
To filter references to platform-specific assets, copy them from a generic WITH_EDITOR container into a platform specific runtime container.
Any operations done during serialization that are not appropriate for use on other platforms or for editor operations should be reverted before Serialize returns.
During PreSave
You can execute synchronous uncached transformations during PreSave in a class's override of UObject::PreSave(FObjectPreSaveContext Context). Parameters of the save and the cook are available through the Context argument.
PreSaveRoot and PostSaveRoot act the same as PreSave but are called only for the primary asset of the package.
Asynchronous Cached Transformations
Launch expensive transformations inside BeginCacheForCookedPlatformData rather than during PreSave or Serialize. IsCachedCookedPlatformDataLoaded returns true when the transformations are complete or if they fail to launch.
The task that performs the transformation should:
Declare a key for the object's current data and look up that key in
UE::DerivedData::GetCachebefore beginning the launch.Handle the result of
GetCachein an asynchronous handler rather than blocking on its completion insideBeginCacheForCookedPlatformData.Calculate it asynchronously and
Putit into DDC if the data is not present in DDC.Store the fetched or calculated data where both
IsCachedCookedPlatformDataLoadedandSerializecan access it — the first to report completion to the cooker, the second to write the data into the cooked content.
If the data is not needed outside of cook serialization, clear it in ClearCachedCookedPlatformData, which the cooker calls after the save is complete.
If data fails to transform, or is not applicable to the target platform, BeginCacheForCookedPlatformData can exit early — but IsCachedCookedPlatformDataLoaded must still return true. Failing to return true will soft lock the cooker: the cook will not complete, and the process will need to be killed.
Transformations During Load
We don’t recommend making cook-specific transformations during load because multiple platforms might be cooking. It’s an unnecessary expense if the load occurs for a package that has already been saved or will not be saved.
If it's necessary, use IsRunningCookCommandlet to decide whether the cook is running and then make the desired transformations. This check is reliable because the editor always launches a separate process to run the CookCommandlet; cooking directly in the editor process is not supported.
Remove UObjects or Packages from a Platform
The cooker omits UObjects that are not needed on the platform from the cooked package. References to these UObjects are set to null at runtime whether they come from other UObjects within the package or from UObjects in other packages.
A class can override functions to specify whether its instances are used on certain platforms and build configurations. The following table shows those functions:
Function | Trigger | Effect |
| Returns | The object is stripped from all cooked platforms, except cooked editors such as UEFN (Unreal Editor for Fortnite). |
| Returns | References from the object are still cooked into the package even though the object itself is removed. A special-case modification to |
| Returns | The object is stripped from client runtime on any platform, but can still exist on dedicated server or standalone game cooks. |
| Returns | The object is stripped from dedicated servers. |
| Returns | The object is stripped from that platform's version of the package. Takes an |
If the package keeps no public objects, the cooker omits the entire package.
You can also omit specific packages by name using NeverCook rules. To NeverCook an asset, create a PrimaryAssetLabel that refers to it and set the label's CookRule=NeverCook.
Create or Pass Non-Package Files
Middleware systems, or other systems not originally built in Unreal Engine, can have data that’s easiest to load at runtime through system-specific filesystem calls rather than through the Unreal Engine's package loading system. To support this, override UObject::WriteAdditionalFile and pass filename and content information to its argument.
Store Asset Registry Tags
GetAssetRegistryTags stores tags from a package's assets. Only public, non-child objects that return true from IsAsset are Assets. At editor or runtime startup, Unreal Engine loads the tags from every available package into the AssetRegistry and keeps them in memory throughout the process. Manager systems can use them in the editor or the runtime game to make decisions about asset loading.
In the editor, GetAssetRegistryTags is called on each asset both during load and during save. Calculating a tag during load can be an unnecessary expense if it's costly to compute and does not change until the package is saved. GetAssetRegistryTags takes a context object reporting whether the call comes from SavePackage, and whether the save is a cook save — so your implementation can skip that work when it isn't needed.
The editor keeps all tags. Most tags are not needed at runtime, so a config-driven set of filters removes them during cooking. For more information, see the CookedTags* values in the [AssetRegistry] section of Engine\Config\BaseGame.ini.
Declare Build Dependencies for Incremental Cooking
Incremental cooking keeps data from the previous cook that includes:
The bytes of the cooked package.
Other artifacts produced for the package, such as shader libraries or
AdditionalCookFiles.Dependency data — the list of dependencies declared for the package, and the values those dependencies had when it was cooked.
During an incremental cook, the cooker reevaluates the recorded dependencies for any package requested in the current cook. If every value matches the previous entry, it skips the package, saving cook time.
A previously cooked package is not automatically part of the current incremental cook. The same graph search runs as in a cook from scratch, so if the RootSet or package references change and the package is no longer referenced, it's not recorded. For packages that the cooker skips, the runtime references they reported in the previous cook define the graph edges. Packages that need recooking ignore those and recalculate.
False Positive Skips
The correctness of incremental cooks requires that the cooked packages and other artifacts it produces are byte-for-byte identical to what a full recook would produce. When the cooker skips a package because its declared dependencies did not change, but recooking that package would produce different results, this is a false positive incremental skip. This is a bug.
False positive skips lead to incorrect behavior at runtime. At best, the differences are insignificant and the old version of the package works just as well. At worst, other packages that were correctly recooked rely on data from the new version, and using the old data instead corrupts them in turn.
To detect false positive skips, run the cook in a mode that identifies which packages are incrementally skippable, recooks them anyway, and compares each recooked package against its previous version. You can activate this mode with the -incrementalvalidate commandline argument. You can also run it as a buildgraph task:
Engine\Build\Graph\Tests\IncrementalValidate.xml
IncrementalValidate reports false positives spuriously for packages that suffer from cook indeterminism. To detect indeterminism, run a -diffonly cook after a full recook without syncing any changes. Some indeterminism is intermittent and can be difficult to reproduce, so a clean run is not proof.
Preventing false positive incremental skips requires a thorough report of all the build dependencies of a package. Build dependencies are dependencies that affect the bytes produced for a package, and to be conservative, any change in one causes the package to be recooked. These are different from runtime dependencies, which the source package requests be added to the cook because it needs them at runtime, but which do not affect the cooked bytes. The engine autogenerates some build dependencies during the cook; others you must declare with system-specific API calls.
AutoGenerated Build Dependencies
Object Pointer Dependencies
All engine UCLASSes use TObjectPtr for their UObject pointers, and we recommend that all project types do this.
During the cook, any TObjectPtr dereferenced during cook operations on a package causes its target to be recorded as a Direct build dependency of that package, which causes a recook when the bytes of the target package's editor save change.
Direct build dependencies on save-serialized TObjectPtrs are necessary because changes to the target package (for example, changes that affect NeedsLoadForClient) can cause those imports to be set to null instead of saved into the source package. Setting an import to null changes the bytes stored in the package, so without a recook, the result would be a false positive incremental skip.
Runtime references are a separate matter. They come from the TObjectPtrs serialized into the saved cooked package, and from those serialized into the editor package's saved version and marked as UsedInGame. They also come from the FSoftObjectPaths serialized into saved cooked packages, but these soft references create no build dependency at all.
Config Dependencies
Unreal Engine collects config parameter accesses made through the GConfig API. It records the config variables read as Config Build Dependencies, and recooks the package if their values in the config files change.
Config Build Dependencies are susceptible to a hidden cached dependency flaw: code caches the config read into another variable that can then be read without going through GConfig. This is commonly done with static function variables:
static bool bHasMyCookParameter = <ReadFromGConfig>();
By default, cached dependencies are hidden, so the cooker does not record them when a package reads the cached value during its cook operations. The package is then not recooked when the config value changes. Because of this, do not cache the results of GConfig. If you do cache them, the system-specific code using the cached values must record the dependency manually.
Redirector Dependencies
Unreal Engine collects CoreRedirect and ObjectRedirector changes automatically. A redirect can change the path used when a UObject loads, and that path is what gets stored in the bytes of the cooked package. Even for soft runtime references, a redirect change can change those bytes.
Because of this, any CoreRedirect or ObjectRedirector that affects an object path stored in a cooked package becomes a build dependency of that package. The package is recooked when a redirector's value changes, when a redirector is removed, or when a new one is added.
Native Class Schema Dependencies
Unreal Engine collects Native Class Schema dependencies for the class of each object that exists in the editor package or is saved into the cooked package. A Native Class Schema includes:
All
UPROPERTYnames and types in the class.The
CustomObjectVersionsthe class declares as used.Any additional system-specific data the class reports from
AppendToClassSchema, aUObjectvirtual function.
Suppress AutoGenerated Build Dependencies
The build dependencies autogenerated by TObjectPtr dereferences may be more than a class needs. To suppress them, use UE_COOK_RESULTPROJECTION_SCOPED(UE::Cook::ResultProjection::None).
Manual Dependency Declaration
A class can declare dependencies for the packages that contain its instances. There are three places to do this: AppendToClassSchema, save Serialize, and OnCookEvent(ECookEvent::PlatformCookDependencies).
AppendToClassSchema
AppendToClassSchema does not declare dependencies. Instead, the code you write calculates the current value of each dependency and reports those values, in a deterministic order, to FAppendToClassSchemaContext.Update.
The cooker calls AppendToClassSchema once per class for the entire cook, and it applies to every package containing an instance of that class. The reported value is saved into the CookDependencies of each of those packages; if it changes, they are recooked.
AppendToClassSchema is useful for declaring config variables, external library versions, or anything else that affects all instances of your class. It’s also a convenient place to add a VersionGuid that can be bumped whenever system-specific code called during cook operations for the class changes. For more information, see Version Bumps for Code Changes.
Declare Dependencies During Save
Two contexts expose AddCookBuildDependency, which takes an FCookDependency constructed by the caller:
Context | Where you get it | Valid when |
|
|
|
| The |
|
A UObject subclass can override OnCookEvent:
void OnCookEvent(UE::Cook::ECookEvent CookEvent, UE::Cook::FCookEventContext& Context)Be sure to call Super::OnCookEvent.
OnCookEvent is a single virtual function that responds to several cooker hooks, distinguished by the CookEvent argument. With ECookEvent::PlatformCookDependencies, the cooker calls it both when saving a package during a cook and when saving a package from the editor.
During a cook save, calling AddCookBuildDependency on any type adds that build dependency to the cooked package.
During an editor save, AddCookBuildDependency works only for FCookDependency::Package(...), and is ignored for all other types. It records the package as a build dependency for both the cook and the AssetManager's graph search, which assigns ChunkIds from primary assets to their referenced secondary assets.
To construct a dependency, call the appropriate static function on FCookDependency. For more information, see FCookDependency Types.
FCookDependency Types
This section covers the most common and most complicated types. For the full list, see the class and function comments on FCookDependency.
Type | Behavior |
Direct Build Dependency on another package | Causes a recook when the bytes of that package change in an editor save. |
Transitive Build Dependency on another package | Causes a recook when any of that package's own dependencies change. |
Function dependency | Causes a recook when the values the function reports change. |
Config dependency | Collected automatically from |
External file dependency | Not collected automatically. Must be declared manually. See the comments on |
Function dependencies are the most complicated and most powerful type. To declare a function dependency:
Register the function with the cooker using the macro
UE_COOK_DEPENDENCY_FUNCTION(Name, Function).In a class's
Serialize, callFCookDependency::Function(Name, Args)to declare that the containing package depends on that function.Argsis a compact binary value holding what to pass to the function — filenames, for example, or an enum naming which subcategory of the function's behavior the class instance uses.
The cooker then calls the function once during the original cook and again during each incremental cook, passing it those Args and an FCookDependencyContext. The function evaluates the current values of its hardcoded dependencies and of the dependencies named by Args, then passes that list, in deterministic order, to FCookDependencyContext.Update. If the reported values change, the package is recooked.
Version Bumps for Code Changes
The cooker calls system-specific C++ functions for each object in a package, as described in Cook Operations. That code determines some or most of the bytes in the package, so changing it changes the cooked bytes. This means the package must be recooked, or the result is a false positive incremental skip.
These changes most commonly occur in PostLoad, Serialize, and PreSave. For example, PostLoad might gain a change that detects invalid old data and replaces it with validated data, or Serialize might gain a change to its platform-specific transformations.
The cooker cannot auto-detect changes to the native functions used in cook operations, so you must submit those changes together with a version bump. This includes Cook Operations, such as GetAssetRegistryTags and the serialize function for structs shared by multiple classes, and external code used by a class, such as a third-party compression library.
Methods to Bump the Version
There are three ways to bump the version:
Modify the
UPROPERTYs on the class. The class schema incorporates any modification automatically, so the cooker recooks the package when it notices the schema has changed. This is the preferred method when it applies, because it requires no extra C++ changes.Add or bump a
CustomVersionused by the class. Bumping anyCustomVersiona class uses recooks every package containing that class. A class declares itsCustomVersionsin two ways:By calling
Ar.UsingCustomVersioninside its override ofUObject::Serialize. This covers calls made when the object was serialized during the package's editor save, and calls made during the synthetic save-serialize on the class's CDO that runs at editor or cooker startup.Through the class's static
DeclareCustomVersionsfunction.UnrealHeaderTooldetects this staticUObjectfunction and calls it from the UClass created for the subclass.
Alter the data reported from the class's override of
UObject::AppendToClassSchema. AsAppendToClassSchemadescribes, its purpose is to declare global non-package data that affects the save of objects of that class. That data can include an arbitrarily formatted constant you bump whenever native class serialization changes — add one as soon as you need it. Because these bumps can happen on competing source control streams, serialize a guid rather than a version number.
There is no equivalent of AppendToClassSchema for structs that automatically affects the class schema of every class using the struct. To change code that affects a struct's cooked bytes, either change a UPROPERTY on the struct, or add a UsingCustomVersion call to the struct's Serialize function and bump that CustomVersion.
The following example shows each method applied to UTexture:
Bump the version even when you are not sure it's needed. An unnecessary bump costs everyone on the project seconds or minutes on their next cook, so it's tempting to optimize. But a missing bump costs many people minutes or hours of diagnosis, and that time is not automated — it monopolizes their attention. Err toward slowing one cook rather than breaking the build.
Declare Other Classes' Schemas
Only classes present in the on-disk editor-saved version of the package, or imported from the cooked version, cause the package to be recooked when their schemas change. To have your package recook when the schema of some other class changes, declare that class using one of these methods:
The class's static
DeclareConstructClassesfunction.UnrealHeaderTooldetects this staticUObjectfunction and calls it from theUClasscreated for the subclass.AddCookBuildDependency(FCookDependency::NativeClass)on theFCookEventContext. For more information, see FCookDependency Types.
Assign Packages to Chunks
The following sections cover chunking in detail. For an introduction to chunk assignment in Unreal Data builds, see Cooking and Chunking.
Unreal Engine supports incremental install. Incremental install is the ability to download and play a game before all of its data files are present on disk. Subsets of those files, called chunks, are downloaded and copied to disk individually, letting the game run even if only the first set of chunks is downloaded. Game modes or levels can specify which chunks need to have finished installing before they can run.
Each chunk has an id — an integer greater than or equal to 0. The project configures the use of a given chunk id and what packages are assigned to it.
Unreal Engine implements chunks via pak files and IoStore container files, assigning each logical chunk a pakfile or container file. Once present on disk, these files are dormant and unused by UE unless they are mounted. Mounting and unmounting pakfiles requires project-specific code. Unmounting reduces some of the filesystem CPU time and memory used to manage the individual package files in the pakfile.
Chunk 0 (Pakchunk0-windows.*) is part of the install chunks, and the engine puts many vital engine packages into it. Projects can add other chunks to the list of install chunks.
AssetManager Chunk Assignment
To specify chunks, we recommend creating a PrimaryAsset (UPrimaryDataAsset). Each PrimaryAsset has:
A
CookRulethat specifies whether to cook it.A
Prioritythat resolves conflicts with other PrimaryAssets.A
ChunkIdthat specifies the single Chunk it goes into.
The most direct type of PrimaryAsset is a PrimaryAssetLabel (UPrimaryAssetLabel), which exists only to label Assets for cooking and chunking. PrimaryAssetLabels have fields to assign the CookRule, Priority, and ChunkId, and fields to specify which other Assets belong in their chunk.
You can also create other PrimaryAsset types by subclassing UPrimaryDataAsset. For example, a character, faction, inventory set, or other game concept can then specify that the cooker should assign all of its references to a given chunk. Maps are a common candidate, and are a special case that AssetManager handles with extra map-specific code.
The AssetManager treats PrimaryAssets as roots of a graph of Assets. The edges are the package references that each Asset makes. UAssetManager::UpdateManagementDatabase implements the traversal: it starts from all known PrimaryAssets, each with references to real assets and a ChunkId, and runs a transitive search over PackageDependencies from the AssetRegistry.
The traversal searches each PrimaryAsset and assigns its ChunkId to every Asset it references. When PrimaryAssets with different ChunkIds reference the same Asset, the result is a ChunkId Conflict. Each PrimaryAsset carries an integer Priority that resolves this conflict:
PrimaryAssets with higher
Priorityvalues are more important.PrimaryAssets with negative
Priorityvalues all rank equally.
The following table describes how conflicts resolve:
Conflict | Result |
Different priorities | The higher-priority PrimaryAsset assigns its |
Tied priorities | The Asset receives the |
Define the Edges of the Reference Graph
When traversing the graph of PrimaryAssets and the Assets they reference, the edges of the graph are defined by package dependencies stored in the AssetRegistry. This is implemented in FAssetRegistryImpl::SetManageReferences. Two dependency properties define those edges, and each is created differently and traversed for different purposes:
EDependencyProperty::Gamemarks a dependency that is used in game rather than beingEditorOnly.SavePackageauto-generates these dependencies in the editor, from theUObjects that the package imports from other packages. Imports fromUsedInGameUObjects and properties get the property; imports fromEditorOnlyUObjects and properties do not. These dependencies are traversed to assignCookRules, which decide whether a package is cooked, and to assignChunkIds.EDependencyProperty::Buildis orthogonal toEDependencyProperty::Game, so a dependency can carry either property or both. A dependency with this property means that data in the dependency asset is incorporated into the cooked bytes of the referencer asset. These dependencies are traversed when assigningChunkIdsto cooked assets, but not when deciding whether assets are cooked.
To declare an EDependencyProperty::Build dependency, override UObject::OnCookEvent in your native class and use ECookEvent::PlatformCookDependencies. During the editor save of a package, OnCookEvent is called on each UObject in the package with Context.IsCooking() == false. Calling Context.AddLoadBuildDependency in that case registers the dependency as EDependencyProperty::Build.
Chunk Assignment Defaults
Every cooked package has to get onto disk. The cooker sends the following packages to Chunk0, which is part of the install chunks:
Packages | Implemented In |
StartupPackages — those already loaded when |
|
Packages in |
|
Packages not assigned to any chunk. |
|
Chunk Dependencies
Config defines a chunk dependency tree, which the default object of UChunkDependencyInfo manages. A child node in the tree means those chunks never mount unless their parent chunks mount as well.
FAssetRegistryGenerator::FixupPackageDependenciesForChunks and UAssetManager::GetPrimaryAssetSetChunkIds remove assets from child ChunkIds when those assets are also present in a parent ChunkId.
When an asset has multiple assigned chunks, use FindHighestSharedChunk to find the chunk that would mount as a parent of all the requested chunks. This is useful for creating games where assets exist in only one chunk.
Unreal does not support ChunkId promotion (moving an asset from child ChunkIds to a parent ChunkId when it appears in more than one child). You can implement it by overriding UAssetManager::GetPrimaryAssetSetChunkIds.
Cooking Concepts
Save Serialization Phases
Save serialization calls Serialize twice: first in the harvest phase, then in the write phase. Both calls happen for a UObject class's override of void Serialize(FArchive& Ar) and for a struct's native serializer. Ar.IsSaving() returns true for both calls, and during a cook, Ar.IsCooking() returns true for both. For the requirements for declaring a struct's native serializer, see Bypass Serialization on a USTRUCT.
Be sure to call Super::Serialize.
Harvest phase:
Ar.IsObjectReferenceCollector()returnstrue.Ar.GetSavePackageSerializeContext()->GetPhase()returnsEObjectSaveContextPhase::Harvest.The phase harvests
UObject,FSoftObjectPath, andFNamereferences, and does not otherwise use the serialized data.You can serialize extra
FName,UObject*, andFSoftObjectPathvalues that the write phase does not serialize. These become runtime references.
Write phase:
Ar.IsObjectReferenceCollector()returnsfalse.Ar.GetSavePackageSerializeContext()->GetPhase()returnsEObjectSaveContextPhase::Write.The phase marshals the serialized data to bytes and stores it in the binary blob for the
UObjecton disk.Serializing an
FName,UObject*, orFSoftObjectPathcauses aSaveErrorunless that target was also serialized during the harvest phase.
Cook Operations
The cook operations for a package are the cooker’s calls to engine code and system-specific code that load, transform, and save the package. The sections below describe the three categories of operations.
Load
Loading starts with a call to LoadPackage, which happens in one of three ways:
The cooker explicitly calls
LoadPackagefor the package.The package loads during startup, before the cooker starts.
LoadPackageInternalis called on the package on behalf of another package that imports it.
LoadPackage is part of LinkerLoad. The LinkerLoad code calls generic code for each package, and also calls system-specific virtual functions on the UObjects within it, such as Serialize and PostLoad. Each of those virtual function calls is a load cook operation on the package.
Transform
Transform cook operations are virtual functions on the UObject:
GetAssetRegistryTagsBeginCacheForCookedPlatformDataIsCachedCookedPlatformDataLoaded
You can also transform data during PreSave or save Serialize, but those count as save cook operations rather than transform ones, because most classes do not use them for transforms.
Save
Saving starts with a call to SavePackage, and includes the SavePackage code and the virtual functions it calls, such as PreSave, Serialize, and GetAssetRegistryTags.
Attribution
The cooker does not directly call functions on objects within a package outside Load, Transform, and Save. But because the cooker runs in the full-power editor environment, the cooker's calls to those functions can trigger system-specific code on other objects, and that code can call any function on objects in any package.
The cooker does not attribute calls outside that set to the target package. It attributes them to the source package containing the object that instigated them.
Attribution also applies to autogenerated build dependencies: during each cook operation on a package, the cooker assigns any autogenerated build dependencies it encounters to that package.