We discovered a bug in the FComponentInstanceDataCache::ApplyToActor() function which is causing TRASHed components to become re-attached to the root component of the parent actor during BP recompilation.
This only affects components which are getting added to the FComponentInstanceDataCache::InstanceComponentTransformToRootMap , i.e those that have the CreationMethod field set to EComponentCreationMethod::Instance and are originally attached to another component who has IsCreatedByConstructionScript() condition return true.
If a component referenced by the InstanceComponentTransformToRootMap becomes trashed in BP recompilation by DestroyConstructedComponents(), then this map will reference a trashed component. Then the following code will attach this invalid component to the root component of the actor.
// Once we're done attaching, if we have any unattached instance components move them to the root
for (const auto& InstanceTransformPair : InstanceComponentTransformToRootMap)
{
check(Actor->GetRootComponent());
USceneComponent* SceneComponent = InstanceTransformPair.Key;
if (SceneComponent && !IsValid(SceneComponent)) <--- The !IsValid() check is wrong
{
SceneComponent->AttachToComponent(Actor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
SceneComponent->SetRelativeTransform(InstanceTransformPair.Value);
}
}
The bug appears to be introduced in CL 17279601 in a refactoring. The original intention was to run this code only in the event the attach parent is not valid. The following change addresses the issue.
if (IsValid(SceneComponent) && !IsValid(SceneComponent->GetAttachParent()))
{
//do the attach and set the root relative transform
}
Thank you for the detailed description of the issue.
I have currently been unable to confirm the issue on my end. I have tried the following steps to reproduce the issue:
Create a C++ Actor
Add a SceneComponent in the Constructor of the C++ actor via CreateDefaultSubobject
Create a Blueprint Actor and set the previously created C++ actor as its parent
Add a SceneComponent to the Blueprint in the Editor
Reparent the added SceneComponent as a child of the SceneComponent created in the C++ parent class
Call the DestroyComponent function of the SceneComponent created in the C++ parent class in the Construction Script method of the Blueprint Actor
Compile the Blueprint Actor
Do these steps sound reasonable, is there anything I may have missed or that should be added. Additionally, if you could provide steps to reproduce and/or a minimal project that reliably reproduces the issue it would help me to verify the results and determine next steps.
Create a C++ Actor class, and in the constructor create a component via CreateDefaultSubobject<> and assign it to the RootComponent member.
Create a BP actor class that derives off of that C++ actor class.
In the BP class via the BP editor create a Child component under the RootComponent.
In the BP class construction script call a native function which spawns a Grandchild component via NewObject<> with the Child component passed in as the Outer. Additionally that native function should setup the Grandchild component as follows:
Set the Grandchild’s CreationMethod to EComponentCreationMethod::Instance.
Attach the Grandchild to the Child component by calling AttachToComponent.
Recompiling the BP class should then result in the Grandchild getting destroyed (trashed) inside AActor::DestroyConstructedComponents(), and subsequently reattached to the RootComponent inside FComponentInstanceDataCache::ApplyToActor()
Thank you for providing the setup steps. I have still been unable to reproduce the described issue. I have attached a project that implements the described setup, are you able to please verify that this is setup correctly and advise on any missing steps or required changes.
Your test setup is working fine on my end when I drop it into our current project. To detect the issue you can put a code breakpoint inside the if (SceneComponent && !IsValid(SceneComponent)) block of the FComponentInstanceDataCache::ApplyToActor() function and you will see a trashed component getting added to the root component’s USceneComponent::AttachChildren array upon recompiling the BP.
After doing a BP recompile a few times you will see the USceneComponent::AttachChildren array growing, and be full of null entries and/or a few trashed components.
Thanks for confirming that this setup works on your side. Unfortunately we have still not been able to replicate the issue (perhaps because it may depend on some settings of your project). Would you be able to provide a minimal repro project which exhibits this issue? This would be needed to be able for us to file a bug report with Epic and will expedite a fix.