Minimap Coordinates Memory Usage Optimization - WoWInterface
07-10-19, 08:03 AM   #1
Terenna
A Warpwood Thunder Caller
Join Date: Jun 2016
Posts: 93
Minimap Coordinates Memory Usage Optimization

The following code is used within my personal minimap addon. The code will cause a constant 3 kb/second gain of memory until garbage collection is run, and goes back down quite low. Is there a way to run this and not have constant garbage being built?

Lua Code:
`local x, y, mapPosObject, mapIDtMinimapCoordsFrame:SetScript('OnUpdate', function(self, elapsed)    self.elapsed = self.elapsed + elapsed    while self.elapsed > 0.5 do --only update the coords 2/second        mapID = C_Map.GetBestMapForUnit('player')        if mapID then            mapPosObject = C_Map.GetPlayerMapPosition(mapID, 'player')            if mapPosObject then                x, y = mapPosObject:GetXY()            end            if (x and (x + y > 0)) then --hide if we don't have coords (instances)                tMinimapCoordsFrame.text:SetFormattedText('%.1f, %.1f', x*100, y*100)            else                tMinimapCoordsFrame:Hide()            end        end         self.elapsed = self.elapsed - 0.5    endend)`

07-10-19, 08:48 AM   #2
kurapica.igas
A Flamescale Wyrmkin
Join Date: Aug 2011
Posts: 114
Because the C_Map.GetPlayerMapPosition always create a new table as the result, so the memory usage will constantly increasing.

Here is a solution from other author:

Lua Code:
`local MapRects                  = {}function GetPlayerMapPos()    local mapid                 = C_Map.GetBestMapForUnit('player')    if not mapid then return end     local rects                 = MapRects[mapid]     if not rects then        rects                   = {}        local _, topleft        = C_Map.GetWorldPosFromMapPos(mapid, CreateVector2D(0,0))        local _, bottomright    = C_Map.GetWorldPosFromMapPos(mapid, CreateVector2D(1,1))         bottomright:Subtract(topleft)        rects                   = { topleft.x, topleft.y, bottomright.x, bottomright.y }        MapRects[mapid]         = rects    end     local x, y                  = UnitPosition("player")    if not x then return end     x, y                        = x - rects[1], y - rects[2]     return y / rects[4], x / rects[3]end`

07-10-19, 09:13 AM   #3
Terenna
A Warpwood Thunder Caller
Join Date: Jun 2016
Posts: 93
Absolutely brilliant, thank you so much!

07-10-19, 10:41 AM   #4
jeruku
A Chromatic Dragonspawn

Join Date: Oct 2010
Posts: 198
May have gone a little overboard but this should increase performance significantly.

Edit: Decided to take out the comments after fixing it up; I'm clearly bad at explaining myself.
Lua Code:
`local GetBestMapForUnit = C_Map.GetBestMapForUnit   local MapRects =  {}local mapIDlocal UnitPosition =  UnitPositionlocal GetWorldPosFromMapPos =  C_Map.GetWorldPosFromMapPoslocal CreateVector2D =  CreateVector2D local function GetPlayerMapPosition(mapid, token, rects, _, x, y)    if mapid then        rects = MapRects[mapid]        if not rects then            rects = {}            _, topleft = GetWorldPosFromMapPos(mapid, CreateVector2D(0,0))            _, bottomright = GetWorldPosFromMapPos(mapid, CreateVector2D(1,1))             bottomright:Subtract(topleft)            rects = { topleft.x, topleft.y, bottomright.x, bottomright.y }            MapRects[mapid] = rects        end         x, y = UnitPosition(token)        if x then            x, y = x - rects[1], y - rects[2]            return y / rects[4], x / rects[3]        else            return 0, 0        end    endend local function OnUpdate(self, elapsed, x, y)    if mapID then        x, y = GetPlayerMapPosition(mapID, 'player')        self.text:SetFormattedText('%.1f, %.1f', x*100, y*100)    endendtMinimapCoordsFrame:HookScript('OnUpdate', OnUpdate) tMinimapCoordsFrame:HookScript('OnEvent', function(self, event, ...)    if event == 'PLAYER_STOPPED_MOVING' then        mapID = nil    elseif event ~= 'PLAYER_ENTERING_WORLD' then        mapID = GetBestMapForUnit('player')    else        mapID = GetBestMapForUnit('player')        OnUpdate(self)        self:UnregisterEvent('PLAYER_ENTERING_WORLD')    endend)tMinimapCoordsFrame:RegisterEvent('ZONE_CHANGED')tMinimapCoordsFrame:RegisterEvent('ZONE_CHANGED_INDOORS')tMinimapCoordsFrame:RegisterEvent('ZONE_CHANGED_NEW_AREA')tMinimapCoordsFrame:RegisterEvent('PLAYER_ENTERING_WORLD')tMinimapCoordsFrame:RegisterEvent('PLAYER_STOPPED_MOVING')tMinimapCoordsFrame:RegisterEvent('PLAYER_STARTED_MOVING')`
__________________
"I have not failed, I simply found 10,000 ways that did not work." - Thomas Edison

