Use sync FontManager allocation in LoadAsync to avoid first-draw race

The previous fire-and-forget Task.Run pattern could leave Plugin.FontManager
null when the first UiBuilder.Draw tick fires (ChatLogWindow dereferences
FontManager.FontAwesome / RegularFont / ItalicFont in its draw paths).
Allocate FontManager and call BuildFonts() synchronously, mirroring
ChatTwo Plugin.cs:152. BuildFonts itself is non-blocking — it just
registers IFontHandles with Dalamud's atlas; the actual atlas rebuild
runs on Dalamud's pipeline a few frames later, so the perceived-load
win still holds (LoadAsync no longer waits for atlas build).

BuildFontsAsync in FontManager.cs stays for the Settings-driven manual
rebuild path.
This commit is contained in:
2026-05-08 21:42:57 +02:00
parent 0b25df0ea7
commit 5931f2f301
+8 -11
View File
@@ -471,18 +471,15 @@ public sealed class Plugin : IAsyncDalamudPlugin
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
// Font-build runs fire-and-forget. The atlas rebuild lands a few // Sync allocation + handle registration. BuildFonts() registers
// hundred ms after LoadAsync returns; first frames draw with // IFontHandles with Dalamud's UiBuilder.FontAtlas — registration
// Dalamud's default font until the Hellion-Exo2 / NotoSans handles // itself is non-blocking (handles stored, lambdas queued). Dalamud
// are ready, then ImGui switches to the custom fonts (visible // rebuilds the atlas on its own pipeline a few frames later; first
// "font-pop"). Mirrors ChatTwo's pattern — perceived-load win // frames render with the default font until the rebuild lands and
// comes from "Finished loading" landing earlier, not from a faster // ImGui switches to Hellion-Exo2 / NotoSans (visible "font-pop").
// atlas build. // Mirrors ChatTwo Plugin.cs:152.
_ = Task.Run(async () =>
{
FontManager = new FontManager(); FontManager = new FontManager();
await FontManager.BuildFontsAsync(cancellationToken).ConfigureAwait(false); FontManager.BuildFonts();
}, cancellationToken);
// Theme init stays sync on the LoadAsync continuation — cheap, // Theme init stays sync on the LoadAsync continuation — cheap,
// and Active is read every Draw frame, so the registry must be // and Active is read every Draw frame, so the registry must be