Thread Tools Display Modes
05-08-15, 04:06 AM   #1
Mayron
A Frostmaul Preserver
 
Mayron's Avatar
AddOn Author - Click to view addons
Join Date: Jan 2010
Posts: 275
new Cast Bar AddOn stacking high on memory

Hi, I have been making a new cast bar mod and it seems to work fine but the memory consumption over time gets very high. It starts at 8kb in AddOn memory but when spamming casts such as at a Training dummy or healing myself, it keeps going up and eventually goes over 2MB - 3MB or even higher.

Is this normal for cast bars? I've been trying to analyse my code to find out the source of this. I thought maybe it was recursion of C_Timer.After calls but after trying to fix this, I don't think it is.

I can post the code here but its long so if you want me to upload it somewhere I can do. I've also tried using a do-end block where the "constructor" is but I wasn't sure why the for loop couldn't call the reference to the function if I used it so I commented it out

Lua Code:
  1. local castbar_prototype = {}
  2.  
  3. -- Returns whether a cast bar has finished casting
  4. function castbar_prototype:IsFinished()
  5.     return self:GetValue() >= select(2, self:GetMinMaxValues());
  6. end
  7.  
  8. local pause = 0;
  9. function castbar_prototype:FadeOut()
  10.     local alpha = self:GetAlpha()
  11.     if (not self.startTime and alpha > 0) then
  12.         pause = pause + 0.1
  13.         if (pause >= 4) then
  14.             self:SetAlpha(alpha - 0.03)
  15.         end
  16.        
  17.         C_Timer.After(0.01, function() self:FadeOut() end) 
  18.     else       
  19.         pause = 0;
  20.         self.fadingOut = nil
  21.     end
  22. end
  23.  
  24. function castbar_prototype:StopCasting()
  25.     if (self.fadingOut) then return end
  26.     self.fadingOut = true
  27.     self.startTime = nil;
  28.     self.unitName = nil;
  29.     self:SetStatusBarColor(0, 1, 1) -- SUCCESSFUL
  30.     self:FadeOut() 
  31. end
  32.  
  33. function castbar_prototype:StartCasting()  
  34.     local name, _, text, texture, startTime, endTime, isTradeSkill, _, notInterruptible = UnitCastingInfo(self.unitID)
  35.     if (not startTime) then return end
  36.    
  37.     self.unitName = UnitName(self.unitID);
  38.    
  39.     startTime = startTime / 1000; -- To make the same as GetTime() format
  40.     endTime = endTime / 1000; -- To make the same as GetTime() format
  41.    
  42.     self.startTime = startTime;
  43.     self.totalTime = endTime - startTime;
  44.    
  45.     self:SetMinMaxValues(0, endTime - startTime) -- 0 to n seconds 
  46.     self.castingText:SetText(name)
  47.     self.icon:SetTexture(texture)
  48.     self.icon:SetTexCoord(0.13, 0.87, 0.13, 0.87)
  49.    
  50.     if (notInterruptible) then
  51.         self:SetStatusBarColor(1, 0, 0)
  52.     else
  53.         self:SetStatusBarColor(0, 1, 0)
  54.     end
  55.    
  56.     self:SetValue(0)
  57.     self:SetAlpha(1)
  58.     if (not self.updateRunning) then self:UpdateCasting(); end
  59. end
  60.  
  61. function castbar_prototype:UpdateCasting()
  62.     -- if not finished then continue updating
  63.     if (self.startTime and not self:IsFinished()) then
  64.         local currentTime = GetTime() - self.startTime
  65.         self:SetValue(currentTime)
  66.        
  67.         local timeRemaining = self.totalTime - currentTime;
  68.         if (timeRemaining < 0) then timeRemaining = 0.0 end
  69.         self.timeRemainingText:SetText(("%.1f"):format(tostring(timeRemaining)))
  70.        
  71.         self.updateRunning = true
  72.         C_Timer.After(0.01, function() self:UpdateCasting() end)       
  73.     else
  74.         self.updateRunning = nil;
  75.         self:StopCasting();
  76.     end
  77. end
  78.  
  79. function castbar_prototype:UNIT_SPELLCAST_INTERRUPTED(unitID, ...)
  80.     self:SetValue(select(2, self:GetMinMaxValues()))   
  81.     self:StopCasting();
  82.     self:SetStatusBarColor(1, 0, 0) -- FAILED
  83. end
  84.  
  85. function castbar_prototype:UNIT_SPELLCAST_INTERRUPTIBLE(unitID, ...)
  86.     self:SetStatusBarColor(0, 1, 0)
  87. end
  88.  
  89. function castbar_prototype:UNIT_SPELLCAST_NOT_INTERRUPTIBLE(unitID, ...)
  90.     self:SetStatusBarColor(1, 0, 0)
  91. end
  92.  
  93. function castbar_prototype:PLAYER_TARGET_CHANGED()
  94.     if (UnitExists(self.unitID) and select(1, UnitCastingInfo(self.unitID))) then
  95.         if (UnitName(self.unitID) == self.unitName) then return end
  96.         self:StopCasting()
  97.         self:StartCasting()
  98.     elseif (self:GetAlpha() > 0) then
  99.         self:StopCasting()
  100.         self:SetAlpha(0)
  101.     end
  102. end
  103.  
  104. function castbar_prototype:UNIT_SPELLCAST_DELAYED(unitID, ...)
  105.     local name, subText, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible = UnitCastingInfo(self.unitID)
  106.     if (not endTime or not self.startTime) then
  107.         self:StopCasting()
  108.         return
  109.     end
  110.     endTime = endTime / 1000;
  111.     self:SetMinMaxValues(0, endTime - self.startTime)
  112. end
  113.  
  114. function castbar_prototype:UNIT_SPELLCAST_START(...)
  115.     local unitID, spell, rank, lineID, spellID = ...;
  116.     if (unitID ~= self.unitID) then return end -- wrong unit type  
  117.     -- start casting:
  118.     self:StartCasting()
  119. end
  120.    
  121. --local CreateCastBar; 
  122. --do
  123.     local backdrop = {
  124.         bgFile = "Interface\\AddOns\\MayronUI\\Media\\StatusBar\\MUI_StatusBar",
  125.         edgeFile = "Interface\\AddOns\\MayronUI\\Media\\Borders\\Skinner",
  126.         edgeSize = 1,
  127.     }
  128.    
  129.     local defaults = {
  130.         player = {
  131.             x = -200, y = -300,
  132.             width = 200, height = 30,
  133.         }
  134.         target = {
  135.             x = 200, y = -300,
  136.             width = 200, height = 30,
  137.         },
  138.         focus = {
  139.             x = -200, y = 100,
  140.             width = 200, height = 30,
  141.         },
  142.     }
  143.        
  144.     -- Constructor
  145.     local function CreateCastBar(unitID)
  146.         local s = defaults[unitID];
  147.        
  148.         -- Set up StatusBar:
  149.         local bar = CreateFrame("StatusBar", "MUI_"..unitID:gsub("^%l", string.upper).."CastBar", UIParent)
  150.        
  151.         -- Appearance properties:
  152.         bar:SetStatusBarTexture("Interface\\AddOns\\MayronUI\\Media\\StatusBar\\MUI_StatusBar")
  153.         bar:SetValue(0)
  154.         bar:SetSize(s.width, s.height)
  155.         bar:SetPoint("CENTER", UIParent, "CENTER", s.x, s.y)
  156.         bar:SetAlpha(0)
  157.         bar:SetFrameLevel(10)
  158.        
  159.         local bg = CreateFrame("Frame", nil, bar)
  160.         bg:SetPoint("TOPLEFT", bar, "TOPLEFT", -1, 1)
  161.         bg:SetPoint("BOTTOMRIGHT", bar, "BOTTOMRIGHT", 1, -1)
  162.         bg:SetBackdrop(backdrop);
  163.         bg:SetBackdropBorderColor(0, 0, 0, 1)
  164.         bg:SetBackdropColor(0, 0, 0, 0.4)
  165.         bg:SetFrameLevel(5)
  166.        
  167.         bar.unitID = unitID;
  168.         for key, func in pairs(castbar_prototype) do
  169.             bar[key] = func;
  170.         end
  171.        
  172.         -- FontStrings:
  173.         bar.castingText = bar:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  174.         bar.castingText:SetPoint("LEFT", bar, "LEFT", 4, 0)
  175.         bar.castingText:SetWidth(150)
  176.         bar.castingText:SetWordWrap(false)
  177.         bar.castingText:SetJustifyH("LEFT")
  178.         bar.timeRemainingText = bar:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  179.         bar.timeRemainingText:SetPoint("RIGHT", bar, "RIGHT", -4, 0)
  180.         bar.timeRemainingText:SetJustifyH("RIGHT")
  181.        
  182.         -- Icon:
  183.         local iconFrame = CreateFrame("Frame", nil, bar)
  184.         iconFrame:SetPoint("TOPRIGHT", bar, "TOPLEFT", -2, 1)  
  185.         iconFrame:SetPoint("BOTTOMLEFT", bar, "BOTTOMLEFT", -(bar:GetHeight() + 4), -1)
  186.         iconFrame:SetBackdrop(backdrop)
  187.         iconFrame:SetBackdropBorderColor(0, 0, 0, 1)
  188.         iconFrame:SetBackdropColor(0, 0, 0, 0.2)
  189.        
  190.         bar.icon = iconFrame:CreateTexture(nil, "ARTWORK")
  191.         bar.icon:SetPoint("TOPLEFT", iconFrame, "TOPLEFT", 1, -1)
  192.         bar.icon:SetPoint("BOTTOMRIGHT", iconFrame, "BOTTOMRIGHT", -1, 1)
  193.        
  194.         -- events:
  195.         bar:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTED", unitID);
  196.         bar:RegisterUnitEvent("UNIT_SPELLCAST_DELAYED", unitID);
  197.         bar:RegisterUnitEvent("UNIT_SPELLCAST_START", unitID);
  198.         bar:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTIBLE", unitID);
  199.         bar:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", unitID);
  200.        
  201.         if (unitID == "target") then
  202.             bar:RegisterEvent("PLAYER_TARGET_CHANGED");
  203.         elseif (unitID == "focus") then
  204.             bar:RegisterEvent("PLAYER_FOCUS_CHANGED");
  205.         end
  206.        
  207.         bar:SetScript("OnEvent", function(self, event, ...)
  208.             if (event == "PLAYER_FOCUS_CHANGED") then
  209.                 event = "PLAYER_TARGET_CHANGED";
  210.             end
  211.             self[event](self, ...);
  212.         end)
  213.        
  214.         return bar;
  215.     end
  216. --end
  217.  
  218. -- Block Blizzard's Cast Bar:
  219. CastingBarFrame:UnregisterAllEvents();
  220. CastingBarFrame:Hide();
  221.  
  222. for unitID, tbl in pairs(defaults) do
  223.     CreateCastBar(unitID)
  224. end

