All Instanced components outside of c++ constructor of an Actor are reset to null when compile a blueprint that inherit the c++ Actor

Hello,

I am trying to build a c++ actor that contains an array of splineComponents so I can add on fly new spline when needed. And I have a BP actor that inherit my c++ Actor. Everything works fine until I needed to compile my blueprint. Indeed each time I hit compile on my blueprint all the spline component added in the array disappeared and their pointers in the array are set to null. So I loose all my edition in the Viewport.

Note that :
1- the issue has nothing to do with the save serialization as when I save and load my project everything loads fine. The issue occurs when i hit compile in BP.

2- if I add some splines in my array in the constructor (c++), those splines and their edition stay when i compile my BP.

3- when I live compile my c++ code all my added splines stay.

What do I need to do so that a component created outside of my constructor is restored after hitting my blueprint compile button?

Hope someone can give an answer or some clues…

Can we see the code where you (a) declare the array, (b) where you add the components to the array, and (c) where you see them set to null?

post the .cpp and .h file with the image of the bp event graph and constructor

Careful about hot reload and live coding always. Do close your editor, before you proceed and verify issue persists.

Here is the function in the .cpp

void ABuildingGenerator::AddNewSpline()
{
const FString nameGenerator = “Generator”;
auto newSpline = NewObject(this, FName(FString::Printf(TEXT(“%s_%d”), *nameGenerator,
SplineComponents.Num())),
RF_Public | RF_Transactional | RF_DefaultSubObject);
newSpline->CreationMethod = EComponentCreationMethod::Instance;
//register des components
newSpline->RegisterComponent();
newSpline->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
SplineComponents.Add(newSpline);

}

Here is the .h

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Edtion)
	TArray<USplineComponent*> SplineComponents;
UFUNCTION(BlueprintCallable, meta = (DisplayName = "AddNewSpline"), Category = "Generator")
	virtual void ABuildingGenerator::AddNewSpline();

live coding is not the issue here as each time i compile via live coding all my edition remain

the issue comes the compile button in the blueprint Actor that inherit my c++ actor .

i add component in the array by calling a fonction ! so it 's not in the constructor.

but for example if i add some spline in my array in the c++ constructor , those splines remain and their edition also ! but the whole purpose of my Actor is to add splines on fly ! Not adding a certain number of splines in the constructor at start! what if after i need to add more spline components? so i need to add components on demand and need to keep the editon of those components even after a Blueprint compile.

do you have some failed casts? investigate if you are not sure

i am not sure to get what you mean here. But yes i always test if the spline i 'am getting from my array is not null before doing any process on it! Then i realized that after compiling from my blueprint editor all my added splines are deleted excepted for those instanced in the constructor. In a normal use case i should never instance some splines in the constructor. i did it because i wanted to see if there is a different behavior if a component is instanced in a constructor or outside as creating instance from constructor is done using CreateDefaultSubobject and the ones created outside use NewObject<>.

From where are you calling AddNewSpline ?

i added a blueprint a function that calls my addNewSpline . And tag that function to be call in editor ! so in the details panel in my viewport there is a button that appears ! that button allows you to your function.

Hmmm, that sounds a little suspicious to me. When you open a blueprint class in the editor it’s like you’re editting the CDO (I think), but I don’t full understand what the Compile button actually does - or how it would interact with a function called (that likely gets dispatched to the inherited CDO from the C++ base class) on the derived blueprint class CDO.

Have you tried setting a breakpoint on AddNewSpline - running the editor, clicking your add new spline button, and then stepping in and through the AddNewSpline function to see if the code path is doing what you expect.

You could also find the code behind the Compile button and set a breakpoint there and step through to see where your elements are getting nulled out and why.

Read this at step 8 and 9 you will get the answer to your question.
Blueprint Compilation - Gamedev Guide (ikrima.dev)

Surely the CDO is recreated when hitting compile on my blueprint. And then a new instance of my actor is created and then a copy of values from the old instance is done on the new one before the destruction of the old instance. The real question here is why all components created outside of my constructor are not restored? there are not even there anymore. there position in the array is set to none (null)

I don’t know if there is a flag i could add to components added outside the constructor so that there are restored back in there state before the BP compile.

1 Like

Did you try stepping through the compile function? If that link is correct then its in FBlueprintCompileReinstancer

You either need to update the blueprint data itself (the UBlueprint, not the generated class or the CDO), or store enough data into default properties to be able to reconstruct everything in the construction script.

For the first one, look into FKismetEditorUtilities::AddComponentsToBlueprint.
The Blueprint can be reached via Cast<UBlueprint>(Class->ClassGeneratedBy).

I finally dived into the whole BP compile process and find out why my pointers are set to null.
As of today when a BP compile is triggered, for each instances of my actor in the editor a new instance is created based on the CDO. After that only pointers that are present in CDO are updated using the old instance (it use the name of the object). All others pointers init outside of the construtor are set to null as they are not present in the CDO.

The copy process is done in unrealEngine.cpp within the function
void UEngine::CopyPropertiesForUnrelatedObjects(UObject* OldObject, UObject* NewObject, FCopyPropertiesForUnrelatedObjectsParams Params).

For those interested pay attention here
if (!ReferenceReplacementMap.Contains(ObjectInOuter)
&& (!Params.bDontClearReferenceIfNewerClassExists || !ObjectInOuter->GetClass()->HasAnyClassFlags(CLASS_NewerVersionExists)))
{
ReferenceReplacementMap.Add(ObjectInOuter, nullptr);
}

here is exactly where all references that are not initially present in the CDO are set to null. And those present in the CDO will be refreshed based on the old instance of the Actor.

I am not sure that i am getting what you are suggesting. i already find out why the behavior is like that. but maybe what you are suggesting can help me elegantly bypass that behavior. Could you please give more explanation?

Yeah I suspected it was something like that.