Addon idle CPU usage

Hi AMR devs,

I investigated a small but persistent CPU issue in the current addon version. The addon appears to keep consuming a lot of CPU time even when the main panel is closed, and the root cause looks like the custom Amr.Wait helper in Core.lua.

Amr.Wait creates a frame named AmrWaitFrame and attaches an OnUpdate handler, but it never clears that handler or hides the frame after the wait queue becomes empty. Since startup initialization schedules a delayed wait via Amr.Wait(5, …), the frame starts running after login and continues receiving OnUpdate forever, even when no waits are pending and the AMR UI is closed.

Relevant code path:

  -- Core.lua
  local _waitTable = {}
  local _waitFrame = nil

  function Amr.Wait(delay, func, ...)
      if not _waitFrame then
          _waitFrame = CreateFrame("Frame", "AmrWaitFrame", UIParent)
          _waitFrame:SetScript("OnUpdate", function (self, elapse)
              local count = #_waitTable
              local i = 1
              while(i <= count) do
                  local waitRecord = table.remove(_waitTable, i)
                  local d = table.remove(waitRecord, 1)
                  local f = table.remove(waitRecord, 1)
                  local p = table.remove(waitRecord, 1)
                  if d > elapse then
                      table.insert(_waitTable, i, { d-elapse, f, p })
                      i = i + 1
                  else
                      count = count - 1
                      f(unpack(p))
                  end
              end
          end)
      end
      table.insert(_waitTable, { delay, func, {...} })
      return true
  end

Suggested fix: make the wait frame active only while _waitTable has pending callbacks. For example:

  local _waitTable = {}
  local _waitFrame = nil

  local function onWaitFrameUpdate(self, elapse)
      local count = #_waitTable
      local i = 1

      while(i <= count) do
          local waitRecord = table.remove(_waitTable, i)
          local d = table.remove(waitRecord, 1)
          local f = table.remove(waitRecord, 1)
          local p = table.remove(waitRecord, 1)

          if d > elapse then
              table.insert(_waitTable, i, { d-elapse, f, p })
              i = i + 1
          else
              count = count - 1
              f(unpack(p))
          end
      end

      if #_waitTable == 0 then
          self:SetScript("OnUpdate", nil)
          self:Hide()
      end
  end

  function Amr.Wait(delay, func, ...)
      if not _waitFrame then
          _waitFrame = CreateFrame("Frame", "AmrWaitFrame", UIParent)
          _waitFrame:Hide()
      end

      table.insert(_waitTable, { delay, func, {...} })

      if not _waitFrame:IsShown() then
          _waitFrame:SetScript("OnUpdate", onWaitFrameUpdate)
          _waitFrame:Show()
      end

      return true
  end

The important cases should still work:

  • A queued wait wakes the frame.
  • The frame stays active while a wait is pending.
  • The frame clears OnUpdate and hides itself once the queue drains.
  • A wait callback can schedule another Amr.Wait, and the frame remains active for the newly queued wait.

This should remove the continuous idle OnUpdate CPU cost without changing any other event-driven background features.

Thanks!

Thanks for pointing that out, I’ll take a look when I return from vacation.

I think that piece of code is actually quite old and was written by someone else… I might see if there are any newer ways to execute something asynchronously on a delay.

Later today we will post v173 of the addon which will fix this issue.

There is a newer built-in function called C_Timer.After that we can use to replace this very old method.