Fix profsync guild-wide lag, price MC trash, bid on 0 GP items (10.20.5)

profsync: stop expanding skill headers on every read. Expand/CollapseSkillHeader
fire SKILL_LINES_CHANGED, which this addon also listens to, so each read re-armed
its own debounced check -- a permanent 15s cycle that dragged pfQuest's
quest-giver rebuild and Cartographer's addon walk along with it on every guild
member, whether or not they ever opened a profession. Reads are now plain scans
that report whether a collapsed header may have censored them; an expand happens
only where the difference actually matters (a known profession vanishing, the
open window's own rank, /status), and is bracketed by a grace window so our own
echo is ignored. A drop is never announced off an incomplete read.

loot: only skip items with NO price entry. A price of 0 is a known item that just
hasn't been priced yet, and it was silently skipping the bid/trade window.

prices: Molten Core trash belts and bracers 0 -> 67 GP, Boots of Blistering
Flames and Core Forged Helmet 100 -> 67, add Core Forged Greaves.

minep default 300.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Horyoshi
2026-08-20 16:52:02 +02:00
co-authored by Claude Opus 5
parent 879620200e
commit f38395a95c
4 changed files with 140 additions and 59 deletions
+109 -35
View File
@@ -109,6 +109,11 @@ local LEARN_SETTLE = 12 -- quiet time after the last "you have learned"
local LEARN_MAX_WAIT = 90 -- ...but never postpone a learn batch longer than this
local RANK_MIN_GAP = 300 -- min seconds between two skill-level-only messages
local SKILL_DEBOUNCE = 15 -- settle time after SKILL_LINES_CHANGED
-- ⚠ Expanding or collapsing a skill header FIRES SKILL_LINES_CHANGED — see the
-- warning above ReadSkills. This is how long the addon disbelieves that event
-- after having caused it itself. A genuine skill-up landing inside the window is
-- simply missed, which costs a cosmetic rank line until the next idle sweep.
local SKILL_ECHO_GRACE = 1
local IDLE_CHECK = 300 -- background skill re-check cadence
local ACK_TIMEOUT = 60 -- how long a batch may sit unacknowledged
local RETRY_DELAY = 600 -- pause everything after finding the relay offline
@@ -186,6 +191,7 @@ local pending = {} -- [profId] = batch awaiting acknowledgement
local metaCache = {} -- [profId][recipeId] = { n = name, i = icon }
local metaRounds = 0
local skillsCheckAt = nil -- debounced SKILL_LINES_CHANGED handler
local skillEchoUntil = nil -- ignore SKILL_LINES_CHANGED we caused ourselves
local nextIdleCheck = nil
local learnSettleAt = nil -- debounced "you have learned" burst
local learnFirstAt = nil -- start of the current burst, bounds the debounce
@@ -523,32 +529,26 @@ local function RestoreSkillHeaders(collapsed)
end
end
-- Returns [profId] = { r = rank, m = max } for every tracked profession this
-- character has, or nil when the pane could not be read cleanly right now.
local function ReadSkills()
local anyCollapsed = false
local function AnyCollapsedHeader()
for i = 1, GetNumSkillLines() do
local _, isHeader, isExpanded = GetSkillLineInfo(i)
if isHeader and not isExpanded then anyCollapsed = true break end
if isHeader and not isExpanded then return true end
end
return false
end
local restore = nil
if anyCollapsed then
restore = ExpandSkillHeaders()
if not restore then return nil end
end
-- ⚠ A NAME CAN APPEAR TWICE. "Survival" is both a Turtle profession and a
-- hunter's class skill line, and a hunter sees both in this pane, so the last
-- one read must not win by accident — the higher cap does, which is always the
-- profession. A hunter who does NOT have the profession is caught later and
-- more cheaply: nothing in their spellbook opens it, so it is never scanned
-- and never reported.
--
-- Deliberately no minimum-cap gate here. It would be a second way to tell the
-- two apart, but it is also a single condition that could silently blank every
-- profession on a client whose returns differ, and the spellbook check already
-- covers the case.
-- ⚠ A NAME CAN APPEAR TWICE. "Survival" is both a Turtle profession and a
-- hunter's class skill line, and a hunter sees both in this pane, so the last
-- one read must not win by accident — the higher cap does, which is always the
-- profession. A hunter who does NOT have the profession is caught later and
-- more cheaply: nothing in their spellbook opens it, so it is never scanned
-- and never reported.
--
-- Deliberately no minimum-cap gate here. It would be a second way to tell the
-- two apart, but it is also a single condition that could silently blank every
-- profession on a client whose returns differ, and the spellbook check already
-- covers the case.
local function ScanSkillLines()
local out = {}
for i = 1, GetNumSkillLines() do
local name, isHeader, _, rank, _, _, maxRank = GetSkillLineInfo(i)
@@ -562,11 +562,48 @@ local function ReadSkills()
end
end
end
RestoreSkillHeaders(restore)
return out
end
--[[ ⚠ EXPANDING A SKILL HEADER IS EXPENSIVE FOR THE WHOLE UI, NOT JUST FOR US.
ExpandSkillHeader/CollapseSkillHeader fire SKILL_LINES_CHANGED — FrameXML's own
expand button calls nothing else and relies on that event to redraw the pane —
and that event goes to EVERY addon. On a normal OctoWoW install it lands on
pfQuest, whose handler compares the visible skill-line names and, because
collapsing a header hides its children from GetSkillLineInfo, sees a change
every single time and runs a full pfDatabase:SearchQuests() quest-giver rebuild;
and on Cartographer's Professions module, which walks GetNumAddOns() calling
GetAddOnMetadata on every installed addon.
Worse, it used to feed itself: this addon listens to SKILL_LINES_CHANGED too,
so its own expand/collapse re-armed the debounced check, which read the skills
again, which expanded again — a permanent 15-second cycle of quest-giver
rebuilds on every guild member, whether or not they ever opened a profession.
That is what made the whole guild lag (2026-08-09).
So: the plain read NEVER touches a header, and callers that can live with a
partial answer ask for one. `mayExpand` is for the rare case where the
difference actually matters — telling "profession abandoned" apart from
"profession hidden under a collapsed header" — and it brackets the mutation in
skillEchoUntil so our own echo is not mistaken for a real skill change.
Returns skills, complete. `complete` false means a collapsed header may be
hiding professions, so a MISSING one proves nothing. ]]
local function ReadSkills(mayExpand)
local skills = ScanSkillLines()
if not AnyCollapsedHeader() then return skills, true end
if not mayExpand then return skills, false end
skillEchoUntil = GetTime() + SKILL_ECHO_GRACE
local restore = ExpandSkillHeaders() -- nil when the Skills pane is open
if not restore then return skills, false end
skills = ScanSkillLines()
RestoreSkillHeaders(restore)
skillEchoUntil = GetTime() + SKILL_ECHO_GRACE
return skills, true
end
-- ---------------------------------------------------------------------------
-- Reading an open profession window
-- ---------------------------------------------------------------------------
@@ -790,13 +827,31 @@ end
local function CheckSkills()
if not CanSend() then return end
local skills = ReadSkills()
if not skills then
skillsCheckAt = GetTime() + SKILL_DEBOUNCE -- Skills pane open, come back
return
end
local skills, complete = ReadSkills(false)
local db = DB()
-- ⚠ Only pay for a header expand when a profession we ALREADY KNOW ABOUT has
-- vanished from the read, because that is the single decision a partial read
-- cannot make (see ReadSkills for what an expand costs the rest of the UI). A
-- rank read off a visible line is trustworthy on its own, and a BRAND NEW
-- profession hidden under a collapsed header is not worth an expand either:
-- opening its window is the only way to use it, and that reports the rank
-- alongside the recipes.
if not complete then
local suspect = false
for id in pairs(db.profs) do
if PROF_BY_ID[id] and not skills[id] then suspect = true break end
end
if suspect then
local full, ok = ReadSkills(true)
if not ok then
skillsCheckAt = GetTime() + SKILL_DEBOUNCE -- Skills pane open, come back
return
end
skills, complete = full, ok
end
end
-- Gone from the pane means abandoned. The row is kept, flagged, and only
-- deleted once the website confirms — otherwise a lost "dropped" message
-- would leave the site advertising recipes the player no longer has.
@@ -809,7 +864,10 @@ local function CheckSkills()
db.profs[id] = nil
pending[id] = nil
elseif not skills[id] then
if not state.gone or not pending[id] then
-- Belt and braces: the suspect check above should have upgraded the read
-- to a complete one before we get here. Never announce a drop off a read
-- that a collapsed header could have censored.
if complete and (not state.gone or not pending[id]) then
state.gone = true
pending[id] = { drop = true, at = GetTime() }
Send("X", tostring(id))
@@ -1043,8 +1101,16 @@ local function OnWindowVisible(isCraft)
if prof then
local state = ProfState(prof.id)
local skills = ReadSkills()
-- The cheap read first; only if THIS profession is the one hidden under a
-- collapsed header is an expand worth it — and here it is, because the
-- alternative is publishing a rank of 0/0. This path already only runs when
-- the recipe list actually changed, so it is rare.
local skills = ReadSkills(false)
local info = skills and skills[prof.id]
if not info then
skills = ReadSkills(true)
info = skills and skills[prof.id]
end
-- The stored rank is -1 until the website has confirmed one, which is not
-- a number to put on the wire; fall back to 0 when the pane is unreadable.
local rank = (info and info.r) or state.r
@@ -1107,6 +1173,12 @@ f:SetScript("OnEvent", function()
end
if event == "SKILL_LINES_CHANGED" then
-- ⚠ IGNORE OUR OWN ECHO. Expanding or collapsing a header to read the pane
-- fires this event, so without the grace window the check that did the
-- expanding re-arms itself and runs again forever, every SKILL_DEBOUNCE
-- seconds, dragging pfQuest's quest-giver rebuild along with it. See
-- ReadSkills.
if skillEchoUntil and GetTime() < skillEchoUntil then return end
-- Fires on every point of skill-up, so it only arms a debounced check; that
-- check sends nothing unless a profession appeared, vanished, or moved.
skillsCheckAt = GetTime() + SKILL_DEBOUNCE
@@ -1198,10 +1270,12 @@ local function PrintStatus()
or (online and " online" or " OFFLINE"))
Say("guild " .. guild .. " | relay " .. relay)
local skills = ReadSkills()
if not skills then
Say("could not read the Skills pane (close the character window)")
else
-- A human typed this, so the expand is worth it here whatever it costs.
local skills, complete = ReadSkills(true)
if not complete then
Say("partial reading — close the character window for the full picture")
end
do
local parts, any = {}, false
for id, info in pairs(skills) do
local prof = PROF_BY_ID[id]