"Razor panel mounted before a sync call never paints — budget frames, not ticks"
▸ SYMPTOM
A loading/progress UI (ScreenPanel + Razor tree) mounted immediately before a synchronous multi-second operation (terrain build, asset load, etc.) never appears. The user sees a frozen editor or blank screen and may interpret it as a crash. The mount code deliberately consumed one Update tick before starting the heavy call, but the loading screen still didn't paint.
▸ CAUSE
A fresh ScreenPanel + Razor tree needs mount → style/layout → paint spread across multiple engine frames. One tick of headroom (~16 ms) is not enough — the Razor pipeline's layout and paint stages each need their own frame. No frames render inside the synchronous block, so the pipeline never completes.
▸ FIX
Gate the synchronous call on a frame counter, not a single tick delay. Each OnUpdate call is one real rendered frame (no frames render inside a sync block, so counting calls counts paints). Start the heavy call only after ~6 frames (~0.1 s — imperceptible next to a multi-second operation it fronts).
private int _frameCounter;
protected override void OnUpdate()
{
if (_armed)
{
_frameCounter++;
if (_frameCounter >= 6)
{
_armed = false;
StartHeavyOperation();
}
}
}▸ WHY IT WORKS
The engine's Razor rendering pipeline is not instant — it requires multiple frame passes to complete the full mount → style resolve → layout measure → paint cycle. By holding several idle frames between the UI mount and the blocking call, the pipeline completes its work and the loading screen actually renders before the sync block freezes the frame loop. The ~6 frame budget is empirical: enough for the pipeline to finish, short enough to be imperceptible next to any operation worth fronting with a loading screen.