I know that this topic has been brought up in the past, and I’ve been following the recommendations outlined in the Investigating Blueprint Data Loss Issues guide. However, even after making the suggested engine changes and setting data change breakpoints to observe what’s been going on, I have not been able to figure out why a child blueprint does not derive from its parent blueprint after restarting the editor.
Let me explain my particular case. I have a blueprint actor with a component. That component contains a UProperty that is a TArray of a UStruct (let’s call the array Attacks). That UStruct contains a UProperty of UStruct, and that UStruct contains a UProperty of a TObjectPtr to a class that derives from UDataAsset (let’s call this property ImpactConfig).
I have a base blueprint actor (let’s call it Melee_T0_DA) and a child blueprint actor (let’s call it Melee_T1_DA) that derives from Melee_T0_DA. I make a change to the first entry of T0_DA’s Attack array, changing the ImpactConfig variable from Melee_Impact_DA to FireMelee_Impact_DA. Making this change on the base class means that in Melee_T1_DA, the first entry of its Attacks array should also update its ImpactConfig variable to match its parent’s (FireMelee_ImpactConfig_DA) instead of the default (Melee_ImpactConfig_DA). Yet, when I restart the editor, the ImpactConfig variable of the derived class is the default value instead of its parent’s value.
I have set breakpoints in the PostConstructInit function, and after opening Melee_T1_DA in the Content Browser, I verified that Melee_T1_DA does initially have its ImpactConfig variable set to FireMelee_ImpactConfig_DA after InitProperties. This at least confirms that the base CDO is copying its values to the derived CDO. However, after setting a data change breakpoint on ImpactConfig, it gets triggered twice. The first is when the memory is cleared from an EmptyAndAddValue call from FArrayProperty’s SerializeItem function, and the second is after a Inner->SerializeItem call in that same FArrayProperty::SerializeItem call (screengrab of callstack included).
Here’s exactly where the value gets stomped in FObjectProperty::SerializeItem.
FObjectHandle OriginalHandle = ObjectPtr->GetHandle();
Slot << *ObjectPtr;
I believe this is where the problem begins--in UStruct::SerializeVersionedTaggedProperties
FStructuredArchive::FSlot ValueSlot = PropertyRecord.EnterField(TEXT(“Value”));
…
uint8* DestAddress = Property->ContainerPtrToValuePtr<uint8>(Data, Tag.ArrayIndex);
uint8* DefaultsFromParent = Property->ContainerPtrToValuePtrForDefaults<uint8>(DefaultsStruct, Defaults, Tag.ArrayIndex);
// This property is ok.
Tag.SerializeTaggedProperty(ValueSlot, Property, DestAddress, DefaultsFromParent);
When I cast Data to UObject* prior to the Tag.SerializeTaggedProperty call, it has the correct value (FireMelee_Impact_DA). After the call, it gets replaced with the default value (Melee_Impact_DA). It appears that ValueSlot has the older default value, and that gets used to overwrite what was originally stored in DestAddress/Data.
There are no subsequent calls that tries to fix this. After this, the Melee_T1_DA blueprint is open in the Blueprint Editor and its ImpactConfig variable is set to Melee_Impact_DA instead of FireMelee_Impact_DA.
I did note in the guide how blueprint actors that contain circular dependencies can cause issues, so I performed two checks. I confirmed in the Reference Viewer that the reference direction only goes one way (from Melee_T1_DA to Melee_T0_DA). Furthermore, I set breakpoints on the constructor for when those specific blueprint actors were created, and verified that there was only one call to LoadPackageInternal on the callstack for both actors. I’m certain that the blueprint actors don’t have circular dependencies, but if there are other ways to check, I’m more than willing to try them out.
Do you have any suggestions on what else I can do to help me get to the bottom of this?
[Attachment Removed]