Thread Tools Display Modes
10-04-22, 11:22 AM   #1
clicket
A Deviate Faerie Dragon
 
clicket's Avatar
AddOn Author - Click to view addons
Join Date: Jun 2018
Posts: 12
Cool Stopping/Reducing microstutter on large func calls

Hello,

My addon attaches a frame to unitframes and displays auras on UNIT_AURA events per frame. The issue I'm having is that I currently look for frames from over 20+ different addons and because there's no way (that I know of) to know when a new frame has been added to the screen, I have to manually scan all frames on the screen to find attachment points for my units.

I do this by using some recursive scanning functionality, but when I run the scan (currently on GROUP_ROSTER_UPDATE) there is a micro stutter / slight freeze. Which, is fair given how hefty the function is.

My question is, is there a best practice for smoothing out large calls over a period of time to avoid this stutter without running into "script ran too long" issues? Currently use a coroutine, but that hasn't seemed to help in a meaningful way.

The repo for the code in question is here: https://github.com/clicketz/buff-ove...patibility.lua

But the relevant bits are here:

Lua Code:
  1. -- This is run out of combat to avoid "script ran too long" errors
  2. -- Goal is to avoid massive coroutine garbage buildup blame
  3. -- Unnecessary to functionality and a "needless" tax.
  4. -- Can disable, but memory blame will be notable.
  5. local collect = CreateFrame("Frame")
  6. collect:SetScript("OnEvent", function(self)
  7.     collectgarbage()
  8.     self:UnregisterAllEvents()
  9. end)
  10.  
  11. -- Main scan func
  12. local function ScanFrames(depth, frame, ...)
  13.     coroutine.yield()
  14.     if not frame then return end
  15.  
  16.     if depth < maxDepth and frame.IsForbidden and not frame:IsForbidden() then
  17.         local type = frame:GetObjectType()
  18.         if type == "Frame" or type == "Button" then
  19.             ScanFrames(depth + 1, frame:GetChildren())
  20.  
  21.             local name = frame:GetName()
  22.             -- Make sure we only store unit frames
  23.             local unit = SecureButton_GetUnit(frame)
  24.  
  25.             if name and unit and not BuffOverlay.frames[frame] then
  26.                 for enabledFrames, data in pairs(enabledFrameNames) do
  27.                     if name:find(enabledFrames) then
  28.                         BuffOverlay.frames[frame] = { unit = data.unit }
  29.                         BuffOverlay:SetupContainer(frame)
  30.                         break
  31.                     end
  32.                 end
  33.             end
  34.         end
  35.     end
  36.     ScanFrames(depth, ...)
  37. end
  38.  
  39. -- Used for unit validation per frame so we're not showing the wrong auras
  40. function BuffOverlay:UpdateUnits()
  41.     for frame, data in pairs(self.frames) do
  42.         local unit = frame[data.unit] or SecureButton_GetUnit(frame)
  43.  
  44.         if unit and not data.blizz then
  45.             self:AddUnitFrame(frame, unit)
  46.         end
  47.     end
  48.     self:RefreshOverlays()
  49.  
  50.     if InCombatLockdown() then
  51.         collect:RegisterEvent("PLAYER_REGEN_ENABLED")
  52.     else
  53.         collectgarbage()
  54.     end
  55. end
  56.  
  57. -- OnUpdate used for controlling coroutine
  58. coFrame:Hide()
  59. coFrame:SetScript("OnUpdate", function(self)
  60.     local start = debugprofilestop()
  61.  
  62.     while debugprofilestop() - start < 15 and coroutine.status(co) ~= "dead" do
  63.         coroutine.resume(co, 1, UIParent:GetChildren())
  64.     end
  65.  
  66.     if coroutine.status(co) == "dead" then
  67.         self:Hide()
  68.         BuffOverlay:UpdateUnits()
  69.     end
  70. end)
  71.  
  72. -- Gets called every GROUP_ROSTER_UPDATE to maintain all visible frames from all addons that we're interested in
  73. function BuffOverlay:GetAllFrames()
  74.     if not coFrame:IsShown() then
  75.         -- Timer is needed to account for addons that have a delay in creating their frames
  76.         C_Timer.After(1, function()
  77.             co = coroutine.create(ScanFrames)
  78.             coFrame:Show()
  79.         end)
  80.     end
  81. end