If anyone wants a challenge to look into this that would be brilliant! I still have a lot of work to do with adding a latency bar etc.. but for now just want to get the basics fixed so its more efficient.

Thank you!

Also I called the template the "prototype" but I don't use metatables since I wanted to add the inherited functions to a widget and didn't want to mess with the widget's metatable (StatusBar). I hope it doesn't make the code confusing :S

Last edited by Mayron : 05-08-15 at 04:09 AM.
  Reply With Quote
05-08-15, 04:36 AM   #2
Lombra
A Molten Giant
 
Lombra's Avatar
AddOn Author - Click to view addons
Join Date: Nov 2006
Posts: 554
Code:
C_Timer.After(0.01, function() self:FadeOut() end)
Code:
C_Timer.After(0.01, function() self:UpdateCasting() end)
These create a new function every time. You'll definitely want to look into that, and you should absolutely avoid doing that even if it doesn't turn out to be the issue.

I would say just use OnUpdate for the UpdateCasting bit and probably an animation for FadeOut. There's nothing inherently bad with OnUpdate, not at all. The new C_Timer functions I think are mostly suitable when the actual time is the significant variable.
__________________
Grab your sword and fight the Horde!
  Reply With Quote
05-08-15, 06:03 AM   #3
Mayron
A Frostmaul Preserver
 
