Description

Duplicating many Actors at once in a Level Instance while in Level Instance Edit will result in a jumbled mess of Actors on the Persistent Level. The licensee has encountered this in Unreal 5.4 and it persists in 5.5. The licensee has analyzed it and reported that this happens because the LevelInstance compilation regenerates the PackedLevelActors blueprint, which then triggers the relevant Actor update in the scene. Since it is a blueprint Actor, the default behavior of the UE is to reapply the ComponentInstanceData of the old Actor to the new Actor, and each Component in the new Actor has to search for the matching ComponentInstanceData in this process, and the matching logic is just a simple match by order and type. This matching logic is a simple order and type matching. Since there are many Components in PackedLevelActor and most of them are of the same type, it is easy to match wrong ComponentInstanceData, and using the wrong ComponentInstanceData will lead to the confusion of Instance data.

The licensee has also offered their solution to the issue in the form of a Git patch. I'll attach it to the Callstack field because it exceeds the character limit of this field.

Steps to Reproduce

The issue derives from duplicating many Actors at once in a Level Instance, so a Level Instance with many Actors is required.
I suggest using the repro project for a quicker reproduction.
Without the repro project:
Create a Level Instance with many actors inside and open it in a Persistent Level.
With repro project:
Open the project (LipDebug)
Open the DebugMap level
Select either side of the Level Instance (ex. BPP_LIP_HalfStationTop)
Press CTRL+E to enter Level Instance Edit mode
Keep pressing CTRL and select many (10-20) actors
Press ALT and Left-Mouse click and drag to make a duplicate of the group of selected actors
Press Save to save the Level Instance
After saving we are automatically returned to the Persistent Level (DebugMap)
Expected Result: the saved/updated Level Instance would display correctly in the Persistent Level
Actual Result: the saved/updated Level Instance displays as a jumbled mess of actors in the wrong position.

Reopening the Instance Level by pressing CTRL+E will show the actors in the correct position.

Callstack

(This is a git patch included by client - but pasted here due to char limits)

diff --git a/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetReinstanceUtilities.cpp b/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetReinstanceUtilities.cpp
index a1eb1145fb10..263da64f4540 100644
— a/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetReinstanceUtilities.cpp
+++ b/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetReinstanceUtilities.cpp
@@ -46,6 +46,7 @@
#include "UObject/PropertyOptional.h"
#include "UObject/PropertyBagRepository.h"
#include "ProfilingDebugging/LoadTimeTracker.h"
+#include "PackedLevelActor/PackedLevelActor.h"

DECLARE_CYCLE_STAT(TEXT("Replace Instances"), EKismetReinstancerStats_ReplaceInstancesOfClass, STATGROUP_KismetReinstancer );
DECLARE_CYCLE_STAT(TEXT("Find Referencers"), EKismetReinstancerStats_FindReferencers, STATGROUP_KismetReinstancer );
@@ -63,6 +64,13 @@ static FAutoConsoleVariableRef CVarUseLegacyAnimInstanceReinstancingBehavior(
TEXT("Use the legacy re-instancing behavior for anim instances where the instance is destroyed and re-created.")
);

+bool GUseOldPackedLevelActorCachedData = false;
+static FAutoConsoleVariableRef CVarUseOldPackedLevelActorCachedData(
+ TEXT("bp.UseOldPackedLevelActorCachedData"),
+ GUseOldPackedLevelActorCachedData,
+ TEXT("")
+);
+
namespace UE::ReinstanceUtils
{
const EObjectFlags FlagMask = RF_Public | RF_ArchetypeObject | RF_Transactional | RF_Transient | RF_TextExportTransient | RF_InheritableComponentTemplate | RF_Standalone; //TODO: what about RF_RootSet?
@@ -1437,7 +1445,10 @@ struct FActorReplacementHelper
, TargetWorldTransform(FTransform::Identity)
, AttachmentData( MoveTemp(InAttachmentData) )
{

  • CachedActorData = StaticCastSharedPtr<FActorTransactionAnnotation>(OldActor->FindOrCreateTransactionAnnotation());
    + if (GUseOldPackedLevelActorCachedData || !InNewActor->IsA(APackedLevelActor::StaticClass()))
    + { + CachedActorData = StaticCastSharedPtr<FActorTransactionAnnotation>(OldActor->FindOrCreateTransactionAnnotation()); + }

    TArray<AActor*> AttachedActors;
    OldActor->GetAttachedActors(AttachedActors);

@@ -1599,6 +1610,7 @@ void FActorReplacementHelper::Finalize(const TMap<UObject*, UObject*>& OldToNewI
}

NewActor->Modify();
+
if (GEditor)
{
ULayersSubsystem* Layers = GEditor->GetEditorSubsystem<ULayersSubsystem>();
diff --git a/Engine/Source/Runtime/Engine/Private/PackedLevelActor/PackedLevelActor.cpp b/Engine/Source/Runtime/Engine/Private/PackedLevelActor/PackedLevelActor.cpp
index 465493c990d9..c80369294dc8 100644
— a/Engine/Source/Runtime/Engine/Private/PackedLevelActor/PackedLevelActor.cpp
+++ b/Engine/Source/Runtime/Engine/Private/PackedLevelActor/PackedLevelActor.cpp
@@ -84,6 +84,10 @@ void APackedLevelActor::RerunConstructionScripts()

if(bShouldRerunConstructionScript)
{
+ if (PackedVersion != GetClass()>GetDefaultObject<APackedLevelActor>()>PackedVersion)
+

{ + DestroyConstructedComponents(); + }

Super::RerunConstructionScripts();
PackedVersion = GetClass()>GetDefaultObject<APackedLevelActor>()>PackedVersion;
}

Have Comments or More Details?

There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-247699 in the post.

0
Login to Vote

Unresolved
CreatedFeb 12, 2025
UpdatedFeb 26, 2025
View Jira Issue