Summary:
We are using NDC Gameplay Burst for crowd death effects. The Niagara System managing the crowd moves over time, so its OwningComponent position changes and data gets written to different Grid cells.
Issue:
When the Burst receiver NS has its LoopBehavior set to anything other than Infinite (e.g., Once), the Entry is never released after the NS completes, and no new NS is re-spawned.
We traced the cause in engine code to the following check in FNDCMapEntryBase::BeginFrame:
bInUse = Data.IsUnique() == false;
The writer NS’s (crowd NS) NDC Write DI caches DataChannelData (a SharedPtr), and even with bUpdateDestinationDataEveryTick = true, the reference is never released. As a result, IsUnique() never returns true, and the Entry is never freed — even after the Burst NS has completed.
Setting LoopBehavior to Infinite makes it work, but since the crowd NS moves across cell boundaries, new Entries and NSs accumulate indefinitely without being released, causing a memory leak.
Questions:
Is using a non-Infinite loop on the receiver NS a supported use case for NDC Gameplay Burst?
Is there a recommended pattern for releasing old cell Entries when writing from a moving OwnerComponent?
Is it safe to add logic to FNDCMapEntry_GameplayBurst::BeginFrame that forces Entry release when all SpawnedComponents are Complete? Are there any side effects we should be aware of?
Interim Workaround:
We have applied the following modification to NiagaraDataChannel_GameplayBurst.cpp:
bool FNDCMapEntry_GameplayBurst::BeginFrame(float DeltaTime, FNiagaraWorldManager* OwningWorld, const FNDCMapKey& Key)
{
if (SpawnedComponents.Num() > 0)
{
bool bAllComplete = true;
for (const TObjectPtr<UNiagaraComponent>& Comp : SpawnedComponents)
{
if (Comp && !Comp->IsComplete())
{
bAllComplete = false;
break;
}
}
if (bAllComplete)
{
return false;
}
}
bool bAttachmentCompValid = AttachComponent.IsExplicitlyNull() || AttachComponent.IsValid();
return Super::BeginFrame(DeltaTime, OwningWorld, Key) && bAttachmentCompValid;
}
[Attachment Removed]