Mayron's Avatar
AddOn Author - Click to view addons
Join Date: Jan 2010
Posts: 275
Originally Posted by Lombra View Post
Code:
C_Timer.After(0.01, function() self:FadeOut() end)
Code:
C_Timer.After(0.01, function() self:UpdateCasting() end)
These create a new function every time. You'll definitely want to look into that, and you should absolutely avoid doing that even if it doesn't turn out to be the issue.

I would say just use OnUpdate for the UpdateCasting bit and probably an animation for FadeOut. There's nothing inherently bad with OnUpdate, not at all. The new C_Timer functions I think are mostly suitable when the actual time is the significant variable.
Thanks for that. That makes a lot of sense. Even if I had something like:

Lua Code:
  1. local function someFunc()
  2.     self:UpdateCasting()
  3. end

and used

Lua Code:
  1. C_Timer.After(0.01, someFunc)

instead..

Would this still be a big problem? Since it is no longer creating a new function each time but I was worried this would cause some sort of large recursive depth which would cause a new function to be opened inside of previous ones causing large memory usage. (not too sure about how Lua handles these types of things)

But I completely agree that using an Animation Widget is much more practical. I just thought C_Timer.After was more efficient compared to OnUpdate from what I read about it a long time ago.

Last edited by Mayron : 05-08-15 at 06:05 AM.
  Reply With Quote
