Detaching & Reattaching Niagara systems with decals leaving dangling pointers

Detaching & Reattaching Niagara systems with decals leaving dangling pointers

I have been having issues detaching our Niagara components from actors we’re about to destroy, so that we can keep the niagara systems alive while they do their deactivation, and then through setting SetAutoDestroy=true they will be cleaned up after the system is fully deactivated.

I tried several ways of getting this to work, but it seems niagara is leaving data somewhere in memory dangling when it is re-attached to a new actor. So when the old actor is destroyed, the system is refering to garbage somewhere. We havent been able to pin down exactly where, but it seems to only happen if this system is spawning a decal.

I’ve tried simply Detaching (without re-attaching) and using Rename with GetWorld as the new owner so the system isnt cleaned up when we destroy the original actor. This caused the garbage collection to clean (parts?) of the system up anyway, but leave dangling pointers, causing a crash later. Then I tried creating an actor to attach to instead, and using Rename to make that actor the new owner while they deactivate. But that seems to cause a garbage collection crash as well, although at a different point (shutdown). Something is not being properly cleaned up when we deactivate systems in this way. below is the code I tried last;

void UCRBlueprintLibrary::DetachAndDeactivateCR(UNiagaraComponent* NiagaraComponent)
{
	// Re-attach this component to a 'global' container that keeps these systems alive, while we can destroy the original owner of the system without deactivating it.
	// This way we can show their Deactivation animation without 
	if (NiagaraComponent)
	{
		if (const ACRGameStateBase* GameStateBase = Cast<ACRGameStateBase>(UGameplayStatics::GetGameState(NiagaraComponent)))
		{
			if (ensureMsgf(GameStateBase->DeactivatedNiagaraComponentContainer, TEXT("UCRBlueprintLibrary::DetachAndDeactivateCR called without a valid DeactivatedNiagaraComponentContainer setup")))
			{
				if (USceneComponent* ComponentToAttachTo = GameStateBase->DeactivatedNiagaraComponentContainer->GetDefaultAttachComponent())
				{
					// We MUST detach it from the scalability manager first as otherwise culling might stop this system from being cleaned up.
					// At this point since we're deactivating, we want to clean up the entire system, we dont need to cull anymore
					NiagaraComponent->SetAllowScalability(false);
 
					NiagaraComponent->AttachToComponent(ComponentToAttachTo, FAttachmentTransformRules::KeepWorldTransform);
 
					// This sets the new owner to the component so it wont be destroyed when its previous owner is destroyed
					NiagaraComponent->Rename(nullptr, GameStateBase->DeactivatedNiagaraComponentContainer);
 
					// This must be true otherwise it will not be destroyed after deactivation is complete
					NiagaraComponent->SetAutoDestroy(true);
 
					NiagaraComponent->Deactivate();
				}
			}
		}
	}
}

So the idea behind this util function is that any actor can decide in its OnDestroyed() to detach the niagara system(s) they have and let them handle their own deactivation, while the original actor is destroyed instantly.

The workaround is keeping the actor alive but deactivate everything in it while we manually wait for the system to deactivate use SetLifeSpan(). But I am not a fan of that solution as we need to specify the lifetime in the actor while the deactivation is handled in the system, which is an implicit dependency. I think its nicer for the system to clean itself up when deactivation is done, while the actor can be destroyed immediately.

Potentially relevant; [Content removed]

I attached the crash callstack as a txt since I ran out of characters!

Any help would be great!

Steps to Reproduce

  • Setup a Niagara particle system with an infinite deactivation
  • Spawn an actor, and attach the system to it
  • Destroy the actor, but just before; detach / reattach the niagara system to a new actor and use Rename to make it the new owner(see code in description)
  • Wait 60 seconds orso
  • Stop PIE
  • Observe crash (50% of the time)

Bumping this one!

Hello [mention removed]​,

Thanks for the report. I have tested the scenario in UE 5.5.4, but I was unable to reproduce the crash locally.

In my test, I performed the following steps:

  • Bound the 1 key to spawn an actor and attach a Niagara System to it.
  • Bound the 2 key to execute the code you provided and immediately destroy the spawned actor.

These are the methods I executed in a custom Blueprint Function Library:

void UMyBlueprintFunctionLibrary::DetachAndDeactivateNS(UNiagaraComponent* NiagaraComponent, USceneComponent* TempAttachment)
{
	if (NiagaraComponent && TempAttachment)
	{
		// We MUST detach it from the scalability manager first as otherwise culling might stop this system from being cleaned up.
		// At this point since we're deactivating, we want to clean up the entire system, we dont need to cull anymore
		NiagaraComponent->SetAllowScalability(false);
 
		//NiagaraComponent->DetachFromParent();
 
		bool bNewAttach = NiagaraComponent->AttachToComponent(TempAttachment, FAttachmentTransformRules::KeepWorldTransform);
 
		UE_LOG(LogTemp, Display, TEXT("Attached to new parent (%s)"), bNewAttach? TEXT("True") : TEXT("False"));
 
		// This sets the new owner to the component so it wont be destroyed when its previous owner is destroyed
		NiagaraComponent->Rename(nullptr, TempAttachment);
 
		// This must be true otherwise it will not be destroyed after deactivation is complete
		NiagaraComponent->SetAutoDestroy(true);
 
		NiagaraComponent->Deactivate();
	}
}
 
void UMyBlueprintFunctionLibrary::SpawnActorAndAttachNiagara(UObject* WorldContextObject, TSubclassOf<AActor> ActorClass, FVector SpawnLocation, FRotator SpawnRotation, UNiagaraSystem* NiagaraSystem, AActor*& OutSpawnedActor, UNiagaraComponent*& OutNiagaraComponent)
{
    OutSpawnedActor = nullptr;
    OutNiagaraComponent = nullptr;
 
    if (!ActorClass || !WorldContextObject || !NiagaraSystem)
    {
        return;
    }
 
    UWorld* World = GEngine->GetWorldFromContextObjectChecked(WorldContextObject);
    AActor* SpawnedActor = World->SpawnActor<AActor>(ActorClass, SpawnLocation, SpawnRotation);
 
    if (SpawnedActor)
    {
        UNiagaraComponent* NiagaraComponent = UNiagaraFunctionLibrary::SpawnSystemAttached(
            NiagaraSystem,
            SpawnedActor->GetRootComponent(),  // Attach to the root of the spawned actor
            NAME_None,
            FVector::ZeroVector,
            FRotator::ZeroRotator,
            EAttachLocation::KeepRelativeOffset,
            true,
            true
        );
 
        // Output the spawned actor and Niagara component
        OutSpawnedActor = SpawnedActor;
        OutNiagaraComponent = NiagaraComponent;
    }
}

Here is how the methods are called in the Level Blueprint:

[Image Removed]

In my tests, I trigger #1 and after a few seconds #2 is triggered to detach and reattach the Niagara System and destroy the actor. I waited some time before stopping the PIE session, but no crash was triggered. I tested this several times.

Could you please provide a minimal repro project or additional details to help reproduce it?

Thanks in advance.

Best,

Francisco

The code and setup looks good, Only thing I am unsure about that might be missing is that your system needs to spawn a decal with infinite life. I attached a barebones system that one of my colleagues made that exposes the problem for us. This system showed us that the problem is the decal, if we remove that, it does not occur. I attached it here for you, he ensured only default assets are referenced to it so hopefully adding it as a zip file is enough! In case it does not work (since its a uasset file) i also exported it to T3D, but it seems that is a deprecated feature as i cant re-import it anywhere from that format. In case its not enough info I also have a few screenshots on how its setup

Let me know if this is enough, or if you tried making your own decal spawn and it still did not happen. Thanks for taking the time!

Spawner (placed in the world)

[Image Removed]

Actor (that is spawned) and spawns the NS system attached

[Image Removed]

Then the system itself

[Image Removed]

[Image Removed]

And here you can see its not referencing to anything outside the default unreal assets, just in case you’re trying to get it to work

[Image Removed]

Hello [mention removed]​,

Thanks for the additional details. I was able to import the PS_LeakySystem.uasset but I’m hitting a compilation error due to the missing niagara module script InitializeDecal called in Decal_InfiniteLife. Is this a custom script or part of another plugin/dependency?

56230_PS_LeakySystem_MessageLog: System failed to compile with 3 errors.
56230_PS_LeakySystem_MessageLog: Error: Unknown Function Call! Missing Script or Data Interface Signature. Stack Name: InitializeDecal - Node: Initialize Decal -  Decal_InfiniteLife, Particle Spawn Script Interpolated, 
56230_PS_LeakySystem_MessageLog: Error: Incorrect number of outputs. Can possibly be fixed with a graph refresh. - Node: Initialize Decal -  Decal_InfiniteLife, Particle Spawn Script Interpolated, 
56230_PS_LeakySystem_MessageLog: Error: Error compiling Pin - Node: Output Particle Spawn Pin: Out -  Decal_InfiniteLife, Particle Spawn Script Interpolated,

Please let me know.

Best,

Francisco