diff --git a/src/Classes/GemSelectControl.lua b/src/Classes/GemSelectControl.lua index dddbde3a88..19f17644b6 100644 --- a/src/Classes/GemSelectControl.lua +++ b/src/Classes/GemSelectControl.lua @@ -53,6 +53,7 @@ local GemSelectClass = newClass("GemSelectControl", "EditControl", function(self lifeReservationPercent = "LifePercent", } self.imbuedSelect = imbued + self.dpsBuildFlag = false end) function GemSelectClass:CalcOutputWithThisGem(calcFunc, gemData, useFullDPS) @@ -168,11 +169,12 @@ function GemSelectClass:BuildList(buf) t_remove(tagsList, 1) -- Search for gem name using increasingly broad search patterns + local lowerSearch = searchTerm:lower() local patternList = { - "^ " .. searchTerm:lower().."$", -- Exact match - "^" .. searchTerm:lower():gsub("%a", " %0%%l+") .. "$", -- Simple abbreviation ("CtF" -> "Cold to Fire") - "^ " .. searchTerm:lower(), -- Starts with - searchTerm:lower(), -- Contains + "^ " .. lowerSearch.."$", -- Exact match + "^" .. lowerSearch:gsub("%a", " %0%%l+") .. "$", -- Simple abbreviation ("CtF" -> "Cold to Fire") + "^ " .. lowerSearch, -- Starts with + lowerSearch, -- Contains } for i, pattern in ipairs(patternList) do local matchList = { } @@ -340,37 +342,40 @@ function GemSelectClass:UpdateSortCache() -- Check for nil because some fields may not be populated, default to 0 local baseDPS = (dpsField == "FullDPS" and calcBase[dpsField] ~= nil and calcBase[dpsField]) or (calcBase.Minion and calcBase.Minion.CombinedDPS) or (calcBase[dpsField] ~= nil and calcBase[dpsField]) or 0 + sortCache.calcFunc = calcFunc + sortCache.useFullDPS = useFullDPS + sortCache.baseDPS = baseDPS + sortCache.dpsField = dpsField + sortCache.pendingGems = { } + for gemId, gemData in pairs(self.gems) do sortCache.dps[gemId] = baseDPS - -- Ignore gems that don't support the active skill + -- Gems that support the active skill or have global effects need DPS calc if sortCache.canSupport[gemId] or (gemData.grantedEffect.hasGlobalEffect and not gemData.grantedEffect.support) then - local output = self:CalcOutputWithThisGem(calcFunc, gemData, useFullDPS) - -- Check for nil because some fields may not be populated, default to 0 - sortCache.dps[gemId] = (dpsField == "FullDPS" and output[dpsField] ~= nil and output[dpsField]) or (output.Minion and output.Minion.CombinedDPS) or (output[dpsField] ~= nil and output[dpsField]) or 0 - end - -- Color based on the DPS - if sortCache.dps[gemId] > baseDPS then - sortCache.dpsColor[gemId] = "^x228866" - elseif sortCache.dps[gemId] < baseDPS then - sortCache.dpsColor[gemId] = "^xFF4422" - else - sortCache.dpsColor[gemId] = "^xFFFF66" + sortCache.pendingGems[#sortCache.pendingGems + 1] = gemId end + -- Neutral color until DPS is computed + sortCache.dpsColor[gemId] = "" end - --ConPrintf("Gem Selector time: %d ms", GetTime() - start) + self.dpsBuildFlag = true end function GemSelectClass:SortGemList(gemList) local sortCache = self.sortCache + local gems = self.gems + -- cache names to avoid repeated table lookups in comparator + local names = {} + for _, gemId in ipairs(gemList) do + local gem = gems[gemId] + names[gemId] = gem and gem.name or gemId + end t_sort(gemList, function(a, b) if sortCache.canSupport[a] == sortCache.canSupport[b] then if self.skillsTab.sortGemsByDPS and sortCache.dps[a] ~= sortCache.dps[b] then return sortCache.dps[a] > sortCache.dps[b] else - local nameA = (self.gems[a] and self.gems[a].name) or a - local nameB = (self.gems[b] and self.gems[b].name) or b - return nameA < nameB + return names[a] < names[b] end else return sortCache.canSupport[a] @@ -378,6 +383,46 @@ function GemSelectClass:SortGemList(gemList) end) end +function GemSelectClass:DPSBuilder() + local sortCache = self.sortCache + if not sortCache or not sortCache.pendingGems then return end + + local pending = sortCache.pendingGems + local calcFunc = sortCache.calcFunc + local useFullDPS = sortCache.useFullDPS + local baseDPS = sortCache.baseDPS + local dpsField = sortCache.dpsField + local start = GetTime() + + for index, gemId in ipairs(pending) do + local gemData = self.gems[gemId] + if gemData then + local output = self:CalcOutputWithThisGem(calcFunc, gemData, useFullDPS) + sortCache.dps[gemId] = (dpsField == "FullDPS" and output[dpsField] ~= nil and output[dpsField]) or (output.Minion and output.Minion.CombinedDPS) or (output[dpsField] ~= nil and output[dpsField]) or 0 + if sortCache.dps[gemId] > baseDPS then + sortCache.dpsColor[gemId] = "^x228866" + elseif sortCache.dps[gemId] < baseDPS then + sortCache.dpsColor[gemId] = "^xFF4422" + else + sortCache.dpsColor[gemId] = "^xFFFF66" + end + end + local now = GetTime() + if now - start > 50 then + if #self.searchStr == 0 then + self:SortGemList(self.list) + end + coroutine.yield() + start = now + end + end + + if #self.searchStr == 0 then + self:SortGemList(self.list) + end + sortCache.pendingGems = nil +end + function GemSelectClass:UpdateGem(setText, addUndo, focusLost) local gemId = self.list[m_max(self.selIndex, 1)] -- don't process unless the buffer equals an actual gem, whether typed, clicked, or navigated with arrows @@ -428,6 +473,20 @@ function GemSelectClass:IsMouseOver() end function GemSelectClass:Draw(viewPort, noTooltip) + if self.dpsBuildFlag then + self.dpsBuildFlag = false + self.dpsBuilder = coroutine.create(self.DPSBuilder) + end + if self.dpsBuilder then + local res, errMsg = coroutine.resume(self.dpsBuilder, self) + if launch.devMode and not res then + error(errMsg) + end + if coroutine.status(self.dpsBuilder) == "dead" then + self.dpsBuilder = nil + end + end + self.EditControl:Draw(viewPort, noTooltip and not self.forceTooltip) local x, y = self:GetPos() local width, height = self:GetSize() @@ -446,6 +505,11 @@ function GemSelectClass:Draw(viewPort, noTooltip) end if self.dropped then SetDrawLayer(nil, 5) + if self.dpsBuilder then + local dots = ("."):rep((math.floor(GetTime() / 500) % 3) + 1) + SetDrawColor(0.75, 0.75, 0.75) + DrawString(x + width - 80, y, "LEFT", height - 2, "VAR", "Sorting" .. dots) + end local cursorX, cursorY = GetCursorPos() self.hoverSel = mOverComp == "DROP" and math.floor((cursorY - y - height + scrollBar.offset) / (height - 4)) + 1 if self.hoverSel and not self.gems[self.list[self.hoverSel]] then @@ -475,10 +539,10 @@ function GemSelectClass:Draw(viewPort, noTooltip) local gemText = gemData and gemData.name or "" DrawString(0, y, "LEFT", height - 4, "VAR", gemText) if gemData then - if gemData.grantedEffect.support and self.sortCache.canSupport[gemId] then + if gemData.grantedEffect.support and self.sortCache.canSupport[gemId] and self.sortCache.dpsColor[gemId] ~= "" then SetDrawColor(self.sortCache.dpsColor[gemId]) main:DrawCheckMark(width - 4 - height / 2 - (scrollBar.enabled and 18 or 0), y + (height - 4) / 2, (height - 4) * 0.8) - elseif gemData.grantedEffect.hasGlobalEffect then + elseif gemData.grantedEffect.hasGlobalEffect and self.sortCache.dpsColor[gemId] ~= "" then SetDrawColor(self.sortCache.dpsColor[gemId]) DrawString(width - 4 - height / 2 - (scrollBar.enabled and 18 or 0), y - 2, "CENTER_X", height, "VAR", "+") end @@ -487,14 +551,23 @@ function GemSelectClass:Draw(viewPort, noTooltip) SetViewport() self:DrawControls(viewPort, (noTooltip and not self.forceTooltip) and self) if self.hoverSel then - local calcFunc, calcBase = self.skillsTab.build.calcsTab:GetMiscCalculator(self.build) - if calcFunc then - self.tooltip:Clear() - local gemData = self.gems[self.list[self.hoverSel]] - local output= self:CalcOutputWithThisGem(calcFunc, gemData, self.skillsTab.sortGemsByDPSField == "FullDPS") - local gemInstance = { + -- Debounce hover + if self.hoverSel == self.lastHoverSel then + self.hoverFrameCount = (self.hoverFrameCount or 0) + 1 + else + self.lastHoverSel = self.hoverSel + self.hoverFrameCount = 0 + end + if self.hoverFrameCount >= 2 then + local calcFunc, calcBase = self.skillsTab.build.calcsTab:GetMiscCalculator(self.build) + if calcFunc then + self.tooltip:Clear() + local gemData = self.gems[self.list[self.hoverSel]] + local output= self:CalcOutputWithThisGem(calcFunc, gemData, self.skillsTab.sortGemsByDPSField == "FullDPS") + local gemInstance = { level = self.skillsTab:ProcessGemLevel(gemData, self.imbuedSelect), quality = self.skillsTab.defaultGemQuality or 0, + qualityId = "Default", count = 1, enabled = true, enableGlobal1 = true, @@ -505,11 +578,15 @@ function GemSelectClass:Draw(viewPort, noTooltip) displayEffect = nil, gemData = gemData } - self:AddGemTooltip(gemInstance) - self.tooltip:AddSeparator(10) - self.skillsTab.build:AddStatComparesToTooltip(self.tooltip, calcBase, output, "^7Selecting this gem will give you:") - self.tooltip:Draw(x, y + height + 2 + (self.hoverSel - 1) * (height - 4) - scrollBar.offset, width, height - 4, viewPort) + self:AddGemTooltip(gemInstance) + self.tooltip:AddSeparator(10) + self.skillsTab.build:AddStatComparesToTooltip(self.tooltip, calcBase, output, "^7Selecting this gem will give you:") + self.tooltip:Draw(x, y + height + 2 + (self.hoverSel - 1) * (height - 4) - scrollBar.offset, width, height - 4, viewPort) + end end + else + self.lastHoverSel = nil + self.hoverFrameCount = 0 end SetDrawLayer(nil, 0) else