Last edited by jeruku : 07-10-19 at 08:59 PM. Reason: Okay, totally fixed... hopefully.

07-10-19, 02:06 PM   #5
SDPhantom
A Pyroguard Emberseer

Join Date: Jul 2006
Posts: 1,821
 Originally Posted by jeruku Lua Code: `-- Move global functions into the frames table; you could also make them locals, like below, but I prefer this method.--     local GetWorldPosFromMapPos = C_Map.GetWorldPosFromMapPostMinimapCoordsFrame.MapRects =  {}tMinimapCoordsFrame.UnitPosition =  UnitPositiontMinimapCoordsFrame.GetWorldPosFromMapPos =  C_Map.GetWorldPosFromMapPostMinimapCoordsFrame.CreateVector2D =  CreateVector2D`
Moving local references to globals into a table defeats the purpose of making them in the first place, which is to save on indexing operations. You run the same number of these operations pulling them from your table as you would calling them as globals.

 Originally Posted by jeruku Lua Code: `--    I dislike strings in OnUpdate; again, personal preference. tMinimapCoordsFrame.FromattedString = '%.1f, %.1f'tMinimapCoordsFrame.PlayerToken = 'player'`
These actually create indexing operations in the following OnUpdate script as opposed to them being stored as a part of the function binary as a constant. All literal values are constants referred to when used in an expression. In the long run, this actually slows down the function instead of offering any performance boost.

 Originally Posted by jeruku Lua Code: `local function Blank_OnUpdate() endlocal OnUpdate = Blank_OnUpdate tMinimapCoordsFrame:HookScript('OnUpdate', OnUpdate) --  Getting the mapID every OnUpdate is rather inefficient, get it when it changes/loadslocal GetBestMapForUnit = C_Map.GetBestMapForUnittMinimapCoordsFrame:HookScript('OnEvent', function(self, event, ...)    if event == 'PLAYER_STARTED_MOVING' then --  Self explanatory event        OnUpdate = Do_OnUpdate    elseif event == 'PLAYER_STOPPED_MOVING' then        OnUpdate = Blank_OnUpdate    elseif event == 'ZONE_CHANGED' or event == 'ZONE_CHANGED_INDOORS' or event == 'ZONE_CHANGED_NEW_AREA' or event == 'PLAYER_ENTERING_WORLD'  then        self.mapID = GetBestMapForUnit('player')                Do_OnUpdate(self, self.mapID)    endend)`
This way of swapping OnUpdate scripts doesn't work at all. It doesn't magically tell the function to watch OnUpdate for changes, it just ends up grabbing Blank_OnUpdate() on initialization and further changes in the event script does nothing. Since the OP used :SetScript() to assign the function in the first place, it's safe to assume you can run :SetScript("OnUpdate",nil) to clear and set it again when necessary. Otherwise, you can set an upvalue to flag and have the script check it when it's to run.
__________________
 "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 : 07-10-19 at 02:32 PM.

07-10-19, 02:36 PM   #6
Terenna
A Warpwood Thunder Caller
Join Date: Jun 2016
Posts: 93
I've been coding my UI casually for ~ 6 years. Turns out I'm still garbage at it compared to you all. It's incredible how absolutely complex you can get to squeeze every inch out of CPU/RAM if you sit down and really go for it.

07-10-19, 03:10 PM   #7
SDPhantom
A Pyroguard Emberseer

Join Date: Jul 2006
Posts: 1,821
It's the nature of being in a community. You can pick up things from others you might have never come across on your own.
__________________
 "All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools." -Anders (Dragon Age: Origins - Awakening)

07-10-19, 08:56 PM   #8
jeruku
A Chromatic Dragonspawn

Join Date: Oct 2010
Posts: 198
 Originally Posted by SDPhantom Moving local references to globals into a table defeats the purpose of making them in the first place, which is to save on indexing operations. You run the same number of these operations pulling them from your table as you would calling them as globals.
