Content (An Instanced property) is missing from cook. Source package referenced an object in target package but the object was stripped out of the target package when saved.

We are upgrading our project from UE5.3 to UE5.5.

There is a setup that’s leading to an error of the form:

LogCook: Error: Content is missing from cook. Source package referenced an object in target package but the object was stripped out of the target package when saved. Source package: /Game/Blueprints/Characters/CHA_NPC_01 Target package: /Game/Blueprints/Characters/CHA_NPC_Base Referenced object: /Game/Blueprints/Characters/CHA_NPC_Base.Default__CHA_NPC_Base_C:CustomSettings_Interest

In the above, CustomSettings_Interest property is Instanced as shown in the image attached (which extends UCustomSettings_Behavior from the image below).

Here’s an image that should show the setup we have that’s leading to this issue in UE5.5

[Image Removed]

We’ve tried backporting code updates mentioned in [Content removed] however, it hasn’t changed the situation for us.

Any guidance here would be of great help. Thanks!

The known fixes we have were specifically about GEN_VARIABLEs in blueprints, but other errors are possible. We don’t know of any currently after taking the fixes made in 5.6, so your problem is new territory and we’ll need to debug.

The first thing to try is content changes that might workaround the problem, and will narrow down the source of the issue if they do workaround it:

  • Resave the source and target package in editor; does that prevent the cooking error?
  • Is the CustomSettings_Interest variable in the base blueprint marked “Editable when inherited”? If so, change it to false, does that prevent the issue?

To sum up my understanding:

The parent class’s CDO’s constructor creates a subobject in the CDO (In FBehaviorPackage::FBehaviorPackage), by calling CreateDefaultSubObject(NameOfClass).

During a recompile the old object is sometimes kept around, and a new object is created again, and created beside the additional object and CreateDefaultSubObject gives it a unique name by incrementing the Number of its FName.

Some subclass CDOs have pointers to one of these subobjects, and they record that pointer when they save, but then that subobject is either not created or deleted or not exported when the parent class is saved.

Is that correct?

If so, it might be caused by incorrect boilerplate in your calls to CreateDefaultSubObject; I would expect CreateDefaultSubObject to handle that case correctly.

If that’s correct, I’ll send this over to the blueprint team for their expert opinion.

The contract between SavePackage and the Blueprint compiler is that all subobjects in a base class that are RF_DefaultSubobject | RF_ArchetypeObject will be imported by child classes, and the blueprint compiler is supposed to *destroy them on load if they are no longer required by the base class. (Footnote: They are not actually destroyed until GC happens, they are renamed into the transient package, but the principle remains true that they are no longer findable as subobjects). Usually this destruction is accomplished during the reinstancing phase of blueprint compilation, by calling FLinkerLoad::PRIVATE_PatchNewObjectIntoExport.

I believe the blueprint reinstancing code is not finding it because the name has changed; I think assigning the Settings object away and then back is not allowed to change the name of the default subobject.

But I don’t understand why during the cook commandlet the newly created DefautlSubObject, with the name “SettingsObjectTypeOne”, rather than “SettingsObjectTypeOne_0”, which is unreferenced but is an RF_ArchetypeObject, is able to be garbage collected; archetype objects are not supposed to be garbage collectable. I would have expected the error in this case to be a spurious unused copy present in “SettingsObjectTypeOne” rather than a removal of that copy during GC and a failure of the import.

But in either error case, the behavior of CreateDefaultSubObject and/or the behavior of the Property Editor when switching the Settings property, is not handling the naming correctly and is causing the error.

Sending to the blueprint team queue for further analysis.

Hey Daniyal,

Could you try integrating CL 38789294 from our P4 depot to see if it fixes the issue? (or on Github). This change is also in 5.6.

The issue is what Matt described: the new instance drop-down isn’t recycling the inherited name of the subobject. Since the name is different, it’s assumed that this subobject instance is actually _different_ than what’s in the CDO. Unfortunately, it’s not at all obvious since we hide the subobject names (except via tooltip). In general, the engine is fairly sensitive to an object’s name and outer chain.

We’ve had internal discussions about whether or not users would ever want to intentionally change the subobject name, but there hasn’t been any movement there. Nonetheless, I think that change should address the problem.

Let us know how it goes.

Thanks,

Dave

As to the suggestions: I’ve tried option 1, did not get much success with it. Option 2, haven’t investigated that one as we may have an uneasy solution for this right now

One of our programmers detected some potentially unexpected references existing in the serialized data. Essentially, seemed like copies. He added this fixup globally to `OnPostEngineInit`:

`// Performs cleanup to ensure that character BP’s only have one default AISettings_Behavior object and that it has no name suffix in order to avoid deserialization issues
FCoreDelegates::OnPostEngineInit.AddLambda(

{
FCoreUObjectDelegates::OnObjectPostCDOCompiled.AddLambda(
(UObject* CDO, const FObjectPostCDOCompiledContext&)
{
if (ACustomCharacter* CharacterCDO = Cast(CDO))
{
TWeakObjectPtr CharacterCDOWeak(CharacterCDO);
auto RestoreOriginalBehavior = =
{
if (!CharacterCDOWeak.IsValid())
{
return;
}

UCustomSettings_Behavior* ReferencedBehavior = CharacterCDOWeak->BehaviorPackage.GetDefaultBehaviorSettings().DefaultBehavior;
UCustomSettings_Behavior* OriginalBehavior = nullptr;
ForEachObjectWithOuter(CharacterCDO,
[&](UObject* Subobject)
{
if (UCustomSettings_Behavior* Behavior = Cast<UCustomSettings_Behavior>(Subobject))
{
if (Behavior != ReferencedBehavior && Behavior->GetFName().GetNumber() == 0)
{
OriginalBehavior = Behavior;
}
}
}
);

if (OriginalBehavior != nullptr && ReferencedBehavior != nullptr)
{
UEngine::CopyPropertiesForUnrelatedObjects(ReferencedBehavior, OriginalBehavior);
CharacterCDO->BehaviorPackage.SetDefaultBehavior(OriginalBehavior);
ReferencedBehavior->ClearFlags(RF_Public);
ReferencedBehavior->SetFlags(RF_Transient);
}
else if (ReferencedBehavior != nullptr && ReferencedBehavior->GetFName().GetNumber() != 0)
{
ReferencedBehavior->Rename(*ReferencedBehavior->GetClass()->GetName());
}
};

if (!IsRunningCommandlet())
{
GEditor->GetTimerManager()->SetTimerForNextTick(FTimerDelegate::CreateLambda(
RestoreOriginalBehavior
));
}
else
{
RestoreOriginalBehavior();
}
}
}
);
}
);`After resaving all our Character BPs with this code (we’re not submitting this to our source control), our cooks have been succeeding so far. Now, admittedly, there is a random factor to this problem as perhaps the GC may not be aggressive enough during cooks to surface this issue, but we’ve had numerous cooks since this resave and they’ve succeeded so far

From our programmer that got the furthest on this - assuming the below definitions:

`UCLASS(Abstract, EditInlineNew, BlueprintType, DefaultToInstanced)
class USettingsObjectBase
{
}

UCLASS()
class USettingsObjectTypeOne : public USettingsObjectBase
{
}

UCLASS()
class USettingsObjectTypeTwo : public USettingsObjectBase
{
}

USTRUCT()
struct FSettingsWrapper
{
UPROPERTY(EditAnywhere, NoClear, Instanced, Meta = (ShowOnlyInnerProperties))
TObjectPtr Settings;

FSettingsWrapper(UObject& Outer)
{
Settings = Outer.CreateDefaultSubobject(USettingsObjectTypeOne::StaticClass()->GetFName());
}
}

UCLASS()
class AProblemActorBase
{
UPROPERTY()
FSettingsWrapper Wrapper;

AProblemActorBase(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, Wrapper(*this)
{
}
}`Steps to put a BP asset into a bad state:

  1. Create a BP asset that inherits from AProblemActorBase
  2. In the class defaults pane, switch the Settings property to an instance of USettingsObjectTypeTwo
  3. In the class defaults pane, switch the Settings property back to an instance of USettingsObjectTypeOne
  4. Save the BP asset

After running these steps, dumping the BP asset’s export entries using the PkgInfo commandlet shows that the Settings subobject’s name is incremented (ie “SettingsObjectTypeOne_0”).

At cook time, when the BP asset is loaded and its generated class’s CDO is populated, it will have two Settings subobjects, one from the FSettingsWrapper constructor and one created for the export entry by the UObject linker.

The initial subobject from the constructor is unreferenced post-serialization and eventually becomes GC’ed, however we did see that initial subobject is still registered as a cooker EDL dependency. Working backwards, we traced it to FPackageHarvester::ProcessImport() calling UE::SavePackageUtilities::GetCDOSubobjects() before the first garbage collection pass during the commandlet’s run. We believe that this is the root cause of the issue but were unsure of a safe way to fix it, so we went with a workaround to avoid the situation entirely.