This does functionally work very well for my uses, but it would be nice to get rid of that stutter.

Last edited by clicket : 10-05-22 at 06:39 AM.
  Reply With Quote
10-04-22, 12:22 PM   #2
Dridzt
A Pyroguard Emberseer
 
Dridzt's Avatar
AddOn Author - Click to view addons
Join Date: Nov 2005
Posts: 1,360
Calling collectgarbage() explicitly is a good way to make the problem worse

Other than that this reads like something that's "cool concept code" but shouldn't run in someone's addon folder.

There has to be a better way to anchor your frames to other frames that doesn't rely on recursively scanning on every group_roster_update which is quite spammy itself.

Edit: My suggestion would be to think about how to re-engineer it so it doesn't do that.

Last edited by Dridzt : 10-04-22 at 12:35 PM.
  Reply With Quote
10-04-22, 12:47 PM   #3
clicket
A Deviate Faerie Dragon
 
clicket's Avatar
AddOn Author - Click to view addons
Join Date: Jun 2018
Posts: 12
Originally Posted by Dridzt View Post
Calling collectgarbage() explicitly is a good way to make the problem worse
Yeah I fully agree. When I test the stutter I never run with it on but not calling it hasn't changed anything in my experience so I left it in for the post. Easy enough to disable, though. I currently use it to appease users who believe a high memory spike means poor performance or something's wrong, which has been complained about to me in the past.

Originally Posted by Dridzt View Post
Other than that this reads like something that's "cool concept code" but shouldn't run in someone's addon folder.
The recursive call is based on a library used by weakauras (LibGetFrame). I'm not sure it's as "cool concept but don't use" as you're implying. But, fair enough. I'd gladly stop using the scan if I could.

Originally Posted by Dridzt View Post
There has to be a better way to anchor your frames to other frames that doesn't rely on recursively scanning on every group_roster_update which is quite spammy itself.
I'm certainly open to suggestion, but I don't see another way to get frame information from N amount of addons without scanning for them. There are no events (that I know of) that will tell me when frames get added by addons (would love something like a COMPACT_UNIT_FRAME_CREATED event similar to NAME_PLATE_CREATED).

For Blizzard frames I just hook into CompactUnitFrame_UpdateAuras which gives me all the frame/unit information I need so there's no scan needed. But without each individual addon offering up that information in a way that everyone can access on event, I'm just not sure how else to go about it.

Last edited by clicket : 10-04-22 at 07:20 PM.
  Reply With Quote
10-04-22, 05:02 PM   #4
Xrystal
nUI Maintainer
 
Xrystal's Avatar
Premium Member
AddOn Author - Click to view addons
Join Date: Feb 2006
Posts: 5,928
Are these other addons plugins to your addons or are you trying to access someone else's addon frame ?

The former can be *fixed* by adding some rules and steps those addons need to uphold and incorporate.

For example nUI allows plugins to be made that changes the skin and layout of the frames that nUI manages. nUI has a function called Registerxxxxxx where xxxx is a feature that nUI manages on the frames. It then talks to the other addon so that the other addon knows what to do or the other addon relays the data on registration that nUI then utilises when it calls its functions.

Whether this will work for your idea or not I don't know but it might provide you with an idea if it is along the right lines.
__________________


Characters:
Gwynedda - 70 - Demon Warlock
Galaviel - 65 - Resto Druid
Gamaliel - 61 - Disc Priest
Gwynytha - 60 - Survival Hunter
Lienae - 60 - Resto Shaman
Plus several others below level 60

Info Panel IDs : http://www.wowinterface.com/forums/s...818#post136818
  Reply With Quote
10-04-22, 05:22 PM   #5
clicket
A Deviate Faerie Dragon
 
clicket's Avatar
AddOn Author - Click to view addons
Join Date: Jun 2018
Posts: 12
Originally Posted by Xrystal View Post
Are these other addons plugins to your addons or are you trying to access someone else's addon frame ?
Unfortunately, the latter. The addons are not plugins. I am grabbing the (raid) frame(s) that other addons create.

