OnComponentDestroyed in editor being called everytime actor is moved

Is it intended behavior for OnComponentDestroyed() to be called (I think) everytime the construction script runs? This does not happen when my component is a C++ Instanced component, but rather when my component was added in the blueprint class itself.

Everytime I drag my actor or make a details panel change in the editor it runs OnComponentDestroyed(). This component in particular is creating some other helper components/actors with it initially and I’m using OnComponentDestroyed() on the main component to remove the extra ones also.

I know components architecturally shouldn’t rely on other components, but it makes sense for what I’m doing with them.

Your actor is probably being recreated “on drag”

drag

in c++ i believe bRunConstructionScriptOnDrag is responsible for this behavior.

2 Likes

@3dRaven This is disabled and not really my issue. Any change to Actor the component is on is causing it to run OnComponentDestroyed(), but only when the actor is a blueprint class and the component added in the CDO of the blueprint and not as a instanced component.

Yes, this is intended, when you compile a BP all components are destroyed and recreated.

Aren’t the other components automatically destroyed when you run the constructor?

@Ares9323 Sorry maybe I’m not explaining this clearly, my apologies if it seems vague.

Forget about the construction script part of it and lets just stay in PostEditPropertyChanged() land. I do understand the compilation needing to rebuild everything, that’s okay. My problem is when any kind of property update happens, the components (in the editor) are being completely rebuilt again. However, this never happens when you add a component as an instanced component, only when the component is declared in a blueprints class CDO.

My confusion is surrounding the different behaviors between the instanced components not having to be rebuilt every time a property is changed vs when added in a blueprint they are.

I found a workaround to my original issue, I’ll mark it as the solution if I can’t find a better alternative.

While the main root component had all the other components marked as UPROPERTY() they also needed to be at least VisibleInstanceOnly, or they would not be properly duplicated over when the component was destroyed. So

Are you sure about this? How are you logging it?
In theory it should also apply to instanced components, every time you edit a variable in an actor placed in the world (in the editor, not at runtime) the constructor is executed.

Are you using default instanced components or did you make custom ones?

Logging by just printing inside of OnComponentDestroyed()

If I have a blueprint class with a component added in it’s CDO - that is the only time I get the reconstruction behavior. If I add the same component (Just instanced now) to the same blueprint actor in the editor, it does not try to repeatedly destroy and recreate all the components on property changes.

Tested with a blank component in 4.27 and it has the same behavior

1 Like

I was distracted and I forgot that the constructor is only called in the CDO.

If you want C++ attached components to behave the same way as in BP you need to create them insidethe OnConstruction() event and you have to call RegisterComponent(), very important otherwise the destructor won’t be called!

Component .cpp

UTestComponent::UTestComponent()
{
	UE_LOG(LogTemp, Warning, TEXT("Component Constructor"));
	AActor *Parent = this->GetOwner();
	// Log parent world location
	if(Parent)
	{
		UE_LOG(LogTemp, Warning, TEXT("Parent World Location: %s"), *Parent->GetActorLocation().ToString());
	}
}

void UTestComponent::OnComponentDestroyed(bool bDestroyingHierarchy)
{
	Super::OnComponentDestroyed(bDestroyingHierarchy);
	UE_LOG(LogTemp, Error, TEXT("Component Destroyed"));

	AActor *Parent = this->GetOwner();
	// Log parent world location
	UE_LOG(LogTemp, Error, TEXT("Parent World Location: %s"), *Parent->GetActorLocation().ToString());
}

Actor .cpp

void AActorWithInstancedComponent::OnConstruction(const FTransform& Transform)
{
	Super::OnConstruction(Transform);
	UE_LOG(LogTemp, Log, TEXT("Actor OnConstruction"));
	class UTestComponent* InstancedComponent = NewObject<UTestComponent>(this,UTestComponent::StaticClass(), TEXT("InstancedComponent"));
	InstancedComponent->RegisterComponent();
}

void AActorWithInstancedComponent::Destroyed()
{
	UE_LOG(LogTemp, Error, TEXT("Actor Destroyed"));
	Super::Destroyed();
}

This is great information! Although I need the opposite behavior, I want the BP to not continuously Destroy then Create. I also would need this to work without having access to the owning actors construction script.

I kinda got around this by having all pointers be UPROPERTY(VisibleAnywhere), and not just UPROPERTY(), the details panels needs to point to the object (I couldn’t tell you why) somehow that makes the pointer values get copied over correctly when destructors are called. It’s quite annoying and it’s actually a large performance hit in the editor because everything is being transacted and making the properties NonTransactional doesn’t copy over the ptr values, but it’s all I got right now.

MainParentActor.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MainParentActor.generated.h"

UCLASS(Blueprintable)
class YOUR_API AMainParentActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AMainParentActor();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(EditAnywhere,BlueprintReadWrite)
	class UChildComponentTest* Child_A;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	class UChildComponentTest* Child_B;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	class USceneComponent* Root;

};

MainParentActor.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "MainParentActor.h"
#include "ChildComponentTest.h"
// Sets default values
AMainParentActor::AMainParentActor()
{ 	
	PrimaryActorTick.bCanEverTick = false;
	bRunConstructionScriptOnDrag = false;
	Root = CreateDefaultSubobject<USceneComponent>("Root");
	SetRootComponent(Root);
	
	Child_A = CreateDefaultSubobject<UChildComponentTest>("Child A");
	Child_A->SetupAttachment(Root);
	
	Child_B = CreateDefaultSubobject<UChildComponentTest>("Child B");
	Child_B->SetupAttachment(Root);
	

}

// Called when the game starts or when spawned
void AMainParentActor::BeginPlay()
{
	Super::BeginPlay();	
}
// Called every frame
void AMainParentActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
}

ChildComponentTest.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Components/StaticMeshComponent.h"
#include "ChildComponentTest.generated.h"

/**
 * 
 */
UCLASS(Blueprintable)
class YOUR_API  UChildComponentTest : public UStaticMeshComponent
{
	GENERATED_BODY()
	
public:
	virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override;

};

ChildComponentTest.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "ChildComponentTest.h"

void UChildComponentTest::OnComponentDestroyed(bool bDestroyingHierarchy) {
	Super::OnComponentDestroyed(bDestroyingHierarchy);

	UE_LOG(LogTemp, Warning, TEXT("Destroying component %s"), *GetName());

}

Only fires destroy once on create. All further dragging in viewport does not trigger destroy on components.

Thank you for the info. In this scenario I can’t use the object initializer because I need to dynamically add the components add runtime to achieve the behavior I want. The same behavior I’m trying to preview in the Editor and that’s where the issues come from.

I was able to get around this by leveraging the transaction system. It seems when objects get destroyed and remade in the editor via this process, the transaction system is used to serialize the previous data back in. I don’t know if that’s because duplicate is being called or not, but having NonTransactional in your property decorators stops this serialization from taking place, and that was part of my original problem. It’s not as performant because of this reason, but it does solve my issue.