In our project we use the LegacyIterative cooking mode, and also have SkipOnlyEditorOnly cook setting enabled which prevents editoronly references from cooking.
We are noticing that in this scenario LegacyIterative cooking also skips treating editoronly references as build dependencies of an asset.
We are wondering if this is expected behavior. If yes, what would be some ways to force an editoronly reference to be considered a build dependency?
We have a custom asset type class that looks as follows:
UCLASS()
class A : public UObject
{
#if WITH_EDITORONLY_DATA
UPROPERTY()
TSoftObjectPtr<class B> EditoronlyReferenceToB;
#endif
}
The behavior we want is for object of class A to recook anytime the object in EditoronlyRefrenceToB changes.
At the same time we don’t want object in EditoronlyRefrenceToB to be cooked.
[Attachment Removed]
Sorry for the delay on this response; I was OOO for a few weeks.
Failing to propagate along editoronly references is not expected behavior, but your example additionally has the reference as a soft reference (TSoftOjbectPtr rather than TObjectPtr), and failing to propagate along soft references is indeed expected. We do not propagate along soft references because those are usually not build dependencies - the cooked bytes of the package holding the reference usually does not need to change in response to changes in the editor version of referenced package.
You could make a local change to follow soft references as well, but making that change will in general cause spurious invalidations and therefore is not something we want to submit as the vanilla engine behavior.
Side note: I recommend updating to 5.7 and changing over to the new version of iterative cook - incremental cook - which will allow you to write c++ code in the class holding the reference to let the cooker know that the reference should be a build dependency and cause the holder of the reference to recook.
LegacyIterative does a graph search starting from the list of modified editor packages (package’s SaveGuid has changed) and following edges created from each package’s AssetRegistry dependencies. This is implemented in //UE5/Release-5.6/Engine/Source/Editor/UnrealEd/Private/Commandlets/AssetRegistryGenerator.cpp:1449. The edges it follows are all hard dependencies and all build dependencies (both ingame and editoronly for each of those categories). You could change this to add another query to add the Soft dependencies.
//UE5/Release-5.6/Engine/Source/Editor/UnrealEd/Private/Commandlets/AssetRegistryGenerator.cpp:1449
TArray<FAssetIdentifier> Referencers;
State.GetReferencers(ModifiedPackage, Referencers, UE::AssetRegistry::EDependencyCategory::Package,
UE::AssetRegistry::EDependencyQuery::Hard);
State.GetReferencers(ModifiedPackage, Referencers, UE::AssetRegistry::EDependencyCategory::Package,
UE::AssetRegistry::EDependencyQuery::Build);
// NEW CODE START
State.GetReferencers(ModifiedPackage, Referencers, UE::AssetRegistry::EDependencyCategory::Package,
UE::AssetRegistry::EDependencyQuery::Soft);
// NEW CODE END
[Attachment Removed]
Thank you for the response. I’m not sure if your change alone will be sufficient to address our issue. I believe there is another fundamental issue here that prevents the legacy iterative dependency scanner from picking up the dependency as changed. We have the bSkipOnlyEditorOnly setting enabled and this causes the editor only dependency to not be cooked, thus its FAssetPackageData is never added to the cooked package registry, which the legacy iterative dependency scanner uses in order to determine that the dependency is out of date. Is there a way to force editor only FAssetPackageData to be added to the asset registry without also cooking that asset?
I see that the new incremental cook has a lot of new and very powerful ways of expressing when an asset is out of date and I would love to take advantage of this system, but unfortunately for us an upgrade to UE 5.7 is not going to be possible on this project.
[Attachment Removed]
Roger, I see why that would fail now that you’ve pointed it out. To fix that problem, then, I think the best way is to add code into CookByTheBookFinishedInternal, before the first call to LockAndEnumeratePackageDatas:
void UCookOnTheFlyServer::CookByTheBookFinishedInternal()
{
using namespace UE::Cook;
check(IsInGameThread());
check(IsCookByTheBookMode());
check(IsInSession());
check(PackageDatas->GetRequestQueue().IsEmpty());
check(PackageDatas->GetRequestQueue().GetDiscoveryQueue().IsEmpty());
check(PackageDatas->GetRequestQueue().GetBuildDependencyDiscoveryQueue().IsEmpty());
check(PackageDatas->GetAssignedToWorkerSet().IsEmpty());
check(PackageDatas->GetLoadQueue().IsEmpty());
check(PackageDatas->GetSaveQueue().IsEmpty());
check(PackageDatas->GetSaveStalledSet().IsEmpty());
// NEW CODE START
AddEditorOnlyBuildDependencies();
// NEW CODE END
TArray<FPackageData*> DanglingGenerationHelpers;
PackageDatas->LockAndEnumeratePackageDatas([&DanglingGenerationHelpers](FPackageData* PackageData)
...
In AddEditorOnlyBuildDependencies, you will
- Find the packages to add by searching the AssetRegistry dependencies for each cooked package, asking the AssetRegistry for their editor-only dependencies, and removing the packages that were already cooked.
- For each package, add an entry for the package into the generated asset registry by following the same code that is executed by SaveCookedPackage when we find that a requested package to cook is editor-only
- Mark the package cook attempted but failed, matching what SaveCookedPackage does for editor-only packages.
void UCookOnTheFlyServer::AddEditorOnlyBuildDependencies()
{
using namespace UE::Cook;
TArray<FPackageData*> PackagesCookedOnAnyPlatform;
TSet<FPackageData*> PackagesCookedOnAllPlatforms;
PackageDatas->LockAndEnumeratePackageDatas(
[&PackagesCookedOnAnyPlatform, &PackagesCookedOnAllPlatforms, this](FPackageData* PackageData)
{
if (PackageData->HasAnyCookedPlatform()) // bIncludeFailed=true by default
{
PackagesCookedOnAnyPlatform.Add(PackageData);
if (PackageData->HasAllCookedPlatforms(PlatformManager->GetSessionPlatforms(),
true /* bIncludeFailed */))
{
PackagesCookedOnAllPlatforms.Add(PackageData);
}
}
});
TArray<FPackageData*> PackagesToAdd;
TArray<FName> Dependencies;
for (FPackageData* CookedPackage : PackagesCookedOnAnyPlatform)
{
Dependencies.Reset();
AssetRegistry->GetDependencies(CookedPackage->GetPackageName(), Dependencies,
UE::AssetRegistry::EDependencyCategory::Package, // Package Dependencies (as opposed to e.g. Management dependencies)
UE::AssetRegistry::EDependencyQuery::EditorOnly); // Only the EditorOnly ones, the cooker already did the used-in-game ones
for (FName DependencyName : Dependencies)
{
FPackageData* DependencyData = PackageDatas->FindPackageDataByPackageName(DependencyName);
if (!DependencyData)
{
continue;
}
bool bExists;
PackagesCookedOnAllPlatforms.Add(DependencyData, &bExists);
if (!bExists)
{
PackagesToAdd.Add(DependencyData);
}
}
}
for (const ITargetPlatform* TargetPlatform : PlatformManager->GetSessionPlatforms())
{
IAssetRegistryReporter& Reporter = *(PlatformManager->GetPlatformData(TargetPlatform)->RegistryReporter);
for (FPackageData* PackageToAdd : PackagesToAdd)
{
if (PackageToAdd->HasCookedPlatform(TargetPlatform, true /* bIncludeFailed */))
{
continue;
}
TOptional<TArray<FAssetData>> AssetDatasFromSave;
UPackage* Package = nullptr; // This Package pointer is unused fortunately, otherwise we would have to load the package or hack the function
ECookResult CookResult = ECookResult::Failed; // Failed is the CookResult used for editor-only packages
FSavePackageResultStruct SavePackageResult = FSavePackageResultStruct(ESavePackageResult::ContainsEditorOnlyData);
TOptional<FAssetPackageData> OverrideAssetPackageData; // Used for generated packages, not applicable since our package is an editor package
TOptional<TArray<FAssetDependency>> OverridePackageDependencies; // Used for generated packages, not applicable since our package is an editor package
// Store the data in the AssetRegistryGenerator
Reporter.UpdateAssetRegistryData(PackageToAdd->GetPackageName(), Package, CookResult, &SavePackageResult,
MoveTemp(AssetDatasFromSave), MoveTemp(OverrideAssetPackageData), MoveTemp(OverridePackageDependencies),
*this);
// Mark it cooked so that CookByTheBookFinishedInternal passes it into the AssetRegistryGenerator as a package to keep.
PackageToAdd->SetPlatformCooked(TargetPlatform, CookResult);
}
}
}
I ran that code in 5.8, there might be some compile errors in 5.6, but I don’t expect any.
Let me know if it doesn’t work or if there is another issue after this one.
[Attachment Removed]