Would definitely love if all these addons just used an API from me instead
  Reply With Quote
10-04-22, 07:18 PM   #6
Dridzt
A Pyroguard Emberseer
 
Dridzt's Avatar
AddOn Author - Click to view addons
Join Date: Nov 2005
Posts: 1,360
Originally Posted by clicket View Post
The recursive call is based on a library used by weakauras (LibGetFrame). I'm not sure it's as "cool concept but don't use" as you're implying. But, fair enough. I'd gladly stop using the scan if I could.
I haven't looked at WeakAuras code but I've used the addon and I'm willing to bet it uses that library to get a reference to a frame when you use the Frame Finder to add an aura to an actionbutton or whatever.

It would use that to get a reference when you set it up, I really doubt it's something called in an OnUpdate or some spammy event. They probably use it to find the frame store the reference to the aura and just do a lookup for trigger updates.


I'll think on it as to how you could do what you want to do a different way.
  Reply With Quote
10-04-22, 07:45 PM   #7
clicket
A Deviate Faerie Dragon
 
clicket's Avatar
AddOn Author - Click to view addons
Join Date: Jun 2018
Posts: 12
Originally Posted by Dridzt View Post
I haven't looked at WeakAuras code but I've used the addon and I'm willing to bet it uses that library to get a reference to a frame when you use the Frame Finder to add an aura to an actionbutton or whatever.

It would use that to get a reference when you set it up, I really doubt it's something called in an OnUpdate or some spammy event. They probably use it to find the frame store the reference to the aura and just do a lookup for trigger updates.
I haven't looked too deeply at their code either. Thinking about it though, I *do* know you can dynamically attach auras to frames in their more recent versions. Perhaps I should look a bit closer...

Originally Posted by Dridzt View Post
I'll think on it as to how you could do what you want to do a different way.
I appreciate it! Not just to make my addon better, but also genuine curiosity. There doesn't seem (to me) to be too many avenues here besides trying to reduce the impact of the scan.
  Reply With Quote
10-04-22, 08:53 PM   #8
Fizzlemizz
I did that?
 
Fizzlemizz's Avatar
Premium Member
AddOn Author - Click to view addons
Join Date: Dec 2011
Posts: 1,877
All frames of the type you seem to be interested in are created in the one place. Someone who knows more about it than I might tell you it's a bad idea but wouldn't hooking CreateFrame and inspecting the Templates parameter do what you want rather than randomly searching through a bazillion frames every ...?

All that would be left to search are the frames created before your addon loaded like the default UI.
__________________
Fizzlemizz
Maintainer of Discord Unit Frames and Discord Art.
Author of FauxMazzle, FauxMazzleHUD and Move Pad Plus.
  Reply With Quote
10-04-22, 09:51 PM   #9
clicket
A Deviate Faerie Dragon
 
clicket's Avatar
AddOn Author - Click to view addons
Join Date: Jun 2018
Posts: 12
Originally Posted by Fizzlemizz View Post
All frames of the type you seem to be interested in are created in the one place. Someone who knows more about it than I might tell you it's a bad idea but wouldn't hooking CreateFrame and inspecting the Templates parameter do what you want rather than randomly searching through a bazillion frames every ...?

All that would be left to search are the frames created before your addon loaded like the default UI.
That... is actually pretty hilarious. There are a couple (not insurmountable) hurdles, but overall it could work in theory.

I'm sure someone will chime in to tell us both how hilariously stupid that would be, but I like it!
  Reply With Quote
10-05-22, 12:38 AM   #10
Fizzlemizz
I did that?
 
Fizzlemizz's Avatar
Premium Member
AddOn Author - Click to view addons
Join Date: Dec 2011
Posts: 1,877
There's no reason I can see for CreateFrame to be "extra special function" but it's probably not what you want every addon doing and only with the absolute minimum required to find what you need.

But hookescurefunc is a hammer I was given so until proven otherwise...
__________________
Fizzlemizz
Maintainer of Discord Unit Frames and Discord Art.
Author of FauxMazzle, FauxMazzleHUD and Move Pad Plus.

