We’re preparing to ship our game on Unreal Engine 5.5, and while stability has been excellent overall — with multiple 2–4 hour playtests running smoothly — we kept encountering a transient crash during our warm-up phase.
Today I finally caught it in a shipping build with symbols and traced it back to:
cpp
CopyEdit
UWorld::SendAllEndOfFrameUpdates()
Specifically, the issue is caused by ComponentsThatNeedPreEndOfFrameSync
being modified while it’s being iterated. This is likely due to render proxy-related updates like cloth simulation triggering changes mid-loop.
Looking into the UE 5.6 branch, it seems Epic already addressed this. However, I doubt this fix will be backported to 5.5 — so here it is for anyone who needs it:
The Original (UE 5.5) – Problematic
cpp
CopyEdit
// Wait for tasks that are generating data for the render proxies, but are not awaited in any TickFunctions
// E.g., see cloth USkeletalMeshComponent::UpdateClothStateAndSimulate
for (UActorComponent* Component : ComponentsThatNeedPreEndOfFrameSync)
{
if (Component)
{
check(!IsValid(Component) || Component->GetMarkedForPreEndOfFrameSync());
if (IsValid(Component))
{
Component->OnPreEndOfFrameSync();
}
FMarkComponentEndOfFrameUpdateState::ClearMarkedForPreEndOfFrameSync(Component);
}
}
ComponentsThatNeedPreEndOfFrameSync.Reset();
Fixed Version (From UE 5.6)
cpp
CopyEdit
// Wait for tasks that are generating data for the render proxies, but are not awaited in any TickFunctions
// E.g., see cloth USkeletalMeshComponent::UpdateClothStateAndSimulate
static TArray<TObjectPtr<UActorComponent>> LocalComponentsThatNeedPreEndOfFrameSync;
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_PostTickComponentUpdate_PreEndOfFrameSync_Gather);
check(IsInGameThread() && !LocalComponentsThatNeedPreEndOfFrameSync.Num());
LocalComponentsThatNeedPreEndOfFrameSync.Append(ComponentsThatNeedPreEndOfFrameSync.Array());
}
for (UActorComponent* Component : LocalComponentsThatNeedPreEndOfFrameSync)
{
if (Component)
{
check(!IsValid(Component) || Component->GetMarkedForPreEndOfFrameSync());
if (IsValid(Component))
{
Component->OnPreEndOfFrameSync();
}
FMarkComponentEndOfFrameUpdateState::ClearMarkedForPreEndOfFrameSync(Component);
}
}
LocalComponentsThatNeedPreEndOfFrameSync.Reset();
ComponentsThatNeedPreEndOfFrameSync.Reset();
This change avoids modifying the TSet
while iterating over it by snapshotting to a local array first — a clean and thread-safe fix.
If you’re shipping on UE 5.5 and running into weird, hard-to-reproduce crashes in SendAllEndOfFrameUpdates()
, this patch will likely solve it.
Hope this helps someone avoid a few hours of head-scratching.
– Teella
TeeKru Games | A.L.A.R.M.