UE 5.6: GameThread Tasks Now Execute During Level Transitions While World is Null - Is This Expected?

Hello, I’ve noticed a significant behavior change in UE 5.6 regarding when GameThread tasks execute during level transitions, and I wanted to confirm if this is expected behavior.

What Changed: In UE 5.5, during FlushRenderingCommands(), the code used FRenderCommandFence::Wait() which defaulted to Wait(false) - meaning it did NOT pump the GameThread task queue. In UE 5.6, this was replaced with FFrameEndSync::Sync(), which uses FRenderThreadFence whose destructor calls Fence.Wait(true) - forcing GameThread task queue pumping.

The Problem: This change causes GameThread tasks to execute at a different time than before. Specifically, during map loading:

  1. UEngine::LoadMap() is called
  2. UEngine::SetCurrentWorld(nullptr) makes the world context null
  3. RHICommandList.FlushMemory() / TrimMemory()is called
  4. This triggers FlushRenderingCommands() → FFrameEndSync::Sync()
  5. The destructor forces Wait(true), pumping the GameThread task queue
  6. Any queued tasks now execute while GetWorld()returns null

Previously, these tasks would execute later after the new map was loaded and the world context was valid.

Question: Is this aggressive pumping behavior during the null-world period intended? If so, what’s the recommended pattern for handling tasks that depend on world context being valid? Our code queues tasks to the GameThread that expect world/game instance to be available, and these now crash during level transitions because they execute while the world is null.

Thanks for any guidance!

Hi Carlos, sorry for the delay on this.

The short answer is no, the additional pumping of game thread tasks during TrimMemory() etc was not intentional in the changes made to FFrameEndSync.

There are two calls to FRenderCommandFence::Wait inside FFrameEndSync::Sync(), both of which are passing the optional bool argument as “true”. One in the destructor of the inline FRenderThreadFence type, and the other in the while loop at the end of the function. I’d be curious to know if removing these optional args so that false is always passed has any other side effects for you. Given the ProcessThreadUntilIdle call made when “FlushMode == EFlushMode::EndFrame”, it might be that we should never be flushing game thread tasks from inside those waits.

We haven’t seen any crashes within our game projects has a result of the changes to FFrameEndSync, which is why we’ve not seen this before. I’m guessing we don’t have game thread tasks which are still in the queue when the world gets unloaded.

Cheers,

Luke