Last edited by Fizzlemizz : 10-05-22 at 12:43 AM.
  Reply With Quote
10-05-22, 02:39 AM   #11
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,323
I'd be careful with hooking CreateFrame() as many areas of the DefaultUI generate dynamic frames, a lot of them are sensitive to taint and will cease functioning and/or spit out taint errors. Some examples are NamePlates and RaidFrames.
__________________
WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)
  Reply With Quote
10-05-22, 03:03 AM   #12
clicket
A Deviate Faerie Dragon
 
clicket's Avatar
AddOn Author - Click to view addons
Join Date: Jun 2018
Posts: 12
Originally Posted by SDPhantom View Post
I'd be careful with hooking CreateFrame() as many areas of the DefaultUI generate dynamic frames, a lot of them are sensitive to taint and will cease functioning and/or spit out taint errors. Some examples are NamePlates and RaidFrames.
Yeah, good call. Luckily I only care about addon frames for this, and pretty specific ones at that. I can pretty safely ignore forbidden frames.

I've been messing around a bit with this and I'm cautiously optimistic about it, but obviously if it just taints the entire UI I'd be very sad.
  Reply With Quote
10-05-22, 03:40 AM   #13
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,323
The problem being, the only way to "secure hook" a function is with hooksecurefunc(), but it won't give you the return for the hooked function, just its args when called. The problem with hooking CreateFrame() in a more standard Lua way will taint any execution path calling it regardless of if you did anything to the created frame or not. The calling code will then likely either call protected functions itself or spread taint through storing its own variables that are then read by other functions that make protected function calls.
__________________
WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)
  Reply With Quote
10-05-22, 04:01 AM   #14
clicket
A Deviate Faerie Dragon
 
clicket's Avatar
AddOn Author - Click to view addons
Join Date: Jun 2018
Posts: 12
One of the args of CreateFrame is the frame name, so I just use that name to find the frame in _G and pass along the reference. I'm *hoping* there's nothing taint-worthy there, but I've been wrong about taint in the past.

Lua Code:
  1. hooksecurefunc("CreateFrame", function(frameType, name, _, template)
  2.     if not addOnsExist then return end
  3.  
  4.     if frameType == "Button" and template then
  5.         local frame = _G[name]
  6.  
  7.         if frame and not frame:IsForbidden() then
  8.             if not name:match("BuffOverlayBar") then
  9.                 tempFramePool[frame] = true
  10.             end
  11.         end
  12.     end
  13. end)

It's working really well for my uses, although it does feel dirty.

Last edited by clicket : 10-06-22 at 04:56 PM.
  Reply With Quote
10-05-22, 09:44 AM   #15
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,323
Frame name is a completely optional arg. If it was supplied, there would be no reason to hook CreateFrame() in the first place, you can just pull the frame from the global environment by that name anyway.

If you have named frames like this:
Code:
SomeAddOnUnitFrame1
SomeAddOnUnitFrame2
SomeAddOnUnitFrame3
SomeAddOnUnitFrame4
You can scan and access these quickly like this:
Lua Code:
  1. local i=1;
  2. while true do-- Infinite loop, we'll break manually
  3.     local button=_G["SomeAddOnUnitFrame"..i];
  4.     if button then
  5. --      Do something with the frame
  6.  
  7.         i=i+1;--    Don't forget to increment the counter
  8.     else
  9.         break;--    Manually break out of loop
  10.     end
  11. end
Note: The unique thing about a repeat ... until block is it can check the state of locals defined inside the loop.
__________________
WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)

Last edited by SDPhantom : 10-05-22 at 10:07 AM.
  Reply With Quote
10-05-22, 05:59 PM   #16
clicket
A Deviate Faerie Dragon
 
clicket's Avatar
AddOn Author - Click to view addons
Join Date: Jun 2018
Posts: 12
Originally Posted by SDPhantom View Post
Frame name is a completely optional arg. If it was supplied, there would be no reason to hook CreateFrame() in the first place, you can just pull the frame from the global environment by that name anyway.
Yes, but for my purposes there will be no unnamed frames that I need so it works out. But I still need to hook it because otherwise I'm back in the situation of randomly scanning all the time to find frames that I don't know exist or not.

