[5.7] Pak / IoStore / MultiCook: Assets do not get assigned to pak/utoc/ucas correctly

Hello :slight_smile:

So as the title said, i’m trying to use the multi cook with pak + iostore, but it seems that the multi process cook is completely ignoring the chunking, most of my assets ends up in the pakchunk0, all other utoc are empty, and some paks have data because of custom rules in defaultPakFileRules

Is it a known issue maybe resolved somewhere along the 5.8 / main already ?

[Attachment Removed]

What do you mean by MultiCook? Do you mean multiplatform cook (e.g. -targetplatform=Windows+PS5) or multiprocess cook (e.g. -cookprocesscount=4)? Does the problem definitely not occur with singleplatform or singleprocess cook?

No, I don’t know of any resolved issue in 5.8 or main that would address this; we have encountered bugs and configuration errors that cause assets to end up in pakchunk0, but I don’t recall any cases where this was caused by multiplatform or multiprocess cook.

What is the commandline to your cook? You can find this in the cook log output, search for run=cook.

Problems that I remember from the the past that caused chunks to be ignored and all assets to be assigned to 0:

  • Startup packages: ALl packages loaded during editor startup (before the end of StartCookByTheBook) are forced into packchunk 0; this is legacy behavior intended to make it easy to cook any packages that your game code expects to load at startup in every configuration.
  • Cooking without specifying manifests in ProjectSettings (bGenerateChunks=true) or on commandline (-manifests)

[Attachment Removed]

Sorry that wasn’t clear :slight_smile: yeah i’m talking about the multiprocess cook :slight_smile: not through the commandline directly, via ini files, but probably the same behavior (still worth noticing).

Regarding the problem, yes i’m positive, the difference between a good chunk build and a bad one is only related to the multiprocess cooking, the way we do this is by creating custom primary assets, rules and chunkId, with and without IoStore, the cook take the good chunk depending on where the asset is referenced in theses primary assets, but as soon as the multiprocess cooking is enabled, all assets go to the chunk0.

I don’t have the cook logs yet but yeah i did dig a bit before and aside from the ini config, i was seeing the cooking and the workers getting passed on the -manifests option

and yes i was having bGenerateChunks as well :slight_smile: That seemed weird to me as well, since i’m seeing a lot of people using it, but maybe a config somewhere is messing up with something, since this project went through quite a few engine bump

the cook is run by this way

[Image Removed]the ini looks like this

[Image Removed]packaging settings

[Image Removed]and the cook command

C:\Powerz\Powerz\Powerz.uproject -run=Cook -TargetPlatform=Windows -zenstore -unversioned -skipeditorcontent -NoErrorSummary -ZenStore -fileopenlog -manifests -abslog=C:\Users\merle\Documents\Unreal\Engine\Programs\AutomationTool\Saved\Cook-2026.05.28-01.40.54.txt -stdout -CrashForUAT -unattended -NoLogTimes

[Attachment Removed]

Forgot also to mention that i was able to work around slightly on the issue with that POC

[Image Removed]where i go do the research myself into our assets, it could help to pinpoint where the problem could be

I didn’t look further yet to see where the data is not initialized durint the multicook, since i’m having other problems with it (one worker getting stuck for more than 20 minutes with around 8000 packages remaing)

[Attachment Removed]

Is the number of packages cooked the same in single process cook versus multi-process cook? You can find the number of packages cooked by looking in the AllChunksInfo.csv file: <ProjectRoot>\Saved\Cooked\<Platform>\<ProjectName>\Metadata\AllChunksInfo.csv. On a previous report of chunking not working, the problem was actually that the packages outside of chunk0 were not being cooked rather than that they were assigned to the wrong chunk. You can also look at this file to check whether the problem lies within the cooker or occurs later during staging; the chunk assignments written out by the cooker are stored in this file as the first index of each line. Those chunk assignments should match the chunk assignments read during staging; those assignments are stored in the files <ProjectRoot>\Saved\Cooked\Platform\<ProjectName>\Metadata\ChunkManifest\pakchunk*.txt.

