Skip to content

Commit c84c820

Browse files
committed
#2014 reduce round tripping when rendering the editor
1 parent 354682f commit c84c820

5 files changed

Lines changed: 300 additions & 16 deletions

File tree

.luacheckrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ globals = {
368368
"GetSpellTexture",
369369
"GetSubZoneText",
370370
"GetTime",
371+
"GetTimePreciseSec",
371372
"GetUnitName",
372373
"GetZoneText",
373374
"IsAddOnLoaded",

GSE/API/Init.lua

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,20 @@ function GSE.PrintDebugMessage(message, module)
160160
end
161161
end
162162

163+
--@debug@
164+
--- Monotonic wall-clock milliseconds, for timing UI phases. Source builds only:
165+
-- everything that calls it is stripped from a packaged build too.
166+
-- NOT debugprofilestop(): that counter is relative to the last
167+
-- debugprofilestart(), which is a shared global any addon may call -- a
168+
-- profiler reset from BigWigs or DBM mid-measurement silently shrinks the
169+
-- result, which is how a 24-second editor open first measured as 576ms.
170+
-- GetTimePreciseSec is monotonic and per-session; fall back on older clients.
171+
function GSE.NowMs()
172+
if GetTimePreciseSec then return GetTimePreciseSec() * 1000 end
173+
return debugprofilestop()
174+
end
175+
--@end-debug@
176+
163177
function GSE.DebugProfile(event)
164178
local currentTimeStop = debugprofilestop()
165179
if GSE.ProfileStop then

GSE_GUI/Editor.lua

Lines changed: 128 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,16 @@ local function ApplyFitPush(macroEditBox, delta, layoutQueue, layoutSeen)
635635
if not sharedChrome and delta ~= 0 and parent.explicitHeight
636636
and parent.height and parent.SetHeight then
637637
parent:SetHeight(parent.height + delta)
638+
-- A resized ancestor still has to re-place its children, so it goes
639+
-- on the SAME queue as the pass-through ones. Previously it was laid
640+
-- out by SetHeight itself, twice (baseMethods:SetHeight lays out
641+
-- self AND self.parent) and immediately -- outside the dedupe, once
642+
-- per box. Measured: 90 of one open's 289 layout passes came from
643+
-- those two lines alone.
644+
if not layoutSeen[parent] then
645+
layoutSeen[parent] = true
646+
layoutQueue[#layoutQueue + 1] = parent
647+
end
638648
elseif parent.DoLayout and not layoutSeen[parent] then
639649
layoutSeen[parent] = true
640650
layoutQueue[#layoutQueue + 1] = parent
@@ -650,13 +660,16 @@ fitFlushDriver:SetScript("OnUpdate", function(self)
650660
pendingFitPushes = {}
651661
self:Hide()
652662
local layoutQueue, layoutSeen = {}, {}
663+
local batched = UI and UI.SuspendLayout
664+
if batched then UI:SuspendLayout() end
653665
for _, push in ipairs(pushes) do
654666
-- A widget released (or pooled) since queueing has no parent chain of
655667
-- its own any more; its push is meaningless, skip it.
656668
if push[1].parent then
657669
ApplyFitPush(push[1], push[2], layoutQueue, layoutSeen)
658670
end
659671
end
672+
if batched and UI.ResumeLayout then UI:ResumeLayout() end
660673
for _, container in ipairs(layoutQueue) do
661674
container:DoLayout()
662675
end
@@ -683,11 +696,18 @@ local function FitMacroEditBoxToContent(macroEditBox, text)
683696
-- MEASURE the rendered text height with a hidden FontString in the box's
684697
-- own font (wraps included) instead of estimating rows x font size --
685698
-- estimates drifted by about a row and showed a spare empty line.
686-
local meter = macroEditBox.gseHeightMeter
699+
-- On the FRAME, not the widget table. Since #2014 MultiLineEditBox is a
700+
-- pooled type, and resetForReuse strips every key added after construction
701+
-- -- including this one. A FontString cannot be destroyed, so caching it on
702+
-- the widget meant a fresh one on every reuse, piling up hidden regions on a
703+
-- frame that is reused forever. That also slows the pool down: its reset
704+
-- walks {frame:GetRegions()} each time. The frame object survives reuse
705+
-- unchanged, so the meter parked on it is found again.
706+
local meter = macroEditBox.frame.gseHeightMeter
687707
if not meter then
688708
meter = macroEditBox.frame:CreateFontString(nil, "ARTWORK")
689709
meter:Hide()
690-
macroEditBox.gseHeightMeter = meter
710+
macroEditBox.frame.gseHeightMeter = meter
691711
end
692712
local fontPath, fontSize, fontFlags = eb:GetFont()
693713
if fontPath then meter:SetFont(fontPath, fontSize or 14, fontFlags or "") end
@@ -1756,6 +1776,18 @@ function GSE.HydrateClassActionIcons(classid)
17561776
end
17571777
end
17581778

1779+
-- One sequence's worth of the hydration below. This is what the editor
1780+
-- actually needs -- the icons for the sequence on screen -- and it is called
1781+
-- as each sequence is opened rather than sweeping the library on every open.
1782+
local function hydrateSequenceIcons(sequence)
1783+
if type(sequence) ~= "table" or type(sequence.Versions) ~= "table" then return end
1784+
for _, versionData in ipairs(sequence.Versions) do
1785+
if type(versionData) == "table" then
1786+
hydrateActionIcons(versionData.Actions)
1787+
end
1788+
end
1789+
end
1790+
17591791
function GSE.HydrateLoadedSequenceActionIcons(scanStats, saveChanges)
17601792
if type(GSE.Library) ~= "table" then return 0, 0, 0 end
17611793

@@ -3247,6 +3279,12 @@ function GSE.CreateEditor()
32473279
editframe.pendingScrollRestore = nil
32483280
local batchLayout = not _G.GSE_NoLayoutBatch
32493281
if batchLayout and UI and UI.SuspendLayout then UI:SuspendLayout() end
3282+
-- Paired with the report in finishDraw. Same reasoning as ManageTree's:
3283+
-- block count and elapsed time are what turn "the editor freezes" into a
3284+
-- number. Silent unless the Editor debug module is on.
3285+
--@debug@
3286+
local drawStartedAt = GSE.NowMs()
3287+
--@end-debug@
32503288
editframe.rawEditor = nil
32513289
SetOuterEditorScrollBarEnabled(true)
32523290
if tcontainer.SetListPadding then
@@ -4834,6 +4872,14 @@ function GSE.CreateEditor()
48344872
return layoutcontainer, finalizeToolbar, CreateAddButtonRow, CreateChildAddButtonRow
48354873
end
48364874
local function drawAction(pcontainer, action, version, keyPath, treepath)
4875+
-- Counted against CountActionBlocks below. The chunk-vs-synchronous
4876+
-- gate trusts that count, so if it undercounts, a big sequence
4877+
-- silently skips chunking and builds every block in one frame --
4878+
-- exactly the freeze the chunking exists to prevent. Divergence
4879+
-- between the two numbers is the tell.
4880+
--@debug@
4881+
editframe.lastDrawnBlocks = (editframe.lastDrawnBlocks or 0) + 1
4882+
--@end-debug@
48374883
local function drawChild(childContainer, childAction, childKeyPath, childTreepath)
48384884
local q = editframe.incBuildQueue
48394885
if q then
@@ -5962,6 +6008,15 @@ function GSE.CreateEditor()
59626008

59636009
local function finishDraw()
59646010
if tcontainer.DoLayout then tcontainer:DoLayout() end
6011+
--@debug@
6012+
GSE.PrintDebugMessage(
6013+
string.format("DrawSequenceEditor: %d blocks counted, %d drawn, %s, in %.0f ms",
6014+
editframe.lastDrawBlockCount or 0, editframe.lastDrawnBlocks or 0,
6015+
(editframe.lastDrawBlockCount or 0) > 12 and "chunked" or "one frame",
6016+
GSE.NowMs() - drawStartedAt),
6017+
Statics.DebugModules["Editor"]
6018+
)
6019+
--@end-debug@
59656020
if editframe.scrollContainer and editframe.scrollContainer.DoLayout then
59666021
editframe.scrollContainer:DoLayout()
59676022
if editframe.scrollContainer.SetScroll then
@@ -5994,6 +6049,10 @@ function GSE.CreateEditor()
59946049
return n
59956050
end
59966051
local totalBlocks = CountActionBlocks(macro)
6052+
--@debug@
6053+
editframe.lastDrawBlockCount = totalBlocks
6054+
editframe.lastDrawnBlocks = 0
6055+
--@end-debug@
59976056

59986057
local INCREMENTAL_MIN_BLOCKS = 12
59996058
if not (C_Timer and C_Timer.After) or totalBlocks <= INCREMENTAL_MIN_BLOCKS then
@@ -7621,10 +7680,55 @@ function GSE.CreateEditor()
76217680
return editframe
76227681
end
76237682

7683+
-- An open this slow is a defect, not a preference, so it is reported to the
7684+
-- user even with debug off -- once, with the numbers needed to act on it.
7685+
-- Below the threshold it stays on the debug module like every other timing.
7686+
--@debug@
7687+
local EDITOR_SLOW_OPEN_MS = 2000
7688+
local function ReportOpenTiming(message, elapsed)
7689+
if elapsed and elapsed > EDITOR_SLOW_OPEN_MS then
7690+
GSE.Print(message, Statics.DebugModules["Editor"])
7691+
else
7692+
GSE.PrintDebugMessage(message, Statics.DebugModules["Editor"])
7693+
end
7694+
end
7695+
7696+
--@end-debug@
7697+
76247698
function GSE.ShowSequences()
7699+
-- End-to-end open cost. ManageTree and DrawSequenceEditor time themselves;
7700+
-- this brackets everything, so a gap between them and this total says the
7701+
-- time is going somewhere neither of those covers -- which is exactly how
7702+
-- the whole-library icon scan below was found (14ms + 123ms inside a
7703+
-- seventeen second open).
7704+
--@debug@
7705+
local openStartedAt = GSE.NowMs()
7706+
--@end-debug@
76257707
local editframe = GSE.CreateEditor()
7708+
--@debug@
7709+
local afterCreate = GSE.NowMs()
7710+
--@end-debug@
76267711
editframe.ManageTree()
7627-
if GSE.HydrateLoadedSequenceActionIcons then GSE.HydrateLoadedSequenceActionIcons() end
7712+
--@debug@
7713+
local afterTree = GSE.NowMs()
7714+
--@end-debug@
7715+
-- The whole-library icon hydration used to run HERE, on every open, and it
7716+
-- was the freeze: it force-loads every class (GSE.EnsureClassLoaded ->
7717+
-- DecodeMessage per sequence, i.e. decompress + deserialise the entire
7718+
-- library) and then walks every action of every version of every sequence
7719+
-- resolving icons, synchronously, before the window appears. A large
7720+
-- library made that seventeen seconds -- with ManageTree at 14ms and the
7721+
-- block draw at 123ms on the same open, so all of it was this.
7722+
--
7723+
-- Nothing on screen needs it: the tree carries sequence icons, not action
7724+
-- icons, and it built fine before this ran. Action icons are needed only
7725+
-- for the sequence actually being drawn, so hydration moved to
7726+
-- GSE.GUILoadEditor, per sequence, as each one is opened. That also
7727+
-- restores the lazy-load design this pass was defeating -- every other
7728+
-- read path uses GSE.EnsureSequenceLoaded for one sequence at a time.
7729+
--
7730+
-- The full-library pass is still available to the icon-scan diagnostic,
7731+
-- which is a deliberate, user-initiated sweep and reports what it changed.
76287732
local lastSequencePath = GSE.GUI.GetLastSequenceEditorPath and GSE.GUI.GetLastSequenceEditorPath()
76297733
local classID = tostring(GSE.GetCurrentClassID and GSE.GetCurrentClassID() or "")
76307734

@@ -7703,6 +7807,20 @@ function GSE.ShowSequences()
77037807

77047808
SetSequenceEditorOpenPreference(true, "sequences")
77057809
editframe:Show()
7810+
-- Split, because "the open is slow" has three candidates and they behave
7811+
-- very differently: CreateEditor builds the whole window and only on the
7812+
-- FIRST open (later opens reuse it -- which is why Keybindings feels
7813+
-- instant if Sequences was opened first), ManageTree is O(sequences), and
7814+
-- select+show covers loading the sequence and drawing its blocks.
7815+
--@debug@
7816+
local openTotal = GSE.NowMs() - openStartedAt
7817+
ReportOpenTiming(
7818+
string.format("ShowSequences: %.0f ms (CreateEditor %.0f, ManageTree %.0f, select+show %.0f)",
7819+
openTotal, afterCreate - openStartedAt,
7820+
afterTree - afterCreate, GSE.NowMs() - afterTree),
7821+
openTotal
7822+
)
7823+
--@end-debug@
77067824
end
77077825

77087826
local function remoteSeqences(message, seqName)
@@ -7779,6 +7897,10 @@ function GSE.GUICreateNewSequence(editor, name, recordedstring)
77797897
editor.newname = nil
77807898
editor.Sequence = sequence
77817899
editor.ClassID = classid
7900+
-- A recorded sequence arrives with actions and no icons, and the tree lands
7901+
-- on its config node, so nothing would call GUILoadEditor for it this
7902+
-- session. Hydrate it here or its blocks draw blank until the next open.
7903+
hydrateSequenceIcons(sequence)
77827904
if GSE.GUI.ResetUndo then GSE.GUI.ResetUndo(editor) end
77837905
editor.ManageTree()
77847906
editor.treeContainer:SelectByValue(
@@ -7861,6 +7983,9 @@ function GSE.GUILoadEditor(editor, key, recordedstring)
78617983
editor.newname = nil
78627984
editor.Sequence = sequence
78637985
editor.ClassID = classid
7986+
-- Fill in any missing action icons for THIS sequence only, now that it is
7987+
-- decoded and before its blocks draw -- see the note in GSE.ShowSequences.
7988+
hydrateSequenceIcons(sequence)
78647989
if GSE.GUI.ResetUndo then GSE.GUI.ResetUndo(editor) end
78657990
end
78667991

0 commit comments

Comments
 (0)