Think there was a misunderstanding, it's not an upvalue but rather the global itself. If you wanna get super technical it would be the same as sometable.UnitPosition = _G["UnitPosition"].

And to my knowledge a table lookup inside a function from a passed variable(self or vararg) is faster than calling a variable from outside of scope. In the cases where you're dealing with a single frame it takes up the same amount of space and is only slightly faster; if you were using OnUpdate on something like hundreds/thousands of frames it would be a trade off between CPU vs RAM depending on the number/type of things added to, or used from, the table. Sadly I'm used to doing it this way and it's certainly a bad habit.

 Originally Posted by SDPhantom All literal values are constants referred to when used in an expression.
I don't always remember things correctly and again shoving everything into the table is a bad habit.

 Originally Posted by SDPhantom This way of swapping OnUpdate scripts doesn't work at all.
Thanks for pointing that out, that was my bad and I have no excuse. I have absolutely no idea why I thought that would work. I corrected my post and will correct it further.
__________________
"I have not failed, I simply found 10,000 ways that did not work." - Thomas Edison

07-11-19, 08:59 AM   #9
Terenna
A Warpwood Thunder Caller
Join Date: Jun 2016
Posts: 93
I modified it ever so slightly to this:
Lua Code:
`local GetBestMapForUnit = C_Map.GetBestMapForUnit   local MapRects = {}local mapIDlocal UnitPosition = UnitPositionlocal GetWorldPosFromMapPos = C_Map.GetWorldPosFromMapPoslocal CreateVector2D = CreateVector2D local function GetPlayerMapPosition(mapid, token, rects, _, x, y)    x, y = UnitPosition(token)    if not x then return end    if mapid then        rects = MapRects[mapid]        if not rects then            rects = {}            _, topleft = GetWorldPosFromMapPos(mapid, CreateVector2D(0,0))            _, bottomright = GetWorldPosFromMapPos(mapid, CreateVector2D(1,1))             bottomright:Subtract(topleft)            rects = { topleft.x, topleft.y, bottomright.x, bottomright.y }            MapRects[mapid] = rects        end                x, y = x - rects[1], y - rects[2]        return y / rects[4], x / rects[3]    endend local function OnUpdate(self, elapsed, x, y)    self.elapsed = self.elapsed + elapsed    while self.elapsed > 0.5 do --only update the coords 2/second        if mapID then            x, y = GetPlayerMapPosition(mapID, 'player')            if x then                self.text:SetFormattedText('%.1f, %.1f', x * 100, y * 100)            else                self.text:SetText('')            end        end         self.elapsed = self.elapsed - 0.5    endend tMinimapCoordsFrame:SetScript('OnEvent', function(self, event, ...)    if event == 'PLAYER_STOPPED_MOVING' then        mapID = nil        tMinimapCoordsFrame:SetScript('OnUpdate', nil)    elseif event == 'PLAYER_STARTED_MOVING' then        mapID = GetBestMapForUnit('player')        tMinimapCoordsFrame:SetScript('OnUpdate', OnUpdate)    elseif event == 'ZONE_CHANGED_NEW_AREA' then        if IsInInstance() then            self:UnregisterEvent('PLAYER_STARTED_MOVING')            self:UnregisterEvent('PLAYER_STOPPED_MOVING')            self:UnregisterEvent('ZONE_CHANGED')            self:UnregisterEvent('ZONE_CHANGED_INDOORS')        else            self:RegisterEvent('PLAYER_STOPPED_MOVING')            self:RegisterEvent('PLAYER_STARTED_MOVING')            self:RegisterEvent('ZONE_CHANGED')            self:RegisterEvent('ZONE_CHANGED_INDOORS')        end    else        mapID = GetBestMapForUnit('player')        OnUpdate(self, 1) --force it to update the coords exactly once as we log in        self:UnregisterEvent('PLAYER_ENTERING_WORLD')        if IsInInstance() then            self:UnregisterEvent('PLAYER_STARTED_MOVING')            self:UnregisterEvent('PLAYER_STOPPED_MOVING')            self:UnregisterEvent('ZONE_CHANGED')            self:UnregisterEvent('ZONE_CHANGED_INDOORS')        end    endend)`

I reworked the OnEvent handler to unregister and register the movement and zone changes if the player is in an instance so it won't be running in there where there are no coordinates.

Last edited by Terenna : 07-11-19 at 09:13 AM.

07-11-19, 11:46 AM   #10
Lybrial
Join Date: Jan 2010
Posts: 66
I use AceTimer-3.0 for updating my coords frame:

Lua Code:
`local CORE, LIBS = LybrialUI:GetCore();local LOCALE = LIBS.LOCALE; local Texts = LybrialMiniMap:NewModule("Texts", "AceTimer-3.0"); Texts.displayName = LOCALE["TEXTS"];Texts.desc = LOCALE["TEXTS_DESC"];Texts.zone = nil;Texts.coords = nil;Texts.date = nil;Texts.clock = nil; local _G = _G;local C_Map = C_Map;local CreateFrame = CreateFrame;local CreateVector2D = CreateVector2D;local GetMinimapZoneText = GetMinimapZoneText;local Minimap = Minimap;local UnitPosition = UnitPosition;local strsub = strsub;local unpack = unpack; function Texts:OnEnableCoords()    if (not self.coords) then        self.coords = CreateFrame("Frame", "LybrialUITextsCoords", Minimap);        self.coords.text = self.coords:CreateFontString(nil, "OVERLAY");        self.coords.MapRects = {};        self.coords:SetScript("OnEvent", function(self)            self.mapId = C_Map.GetBestMapForUnit("player");        end);        self.coords:RegisterEvent("PLAYER_ENTERING_WORLD");        self.coords:RegisterEvent("ZONE_CHANGED");        self.coords:RegisterEvent("ZONE_CHANGED_INDOORS");        self.coords:RegisterEvent("ZONE_CHANGED_NEW_AREA");        self:ScheduleRepeatingTimer(self.UpdatePlayerMapPosition, 1);    endend function Texts:OnUpdateCoords()    local show = self.db.profile.texts.coords.show;    local anchor = self.db.profile.texts.coords.position.anchor;    local offsetX = self.db.profile.texts.coords.position.offset.x;    local offsetY = self.db.profile.texts.coords.position.offset.y;    local font = self.db.profile.texts.coords.font.font;    local fontSize = self.db.profile.texts.coords.font.size;    local fontOutline = self.db.profile.texts.coords.font.outline;    local fontColor = self.db.profile.texts.coords.font.color;     self.coords:SetSize(120, 30);    self.coords:ClearAllPoints();    self.coords:SetPoint(anchor, Minimap, anchor, offsetX, offsetY);    self.coords.text:ClearAllPoints();    self.coords.text:SetPoint("CENTER");    self.coords.text:SetFont(LIBS.LSM:Fetch("font", font), fontSize, fontOutline);    self.coords.text:SetTextColor(unpack(fontColor));     if (show) then        self.coords:Show();    else        self.coords:Hide();    endend function Texts:UpdatePlayerMapPosition()    local coords = Texts.coords;     if (not coords:IsShown()) then        return ;    end     if (not coords.mapId) then        return ;    end     local rects = coords.MapRects[coords.mapId];     if (not rects) then        rects = {};         local _, topLeft = C_Map.GetWorldPosFromMapPos(coords.mapId, CreateVector2D(0, 0));        local _, bottomRight = C_Map.GetWorldPosFromMapPos(coords.mapId, CreateVector2D(1, 1));         bottomRight:Subtract(topLeft);         rects = { topLeft.x, topLeft.y, bottomRight.x, bottomRight.y };         coords.MapRects[coords.mapId] = rects;    end     local x, y = UnitPosition("player");     if (x) then        x = x - rects[1];        y = y - rects[2];        x = x / rects[3];        y = y / rects[4];        y = y * 100;        x = x * 100;         coords.text:SetFormattedText("%.1f, %.1f", y, x);    endend`

07-11-19, 04:42 PM   #11
Kanegasi
An Onyxian Warder

Join Date: Apr 2007
Posts: 374
 Originally Posted by Terenna I've been coding my UI casually for ~ 6 years. Turns out I'm still garbage at it compared to you all. It's incredible how absolutely complex you can get to squeeze every inch out of CPU/RAM if you sit down and really go for it.
I feel it's important to point out that RAM usage is not a performance issue, except for leak cases such as this thread you created or playing WoW with less than 8GB of RAM. Many addons, "Details" being a great example, save on a lot of CPU by caching as much info as possible.

CPU will always be WoW's bottleneck. Do not be afraid to cache as much data as possible instead of querying the API. The less you do each frame, especially in combat, the better.

07-12-19, 10:40 AM   #12
kurapica.igas
A Flamescale Wyrmkin
Join Date: Aug 2011
Posts: 114
 Originally Posted by Kanegasi I feel it's important to point out that RAM usage is not a performance issue, except for leak cases such as this thread you created or playing WoW with less than 8GB of RAM. Many addons, "Details" being a great example, save on a lot of CPU by caching as much info as possible. CPU will always be WoW's bottleneck. Do not be afraid to cache as much data as possible instead of querying the API. The less you do each frame, especially in combat, the better.