In general, there are also a lot of weird naming conventions for these addons. With grabbing frames indiscriminately I can just filter by pattern, but searching for a specific name with incrementing indices in multiple locations in the framename would be challenging to generalize for a loop like that across n addons.

For instance, here are three different patterns I currently use:

Lua Code:
  1. ^ElvUF_Raid%d+Group%dUnitButton%d+$ -- ElvUI
  2. ^Vd%dH%d+$ -- VuhDo
  3. ^CellRaidFrameHeader%d+UnitButton%d+$ -- Cell

Each of these increments their digits differently (even sometimes differently from itself based on certain settings within the addon. Grid2 gets pretty wonky with that IIRC). How many times do I increment the first digit in each? The second? Does the third one ever increment or is it always "1"? Questions like these, since the answers change heavily between addons, make it exceedingly difficult to generalize, and also pretty heavily affect the runtime depending on the answers.

I could definitely just set up a separate scan function for every addon, and maybe that's smarter, but it seems less elegant to me. I also suspect there would be quite a few edge cases I miss with that approach since specificity matters. I'll think more on it.

Anyway, thanks for all the help!

Last edited by clicket : 10-05-22 at 07:37 PM.
  Reply With Quote
10-05-22, 07:20 PM   #17
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,323
Any single addon isn't likely to change their naming scheme and it's doubtful people are going to use more than one UnitFrame addon as they are likely to conflict. You can check which is loaded and then scan for frames specific to that addon. Narrowing the scope of the scan in this way reduces the time needed for processing.

You can also cache what addons are loaded when you load and watch ADDON_LOADED if any are loaded after yours. Also, the code I posted, when an addon isn't there or frame hasn't been created yet, will immediately quit on the first check and not waste any further processing.
__________________
WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)
  Reply With Quote
10-05-22, 07:44 PM   #18
clicket
A Deviate Faerie Dragon
 
clicket's Avatar
AddOn Author - Click to view addons
Join Date: Jun 2018
Posts: 12
Originally Posted by SDPhantom View Post
Any single addon isn't likely to change their naming scheme
An example of this is when Grid2 is under default settings, all of its buttons are under Header1 something like Grid2Header1UnitButton1-40. When you enable a setting that forces all groups to show or some such setting, it separates everything into multiple headers, up to 9 I believe. I'd have to always look for header 1-9 and button 1-40, even though there will only ever be 40 buttons. (I could be misremembering, I don't have access to wow atm, but the point should be clear).

It's something like this, that gets caught by a pattern but not a specific search, that makes the specific search worse in my opinion. Because I'm not going to deep-dive into every setting of every single raid frame addon.

Originally Posted by SDPhantom View Post
You can check which is loaded and then scan for frames specific to that addon. Narrowing the scope of the scan in this way reduces the time needed for processing.
Yeah, agreed. I already do this.

Last edited by clicket : 10-05-22 at 08:15 PM.
  Reply With Quote
10-05-22, 08:11 PM   #19
Fizzlemizz
I did that?
 
Fizzlemizz's Avatar
Premium Member
AddOn Author - Click to view addons
Join Date: Dec 2011
Posts: 1,877
The problem is keeping up with new addons of the required type. The list of unit frame addons is not as small, ancient or unchaging as it once was. There's even a second DUF around these days .
__________________
Fizzlemizz
Maintainer of Discord Unit Frames and Discord Art.
Author of FauxMazzle, FauxMazzleHUD and Move Pad Plus.
  Reply With Quote
10-05-22, 08:41 PM   #20
clicket
A Deviate Faerie Dragon
 
clicket's Avatar
AddOn Author - Click to view addons
Join Date: Jun 2018
Posts: 12
Yeah, and with that in mind it's even more appealing to keep things as generalized as possible. I was pretty surprised when I started adding addons to support with just how many there are.
  Reply With Quote

WoWInterface » Developer Discussions » Lua/XML Help » Stopping/Reducing microstutter on large func calls

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off