the symptom, in your words

"Defer a synchronous blocking call a few frames so the loading overlay actually paints"

✓ verified on 26.07.15a
lane Building UIposted

▸ SYMPTOM

A button click is supposed to show a loading overlay and then run a heavy job (a ~2-second synchronous, main-thread operation). Instead the UI freezes with no overlay, and the overlay only flashes in after the freeze — i.e. effectively never, since the work is already done by the time it paints.

▸ CAUSE

A razor onclick that flips an overlay flag and then immediately runs a synchronous job blocks the main thread before the next render pass. The overlay flag is set, but the render that would draw the overlay never gets to run until the blocking call returns — by which point there's nothing left to wait for. Show-and-block in the same handler can't work, because painting and blocking compete for the same thread and the block always wins.

▸ FIX

Split it across frames: the click shows the overlay and arms a short frame countdown; a separate OnUpdate decrements the countdown and runs the blocking work only when it reaches zero, so at least one render pass has painted the overlay first.

snippet
int _blockCountdown = -1;

void OnGenerateClicked()
{
    Overlay.Instance?.Show();   // null-safe: still runs headless / from menu
    _blockCountdown = 2;        // let the overlay paint for a couple frames
}

protected override void OnUpdate()
{
    if ( _blockCountdown < 0 ) return;
    if ( _blockCountdown-- > 0 ) return;

    RunHeavySynchronousJob();   // now the overlay is already on screen
    Overlay.Instance?.Hide();
}

Two details that matter:

  • Keep the overlay call null-safe (Overlay.Instance?.Show()) so the blocking work still runs when there is no overlay instance — headless, or from a menu context. This is the same defensive shape as a null-safe screen-fade.
  • The countdown field is not read by markup, so it stays out of BuildHash — the overlay owns its own visible state and the countdown never triggers a spurious re-render.

▸ WHY IT WORKS

The overlay needs a render pass to appear, and a render pass only happens between frames. Deferring the blocking call by a frame or two guarantees the overlay has been painted before the main thread is monopolized, so the user sees the loading state during the freeze instead of after it. Two frames is enough headroom for the show to land; the exact count isn't critical as long as it's at least one.

Verified on engine 26.07.15a: seen in a real project.
s&box moves fast; an undated fix is a liability. Spot a stale detail?
changelog
  • Published

Want to know when new guides or fixes drop? Join the community to help build this out. Report gotchas, flag outdated fixes, or just lurk.

Join the Discord