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!