In my addon Scorpio, I try to solve the performance problem not by less operations, but with a task schedule system to smoothing the operations like

Lua Code:
`Scorpio "TestBigLoop" "" __Async__()function LongLoop()    local time = GetTime()    local prev = 0     for i = 1, 10^7 do        if i%10 == 0 then            Continue() -- The frame will freeze if miss this             if time ~= GetTime() then                -- Means the thread is resumed in the next frame through OnUpdate                time = GetTime()                 -- Here is the current time and the cycle count of the previous phase                print(time, i - prev)                prev = i            end        end    endend LongLoop()`

The function LongLoop will be processed in a coroutine, and the Continue method will yield it if there is no more time for tasks in the same frame and resume it in the next frame if there is enough time.

This is how the script runs in my old laptop(i5-3230M), I changed a setting to keep the fps to 60, so for one frame the loop will be processed for 2k6-4k times, with the task schedule system I can smooth all my addons to keep the high fps(for my old hardware).

Besides the Continue, there are many others like Wait to wait for several system events, so for me, I can re-code Terenna's code like :

Lua Code:
`Scorpio "ShowPosition" "" tCoordsFrame = UIParent:CreateFontString(nil, "OVERLAY", "GameFontNormal")tCoordsFrame:SetPoint("CENTER") __Async__()function RefreshPosition()    local GetBestMapForUnit     = C_Map.GetBestMapForUnit       local MapRects              = {}    local UnitPosition          = UnitPosition    local GetWorldPosFromMapPos = C_Map.GetWorldPosFromMapPos    local CreateVector2D        = CreateVector2D     while true do        if IsPlayerMoving() then            local mapID         = GetBestMapForUnit('player')             local x, y          = UnitPosition('player')            if x and mapID then                local rects     = MapRects[mapID]                if not rects then                    local _, topleft        = GetWorldPosFromMapPos(mapID, CreateVector2D(0,0))                    local _, bottomright    = GetWorldPosFromMapPos(mapID, CreateVector2D(1,1))                             bottomright:Subtract(topleft)                    rects = { topleft.x, topleft.y, bottomright.x, bottomright.y }                    MapRects[mapID]         = rects                end                                x, y = x - rects[1], y - rects[2]                x, y = y / rects[4], x / rects[3]                 tCoordsFrame:SetFormattedText('%.1f, %.1f', x * 100, y * 100)                 Delay(0.5)   -- wait 0.5 sec            else                tCoordsFrame:SetText("")                -- wait player change zone                Wait("PLAYER_ENTERING_WORLD", "ZONE_CHANGED", "ZONE_CHANGED_INDOORS")            end        else            Wait("PLAYER_STARTED_MOVING") -- wait player move        end    endend RefreshPosition()`

Although I need more codes(include the framework) to do the same operations, we still can gain better performance through carefully coroutine controls.

Last edited by kurapica.igas : 07-12-19 at 10:44 AM.

07-21-19, 06:58 AM   #13
wildcard25
A Fallenroot Satyr

Join Date: Jan 2012
Posts: 27
 Originally Posted by Terenna Lua Code: `local function OnUpdate(self, elapsed, x, y)    self.elapsed = self.elapsed + elapsed    while self.elapsed > 0.5 do --only update the coords 2/second        if mapID then            x, y = GetPlayerMapPosition(mapID, 'player')            if x then                self.text:SetFormattedText('%.1f, %.1f', x * 100, y * 100)            else                self.text:SetText('')            end        end         self.elapsed = self.elapsed - 0.5    endend`
I'd probably only update the co-ordinates once in the OnUpdate function, then wait for at least another half a second before doing it again. Currently this keeps recalculating the same thing until it gets the elapsed time under half a second. If the pc is already under enough load that more than 1 second has passed between frames, then this would just be adding to the work load.
Lua Code:
`local function OnUpdate(self, elapsed, x, y)    self.elapsed = (self.elapsed or 0) + elapsed    if self.elapsed > 0.5 then --only update the coords 2/second        if mapID then            x, y = GetPlayerMapPosition(mapID, 'player')            if x then                self.text:SetFormattedText('%.1f, %.1f', x * 100, y * 100)            else                self.text:SetText('')            end        end         self.elapsed = 0    endend`

 WoWInterface » Minimap Coordinates Memory Usage Optimization

 Thread Tools Display Modes Linear Mode

 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