Assuming all the packages are cooked but are assigned to the wrong chunk in the cook’s output files, we’ll need to start from scratch on debugging this; I don’t have any ideas what could be causing it. The assignment of packages to chunks occurs on the director and that assignment by default does not rely on any data recorded from the load and save of assets, so I expected that it could not possibly be affected by the presence of multi-process cook. The code you showed in UPzAssetManager also looks like it should be independent of multi-process cook. Is that all the code that is present in the function? What do you mean by POC?

My recommendation is to hit a breakpoint in GetPackageChunkIds during multi-process cook. This is called on the director process only; it is not called on the CookWorkers. It is called during UCookOnTheFlyServer::CookByTheBookFinished -> FAssetRegistryGenerator::FinalizeChunkIDs ->CalculateChunkIdsAndAssignToManifest -> UAssetManager::GetPackageChunkIds.

When your breakpoint gets hit, first look up the stack In CalculateChunkIdsAndAssignToManifest. Look at the size of StartupPackages and ensure that it looks correct; it should include only a few thousand packages - the ones that were loaded before the cook started - and should not include all packages. (e.g. on Lyra the number of StartupPackages is 1032 and the number of cooked packages is 3878). You can see the number of cooked packages by looking at the length of CookedPackages parameter in FAssetRegistryGenerator::FinalizeChunkIDs.

If StartupPackages looks correct, step through the code in your GetPackageChunkIds function. Is it behaving correctly? Is it failing to find any data possibly because it uses cached data that is populated when packages load during the cook, and some packages it relies on were not loaded because they were saved on CookWorkers rather than on the CookDirector?

If the behavior of GetPackageChunkIds also looks correct, then we will need to look at the code that comes after FinalizeChunkIds and reads those chunkids, in FAssetRegistryGenerator::SaveManifests -> GenerateStreamingInstallManifest.

[Attachment Removed]

Hello :slight_smile: Yes that’s the all code, POC stands for proof of concept, quick and dirty way to work around the chunk assignement issue, i basically go over our custom primary assets and overriding myself the good chunk, and if not found, fallback to the default asset manager function

All the packages are cooked, like they are without the multiprocess cook

So i checked the files, in AllChunksInfo.csv, everything is assigned to chunk 0, and it seems like the manifests are only wrottent for the chunk0 itself, which i assume might be the issue

[Image Removed]I’m gonna get some more info with debugging, maybe they are all considered startup packages, will let you know

[Attachment Removed]

So it seems to have correct values for startup packages and cooked packages

[Image Removed]i’m gonna look into the chunkmap built after the cooking, i’m guessing the issue might be there

Do you happend to know anything about an issue like this ?

[Image Removed]it’s slowing down my debug quite a bit, it seems that the worker is not responding to retraction request, i’m guessing the worker is waiting on shader compilations, problem is that’s the 60th cook i do with the same data, i don’t have any missing shadermap anymore, or should not

[Attachment Removed]

I think i nailed it, i’m guessing it’s not working because this is never happening in the case of the multiprocess cook

[Image Removed]leading to my managers to never get their chunks ids updated, and so all assets are defaulting to chunk0

will see if i hit the breakpoint in the multiprocess context

[Attachment Removed]

As a proof of concept i did apply this code

[Image Removed]the fallback to load sync, which eventually fixed my initial chunk map as well, and leverage another issue that was hidden

[Image Removed]without the check || IsRunningCookCommandlet, my editor primary asset type were getting cooked

[Attachment Removed]

The IsRunningCookCommandlet() is actually not a good fix, this is preventing assets to find theses primary assets at cook time

[Attachment Removed]

A check editor here seems to do it

[Image Removed]

[Attachment Removed]

Sorry I didn’t think about that before; it’s been a while since I have looked at the PrimaryAssetLabel code.

