After updating from Unreal 5.4 to 5.5.4 we are encountering a blueprint reinstancing / object duplication issue.
This may possibly (but not necessarily) be related to this other issue discussed on Reddit (https://www.reddit.com/r/unrealengine/comments/1hof3mp/comment/m497n2o/) and may be something that surfaced after a specific commit (https://github.com/EpicGames/UnrealEngine/commit/c43bd3c5a09f4dc6e2e859dc34868ef9619a7e9c), although the exact point in the code where the failure happens does not seem to have been touched by that change.
Steps to reproduce
Our setup involves an automated test that compiles all blueprints in the project. Among them we have:
- A widget blueprint inheriting directly from UScrollBox.
- A widget blueprint inheriting from UCommonUserWidget which uses (1) and has content inside the ScrollBox.
The issue happens if we use the automated test to perform the following steps in order:
A. Compile blueprint 1
B. Compile blueprint 2
C. Compile blueprint 1
D. Compile blueprint 2
The core issue happens in step (C), when the ScrollBox instance of (1) that is contained in (2) is duplicated for reinstancing. The Slots array of the original instance of (1) contains only one item, the slot object. A duplicate of the slot object is created, referencing the new instance of (1) as its Outer. However, the Slots array of the new instance of (1) still retains references to the original slot object.
[Image Removed]Then, in step (D), recompiling creates a duplicate of the WidgetTree. The algorithm now fails to create a complete new copy, because the Outer of the slot object in the Slots array of the ScollBox is incorrect, thus the slot and all its children in the new WidgetTree are the same as the old WidgetTree.
During the validation of the duplicate WidgetTree, we hit this assertion in UWidgetBlueprint::ValidateGeneratedClass:
Ensure condition failed: Widget->GetOuter() == WidgetTree
Potential point of failure
The point of failure in step (C) seems to be in FBlueprintCompileReinstancer::CopyPropertiesForUnrelatedObjects. While copying the properties of the old “REINST_” object of the ScrollBox to the new instance, the process fails to use the OldToNewInstanceMap, which correctly contains the mapping from the old slot object to the newly created slot object.
I tracked the behavior further down in UEngine::CopyPropertiesForUnrelatedObjects.
Because Params.bReplaceInternalReferenceUponRead is false, Params.OptionalReplacementMappings is ignored when creating the serialization Reader. Thus the old objects are populated into the NewObject during deserialization.
Later, FFindInstancedReferenceSubobjectHelper::Duplicate does nothing to correct the situation (and I’m not sure that this is the purpose of the function either), because the old and new slot objects (array items) are present inside the Params.OptionalReplacementMappings that is passed to the function, and are considered to have been already handled.
Tenative fix just for demonstration
A tentative fix that corrects the situation for us is to set Params.bReplaceInternalReferenceUponRead unconditionally inside FBlueprintCompileReinstancer::CopyPropertiesForUnrelatedObjects.
[Image Removed]However, I’m not familiar enough with this code to understand the side effects or implications of this change, and there may be a more correct fix. We look forward to receiving advice on this issue!