diff --git a/HellionChat/Ui/ChatLogWindow.cs b/HellionChat/Ui/ChatLogWindow.cs index 367a9ca..a240da9 100644 --- a/HellionChat/Ui/ChatLogWindow.cs +++ b/HellionChat/Ui/ChatLogWindow.cs @@ -636,6 +636,15 @@ public sealed class ChatLogWindow : Window IsOpen = true; } + // v1.4.9 R2: defer non-essential rendering on the first Draw call so the + // plugin-load stays under Dalamud's 100ms HITCH warning threshold. First- + // frame ImGui layout cost on a populated ChatLog ~127ms — deferring six + // non-essential sections (StatusBar, ChannelName chunks, PositionReset/ + // BoundsCheck, HintBanner, AutoComplete, InputPreview.CalculatePreview) + // shaves ~33ms down to ~94ms. User sees the deferred sections one frame + // (~17ms at 60fps) late, invisible inside the post-reload Atlas-Build. + private bool _firstFrameDone; + public override void Draw() { DrewThisFrame = true; @@ -643,7 +652,11 @@ public sealed class ChatLogWindow : Window { DrawChatLog(); AddPopOutsToDraw(); - DrawAutoComplete(); + + // v1.4.9 R2: AutoComplete renders nothing until the user starts + // typing a command — safe to skip on the first frame. ~6ms. + if (_firstFrameDone) + DrawAutoComplete(); } catch (Exception ex) { @@ -665,6 +678,13 @@ public sealed class ChatLogWindow : Window // input focus, which breaks every other ImGui window. Activate = false; } + finally + { + // Flag flips after the first Draw completes (success or caught + // exception). Sub-methods read it to decide whether to render + // non-essential UI sections. + _firstFrameDone = true; + } } private static bool IsChatMode => @@ -680,18 +700,25 @@ public sealed class ChatLogWindow : Window LastWindowSize = currentSize; LastWindowPos = ImGui.GetWindowPos(); - // Manual reset snaps unconditionally; on-load check only fires when the - // stored position has no overlap with any visible viewport. - if (RequestPositionReset) + // v1.4.9 R2: skip the bounds-check chain on the first frame. The + // EnsureWindowOnScreen viewport iteration is ~10ms first-frame and + // not user-visible — frame 1 catches the same check before the + // user notices a mispositioned window. + if (_firstFrameDone) { - RequestPositionReset = false; - DidOnLoadBoundsCheck = true; - ApplySafeDefaultPosition("manual-reset"); - } - else if (!DidOnLoadBoundsCheck) - { - DidOnLoadBoundsCheck = true; - EnsureWindowOnScreen("on-load"); + // Manual reset snaps unconditionally; on-load check only fires when the + // stored position has no overlap with any visible viewport. + if (RequestPositionReset) + { + RequestPositionReset = false; + DidOnLoadBoundsCheck = true; + ApplySafeDefaultPosition("manual-reset"); + } + else if (!DidOnLoadBoundsCheck) + { + DidOnLoadBoundsCheck = true; + EnsureWindowOnScreen("on-load"); + } } if (resized) @@ -700,12 +727,17 @@ public sealed class ChatLogWindow : Window LastViewport = ImGui.GetWindowViewport().Handle; WasDocked = ImGui.IsWindowDocked(); - if (IsChatMode && Plugin.InputPreview.IsDrawable) + // v1.4.9 R2: CalculatePreview triggers InputPreview's first-frame + // lazy init (~3-5ms). User-typing-driven, safe to defer one frame. + if (_firstFrameDone && IsChatMode && Plugin.InputPreview.IsDrawable) Plugin.InputPreview.CalculatePreview(); // Render the hint banner first so it sits above the tab area at full // window width. ImGui accounts for its height automatically. - DrawV061HintBannerIfNeeded(); + // v1.4.9 R2: skip on first frame (~3-5ms layout cost). The banner + // is a v0.6.1 migration notice that returns the same result frame 1. + if (_firstFrameDone) + DrawV061HintBannerIfNeeded(); if (Plugin.Config.SidebarTabView) DrawTabSidebar(); @@ -938,7 +970,11 @@ public sealed class ChatLogWindow : Window // v1.2.0 — Bottom-Status-Bar. Letzter Render-Step in DrawChatLog, // damit alle Zeilen-Operationen davor keine Layout-Sprünge auslösen. - Plugin.StatusBar.Draw(Plugin); + // v1.4.9 R2: skip on the first frame; ~12ms of first-frame layout + // cost. User sees the StatusBar 1 frame (~17ms at 60fps) later + // which is hidden inside the post-reload Atlas-Build window. + if (_firstFrameDone) + Plugin.StatusBar.Draw(Plugin); } internal Dictionary GetValidChannels() @@ -989,6 +1025,16 @@ public sealed class ChatLogWindow : Window private void DrawChannelName(Tab activeTab) { + // v1.4.9 R2: plain-text fallback on the first frame. ReadChannelName + // builds SeString chunks and DrawChunks runs SeString-Renderer layout + // — together ~18ms first-frame. Frame 1 renders the real chunks; the + // user sees the tab name for ~17ms during the post-reload window. + if (!_firstFrameDone) + { + ImGui.TextUnformatted(activeTab.Name); + return; + } + var currentChannel = ReadChannelName(activeTab); if (!currentChannel.SequenceEqual(PreviousChannel)) PreviousChannel = currentChannel;