Do you have your own subclass of PrimaryAssetLabels? Currently the basee class AssetManager does not well support subclasses of PrimaryAssetTypes; it is hardcoded to only load the baseclass of PrimaryAssetLabels. It loads all instances of those at the beginning of UpdateManagementDatabase via a call to ApplyPrimaryAssetLabels. If you have your own subclass of PrimaryAssetLabel, you should override ApplyPrimaryAssetLabels and load all your PrimaryAssetLabel types so that they call UpdateAssetBundleData. This will be pretty much the same as what you wrote above, except that I recommend you call it only for PrimaryAssetLabel types, not for all PrimaryAsset types. This recommendation is only important for performance; some primaryassets (such as Maps) are very expensive to load, so we don’t want to support this dynamic update for them. PrimaryAssetLabels are the ones that we make cheap to load and that we therefore allow dynamic update of when they load.

For bIsEditorOnly, I’m surprised that reading that variable in UpdateManagementDatabase prevents assets from being cooked; that function is only supposed to influence chunk assignments, not influence whether ■■■■■ get cooked.

The decision of whether to cook PrimaryAssets is made in UAssetManager::ModifyCook. You can see it is checking bIsEditorOnly there. Does one of your PrimaryAsset types somehow have an override of bIsEditorOnly, rather than using the one set in ini, and that override is not being applied in time before ModifyCook is called?

[Attachment Removed]

No worries :slight_smile: Yes indeed we have a subclass of primary asset, thanks i will update that to load where it fits better :slight_smile:

I was surprised too, especially since we are using that since a long time, but i guess the fact that the primary asset were loaded later during the cook did hide that problem, we are using a primary asset type for the quest, but my packaging step is taking care of everything related to cooking, but yeah without that check quests that have been unused for years were starting to get cooked, i’m guessing they are included by the fact they are primary asset, which is why my chunk primary asset were cooked as well, since i don’t reference them anywhere

No at the beginning we were not having this primary asset as editor-only in the project settings, it’s when it starts to get cooked that i checked that box in the settings, i’m guessing that’s because they are include in the cooking process before the modifycook is even called

[Attachment Removed]

Here is a proposal based on your recommendations

[Image Removed]

[Attachment Removed]

I found the sources of the massive slow down as well, this was coming from wwise additional file cooking, not sure why it behave like that, but the cooking on the worker is really slow to send back the file to the director, while it seems the file are already wrotten on the disk, to solve this i did force all wwise asset to be assigned to the local worker

[Attachment Removed]

I found I had to make a bigger change than the one you wrote above; creating the field on FPrimaryAssetTypeInfo did not pass it through to the FPrimaryAssetTypeInfo stored in AssetTypeMap, because the data is copied from UAssetManagerSettings’ copy of the FPrimaryAssetTypeInfo into AssetTypeMap by the function ScanPathsForPrimaryAssets. I had to change the function prototype of ScanPathsForPrimaryAssets to pass through the new variable. I also renamed the variable you created from bRelevantToChunking into bNeedsLoadForCooking, since the behavior we are adding is that we load the PrimaryAsset.

I am still testing this change and plan to submit it in a few days. Here is the new version of ApplyPrimaryAssetLabels that I wrote. Let me know if anything looks wrong. I was reminded of the the FSoftObjectPathSerializationSerializationScope at the top of this function (present before my change); I think that is what is necessary to be able to load your bEditorOnly PrimaryAssets without adding all of their referenced packages to the cook. Without the scope, those SoftObjectPaths are automatically added because the package holding them is a startup package (a package loaded before a checkpoint is reached during StartCookByTheBook).

