diff --git a/HellionChat/Plugin.cs b/HellionChat/Plugin.cs index a0cf891..06f1875 100755 --- a/HellionChat/Plugin.cs +++ b/HellionChat/Plugin.cs @@ -166,6 +166,15 @@ public sealed class Plugin : IAsyncDalamudPlugin // because the tick reads it from a different thread than the writer. private volatile bool _isDisposing; + // v1.9.0 B5: last full Draw() wall-time in ms, written once per frame at + // the end of the UiBuilder.Draw handler. Covers the GlobalStyleScope push + // and the font push (§7.5 First-Frame-HITCH must include atlas/style + // prologue cost), not just WindowSystem.Draw — measuring the inner call + // alone would drop the prologue and make the figure non-comparable to the + // v1.5.6 baseline. Only accumulated here; the disk write happens in + // PerformanceBaselineStep so the hot path stays allocation-free. + internal double LastDrawMs; + internal int DeferredSaveFrames = -1; // Cancels the v1.4.8 FTS5 bulk-insert worker on plugin teardown. The @@ -1012,48 +1021,65 @@ public sealed class Plugin : IAsyncDalamudPlugin private void Draw() { - // v1.4.8 B2: pick up external edits of the active custom theme JSON - // without forcing the user to re-click the picker. The disk-stat is - // 1Hz-throttled inside RefreshActiveIfStale, so this is essentially - // free on built-in themes and ~1 stat/second on custom themes. - ThemeRegistry.RefreshActiveIfStale(); - - using IDisposable _style = Ui.StyleEngine.GlobalStyleScope.Push( - ThemeRegistry.Active, - ThemeRegistry, - Config.WindowOpacity - ); - - if (Config.HideInLoadingScreens && Condition[ConditionFlag.BetweenAreas]) - { - TypingIpc.Update(); + // v1.9.0 B5: time the whole handler (style + font prologue included). + // Bail before measuring once teardown has begun — a late Draw tick + // must not touch ThemeRegistry / FontManager after DisposeAsync. + if (_isDisposing) return; - } - // Hide all plugin windows while the New Game+ menu is open. - if ( - Config.HideInNewGamePlusMenu - && GameFunctions.GameFunctions.IsAddonInteractable( - GameFunctions.GameFunctions.NewGamePlusAddonName + var drawWatch = Stopwatch.StartNew(); + try + { + // v1.4.8 B2: pick up external edits of the active custom theme JSON + // without forcing the user to re-click the picker. The disk-stat is + // 1Hz-throttled inside RefreshActiveIfStale, so this is essentially + // free on built-in themes and ~1 stat/second on custom themes. + ThemeRegistry.RefreshActiveIfStale(); + + using IDisposable _style = Ui.StyleEngine.GlobalStyleScope.Push( + ThemeRegistry.Active, + ThemeRegistry, + Config.WindowOpacity + ); + + if (Config.HideInLoadingScreens && Condition[ConditionFlag.BetweenAreas]) + { + TypingIpc.Update(); + return; + } + + // Hide all plugin windows while the New Game+ menu is open. + if ( + Config.HideInNewGamePlusMenu + && GameFunctions.GameFunctions.IsAddonInteractable( + GameFunctions.GameFunctions.NewGamePlusAddonName + ) ) - ) - { + { + TypingIpc.Update(); + return; + } + + Interface.UiBuilder.DisableUserUiHide = !Config.HideWhenUiHidden; + + // RegularFont is nullable only because the live rebuild path + // disposes it before reassigning; both ends of that swap happen on + // this same draw thread, so it cannot be null here. + var useRegularFont = Config.FontsEnabled || Config.UseHellionFont; + using ((useRegularFont ? FontManager.RegularFont! : FontManager.Axis).Push()) + WindowSystem.Draw(); + TypingIpc.Update(); - return; + + FileDialogManager.Draw(); + } + finally + { + // finally so the early-return frames (loading screen / NG+) record + // their (cheap) time too instead of freezing on the last full frame. + drawWatch.Stop(); + LastDrawMs = drawWatch.Elapsed.TotalMilliseconds; } - - Interface.UiBuilder.DisableUserUiHide = !Config.HideWhenUiHidden; - - // RegularFont is nullable only because the live rebuild path - // disposes it before reassigning; both ends of that swap happen on - // this same draw thread, so it cannot be null here. - var useRegularFont = Config.FontsEnabled || Config.UseHellionFont; - using ((useRegularFont ? FontManager.RegularFont! : FontManager.Axis).Push()) - WindowSystem.Draw(); - - TypingIpc.Update(); - - FileDialogManager.Draw(); } internal void SaveConfig()