05-08-15, 09:01 AM   #4
jeruku
A Cobalt Mageweaver
 
jeruku's Avatar
AddOn Author - Click to view addons
Join Date: Oct 2010
Posts: 223
Originally Posted by Mayron View Post
...
Would this still be a big problem? Since it is no longer creating a new function each time but I was worried this would cause some sort of large recursive depth which would cause a new function to be opened inside of previous ones causing large memory usage. (not too sure about how Lua handles these types of things)

But I completely agree that using an Animation Widget is much more practical. I just thought C_Timer.After was more efficient compared to OnUpdate from what I read about it a long time ago.
No, that should remedy the problem.

Yes and no. In this case it could be just as efficient to use C_Timer.NewTicker, an OnUpdate hook, or an animation group. OnUpdate is only bad when used incorrectly like iterating over large tables or doing large operations on each frame.
__________________
"I have not failed, I simply found 10,000 ways that did not work." - Thomas Edison
  Reply With Quote
05-08-15, 10:40 AM   #5
Lombra
A Molten Giant
 
Lombra's Avatar
AddOn Author - Click to view addons
Join Date: Nov 2006
Posts: 554
Originally Posted by Mayron View Post
Thanks for that. That makes a lot of sense. Even if I had something like:

Lua Code:
  1. local function someFunc()
  2.     self:UpdateCasting()
  3. end

and used

Lua Code:
  1. C_Timer.After(0.01, someFunc)

instead..

Would this still be a big problem?
That should be perfectly fine as far as memory usage goes, but you have the problem of getting the self in there, then.

C_Timer is probably better in certain scenarios, but I don't know that it will be better here. You're effectively using it as one would OnUpdate, running a script very frequently, and that script on its own is going to be the processing heavy bit as opposed to using OnUpdate vs C_Timer making a real difference. If you want to do something like "say hi after 3 seconds", then C_Timer is probably better, because the actual timer will run in C or some other lower level thing, rather than the logic being handled by Lua in a constantly executing script.
__________________
Grab your sword and fight the Horde!
  Reply With Quote
05-08-15, 12:56 PM   #6
Mayron
A Frostmaul Preserver
 
Mayron's Avatar
AddOn Author - Click to view addons
Join Date: Jan 2010
Posts: 275
Ah okay, thanks for clearing that up.

I ended up using OnUpdate because it was more convenient of a solution and used UIFrameFadeIn and UIFrameFadeOut functions rather than an animation since it means less code and I just wanted a simple fading effect. It saved me a lot of work. The memory usage is 139kb after a full session of casting till my mana was gone which I'm satisfied with.

Thanks again!
  Reply With Quote
05-09-15, 03:32 AM   #7
Lombra
A Molten Giant
 
Lombra's Avatar
AddOn Author - Click to view addons
Join Date: Nov 2006
Posts: 554
Sounds good! UIFrameFadeIn and UIFrameFadeOut used to taint the talent frame (switching talents) or something though, no idea whether it still does. Might want to look into that. Have the functions run before you open the talent frame.
__________________
Grab your sword and fight the Horde!
  Reply With Quote
05-11-15, 04:39 PM   #8
Mayron
A Frostmaul Preserver
 
Mayron's Avatar
AddOn Author - Click to view addons
Join Date: Jan 2010
Posts: 275
Originally Posted by Lombra View Post
Sounds good! UIFrameFadeIn and UIFrameFadeOut used to taint the talent frame (switching talents) or something though, no idea whether it still does. Might want to look into that. Have the functions run before you open the talent frame.
Thanks for the heads up but nope it all seems to be working perfectly. Testing it thoroughly with no tainting issues
  Reply With Quote

WoWInterface » Developer Discussions » Lua/XML Help » new Cast Bar AddOn stacking high on memory

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