void UAssetManager::ApplyPrimaryAssetLabels()
{
	// For the benefit of the PrimaryAssets we load in this function, turn off soft object path tracking to avoid
	// causing the SoftObjectPaths serialized into their list of bundles from automatically being cooked.
	FSoftObjectPathSerializationScope SerializationScope(NAME_None, NAME_None, ESoftObjectPathCollectType::NeverCollect, ESoftObjectPathSerializeType::AlwaysSerialize);
 
	TArray<FPrimaryAssetId> Assets;
	for (const TPair<FName, TSharedRef<FPrimaryAssetTypeData>>& TypePair : AssetTypeMap)
	{
		FPrimaryAssetType PrimaryAssetType = FPrimaryAssetType(TypePair.Key);
		const FPrimaryAssetTypeData& TypeData = TypePair.Value.Get();
 
		// Load from disk all UPrimaryAsset instances that are PrimaryAssetLabels, or are any other PrimaryAssetType
		// that opts-in to bNeedsLoadForCooking.
		if (TypeData.Info.bNeedsLoadForCooking || PrimaryAssetType == PrimaryAssetLabelType)
		{
			for (const TPair<FName, FPrimaryAssetData>& Pair : TypeData.GetAssets())
			{
				Assets.Add(FPrimaryAssetId(PrimaryAssetType, Pair.Key));
			}
		}
	}
 
	TArray<FName> LoadBundles; // Empty, load the Asset but not its bundles.
	FAssetManagerLoadParams LoadParams;
	LoadParams.Priority = FStreamableManager::DefaultAsyncLoadPriority;
	UE::FSourceLocation Location = UE::FSourceLocation::Current();
 
	TSharedPtr<FStreamableHandle> Handle = LoadPrimaryAssets(Assets, LoadBundles, MoveTemp(LoadParams), MoveTemp(Location));
	if (Handle.IsValid())
	{
		Handle->WaitUntilComplete();
	}
	
	// PostLoad in UPrimaryDataAsset calls UpdateAssetBundleData.
	// PostLoad in PrimaryAssetLabel sets PrimaryAssetRules overrides. Any other class that opts-in to
	// bNeedsLoadForCooking and wants to set PrimaryAssetRules overrides needs to do the same.
}

For the slow WWise additional file cooking, we added some code in 5.7 to avoid collisions between additional files written by cookworkers; maybe that code is misbehaving. FCookDirector::MoveFileSynchronized in CookDirector.cpp. It is supposed to be a small IO cost because it is just moving the files that were written by the CookWorkers, from their per-worker temp directory into the final synchronized location. Those two directories are set up in UCookOnTheFlyServer::WriteFileOnCookDirector in CookOnTheFlyServer.cpp.

CookWorkerClient->ReportFileTransfer(TempFileName, FileData.Filename);I assumed TempFileName and FileData.Filename would be on the same drive, so that this would be a fast rename, but maybe that is not guaranteed. Are they on different drives in your case? Is my guess correct that the the IFileManager::Get().Move call is the line that is making the handling of the files slow?

[Attachment Removed]

Thanks :slight_smile: Ah yes make sense, i was going through the settings directly, that’s why i didn’t run into the issue, i’m keeping that for now, but let me know when the official commit is pushed so i will replace what i did :slight_smile:

Actually i don’t think the issue comes from that function, from what i saw putting some logging around theses functions, it seems more that the worker or the director is slow to get the messages concerning the files, not moving the files itself, it’s getting some files, moving them, then it waits a bit, getting some other files, and so on

CI related, all the workers are on the same drive, same machine

I was suspecting at first that Wwise was the issue and the writing of actual files was slow, but the problem disappeared immediatly when i moved wwise to be cooked only on the local worker

[Attachment Removed]

I have submitted the AssetManager change in CL 54900060, aka github commit 760547a5c2d85fc35a7457f5546ca85a076d7ae6.

> AssetManager: Add bNeedsLoadForCooking to PrimaryAssetTypeInfo, so that PrimaryAssets other than PrimaryAssetLabel can opt in to being loaded during cook to override their data AssetBundles and other data saved at their last editorsave.

> Change the arguments of UAssetManager::ScanPathsForPrimaryAsset to take a PrimaryAssetTypeInfo to copy from rather than passing through each of the fields separately.

[Attachment Removed]

Thanks for that :slight_smile: i will look into integrating this :slight_smile:

[Attachment Removed]