WoWInterface

WoWInterface (https://www.wowinterface.com/forums/index.php)
-   PTR API and Graphics Changes (https://www.wowinterface.com/forums/forumdisplay.php?f=175)
-   -   C_Map.GetPlayerMapPosition Memory Usage (https://www.wowinterface.com/forums/showthread.php?t=56290)

thomasjohnshannon 06-15-18 05:39 PM

C_Map.GetPlayerMapPosition Memory Usage
 
Anyone else having memory issues with C_Map.GetPlayerMapPosition? I updated some map coords code to use the new functions and noticed that the addons memory usage grows every update. By commenting out other code I was able to narrow the problem down to this function so I know it is the problem. I had it setup like this.

Lua Code:
  1. local mapID = C_Map.GetBestMapForUnit("player")
  2. local px, py = C_Map.GetPlayerMapPosition(mapID, "player"):GetXY()

and also tried this.

Lua Code:
  1. local px, py = C_Map.GetPlayerMapPosition(C_Map.GetBestMapForUnit("player"), "player"):GetXY()

I also tried moving the variables outside of the function but nothing changed. Anyone know how to fix this. The addon worked with no issues with the old functions on live.

SDPhantom 06-15-18 05:53 PM

It's just how Lua handles tables, and it's been a constant issue I've had with Blizzard's trend with their APIs lately. Every time an API is called that returns a table, it takes up a small chunk of memory. This can add up quickly, especially in combat log events and OnUpdate. It doesn't matter if you assign it to a variable or not, just the act of calling the function does this.

Specificly, C_Map.GetPlayerMapPosition() is generating this new table every time it's called. The memory eventually gets freed when Lua determines it needs to, but it's bad practice to rely on its garbage collection as it can cause random lag spikes.

elcius 06-15-18 09:38 PM

Lua Code:
  1. local MapRects = {};
  2. local TempVec2D = CreateVector2D(0,0);
  3. function GetPlayerMapPos(MapID)
  4.     local R,P,_ = MapRects[MapID],TempVec2D;
  5.     if not R then
  6.         R = {};
  7.         _, R[1] = C_Map.GetWorldPosFromMapPos(MapID,CreateVector2D(0,0));
  8.         _, R[2] = C_Map.GetWorldPosFromMapPos(MapID,CreateVector2D(1,1));
  9.         R[2]:Subtract(R[1]);
  10.         MapRects[MapID] = R;
  11.     end
  12.     P.x, P.y = UnitPosition('Player');
  13.     P:Subtract(R[1]);
  14.     return (1/R[2].y)*P.y, (1/R[2].x)*P.x;
  15. end

siweia 06-17-18 12:40 PM

Code:

        local mapID, position
        local formatText = ": %.1f, %.1f"

        hooksecurefunc(WorldMapFrame, "OnMapChanged", function(self)
                mapID = self:GetMapID()
        end)

        local function CoordsUpdate(player, cursor)
                local cx, cy = CursorCoords()
                if cx and cy then
                        cursor:SetFormattedText(MOUSE_LABEL..formatText, 100 * cx, 100 * cy)
                else
                        cursor:SetText(MOUSE_LABEL..": --, --")
                end

                position = C_Map.GetPlayerMapPosition(mapID, "player")
                if not position or (position.x == 0 and position.y == 0) then
                        player:SetText(PLAYER..": --, --")
                else
                        player:SetFormattedText(PLAYER..formatText, 100 * position.x, 100 * position.y)
                        wipe(position)
                end
        end


Rilgamon 06-17-18 01:02 PM

Lua Code:
  1. local mapID = C_Map.GetBestMapForUnit("player")
  2. local px, py = C_Map.GetPlayerMapPosition(mapID, "player"):GetXY()
When you use it that way make sure that mapID has a value.
mapID can be undefined while using portals.

VincentSDSH 06-17-18 03:41 PM

Lua Code:
  1. local px, py = C_Map.GetPlayerMapPosition(mapID, "player"):GetXY()
Iirc, an instance will have a mapID but not have the Position mixin which contains the :GetXY() function.

SDPhantom 06-18-18 05:54 AM

This is getting off topic. The discussion is how to counteract the memory impact of calling C_Map.GetPlayerMapPosition() frequently.

jeruku 06-18-18 10:58 AM

Arguments are reserved(?)
 
In the old days of scanning for nameplates JamPlates used the method of declaring variables as arguments to reserve memory.

Edit: Think there was a misunderstanding. The global function was localized because it bothered me.

Lua Code:
  1. -- get/set mapID somewhere/somehow
  2. local function GetPlayerMapPos(px, py)  --  declare variables as arguments
  3.     -- some stuff here
  4.     px, py = C_Map.GetPlayerMapPosition(mapID, "player"):GetXY()
  5.     -- more stuff
  6. end

Siku 06-18-18 12:58 PM

That's just localizing the function, because local functions are something along the lines of ~25% faster than global functions in terms of executions.

The problem with map position is that some position-tracking addons uses it OnUpdate which was fine when it was two simple numbers. But the new API causes that code to create a new table every frame. Sure, those tables end up being collected as garbage sooner or later, but it's rather annoying and may scare users into thinking something is wrong with your addon.

TOM_RUS 06-18-18 05:21 PM

Blizzard dev who created those API's said to not worry about memory usage.

SDPhantom 06-19-18 02:19 AM

That's the point though, it's not just memory. When the garbage collection is called more often, it eats up cycles and becomes a CPU problem.

thomasjohnshannon 06-19-18 08:17 PM

I never said anything but just so it clear to everyone the function that elcius posted works well.

siweia 06-21-18 12:14 AM

Does anyone know how to scale the worldmap properly?
If you just simple SetScale, you won't be able to switch zones by click on different part of the maps.

elcius 06-21-18 12:57 AM

Quote:

Originally Posted by siweia (Post 328401)
Does anyone know how to scale the worldmap properly?
If you just simple SetScale, you won't be able to switch zones by click on different part of the maps.

Code:

WorldMapFrame.ScrollContainer.GetCursorPosition = function(f)
        local x,y = MapCanvasScrollControllerMixin.GetCursorPosition(f);
        local s = WorldMapFrame:GetScale();
        return x/s, y/s;
end


siweia 06-22-18 10:59 PM

Quote:

Originally Posted by elcius (Post 328402)
Code:

WorldMapFrame.ScrollContainer.GetCursorPosition = function(f)
        local x,y = MapCanvasScrollControllerMixin.GetCursorPosition(f);
        local s = WorldMapFrame:GetScale();
        return x/s, y/s;
end


I tried the code, the memory usage went up each time you switch zones on the worldmap.

Nevcairiel 07-02-18 01:58 AM

Quote:

Originally Posted by SDPhantom (Post 328382)
That's the point though, it's not just memory. When the garbage collection is called more often, it eats up cycles and becomes a CPU problem.

Unless you can measure an actual impact from that, its still not a problem.

If you are extremely worried about the memory consumption from the Map APIs, you could use my Map library HereBeDragons which offers table-free position APIs (althought thats not the reason I made it that way, its just the same API the library had in 7.x already) - and no, it also doesn't just wrap the Blizzard Map API to "hide" the tables. :)

siweia 07-07-18 10:47 PM

I found this in LeatixPlus by using "RunScript", and it does not produce memory waste.

Code:

local pTimer = 0

local function UpdateCharacterCoords(self, elapsed)
        pTimer = pTimer + elapsed
        if pTimer > 0.2 then
                RunScript('LeaPlusGlobalCoords = C_Map.GetPlayerMapPosition(0, "player")')
                if LeaPlusGlobalCoords then
                        cChar.x:SetFormattedText("%0.1f", LeaPlusGlobalCoords.x * 100)
                        cChar.y:SetFormattedText("%0.1f", LeaPlusGlobalCoords.y * 100)
                else
                        cChar.x:SetText("")
                        cChar.y:SetText("")
                end
                pTimer = 0
        end
end
cChar:SetScript("OnUpdate", UpdateCharacterCoords)


dssd 07-07-18 11:12 PM

That's way worse. It's just not attributing the memory usage to the addon. Now it's spending time lexing and compiling the string into byte code, which then becomes garbage. And then you pay the same price for the C_Map.GetPlayerMapPosition call.

Kanegasi 07-08-18 05:45 AM

Quote:

Originally Posted by Nevcairiel (Post 328515)
Unless you can measure an actual impact from that, its still not a problem.

You can. Initiate a garbage collection manually and watch your FPS.

Memory usage is not an issue. Having giant tables of data actually saves cpu, especially if you’re not calling things in combat. One fine example of memory/cpu efficiency is Details. In terms of combat FPS, Details clearly, objectively, wins over Skada or Recount.

Memory waste is bad. An occasional wasted object is sloppy but not usually an issue. However, dumping a table every frame is what we call a “memory leak”. Lua’s garbage collection is the least efficient part of the entire implementation. It’s like a garbage inception. You want to avoid it at all costs, but with hundreds of different addon authors, it’s inevitable. A blip in FPS every hour or so is fine, maybe every 30 minutes if I’m running a bunch of addons, but memory leaks trigger one every few minutes, as often as several a minute.

Having Blizzard give you a function that directly generates waste every time you use it for data that several addons want on an OnUpdate resolution is probably the worst thing any Blizzard dev has ever done for this game. It’s atrocious, disgusting, reeks of novice coding. I have enjoyed all the mixin stuff I’ve seen popup over the last few years. Someone on the dev team has lots of love for Lua. I sincerely hope these wasteful issues were an oversight, a bug, or done from someone new that will be fixed by someone senior.

Nevcairiel 07-08-18 12:33 PM

Quote:

Originally Posted by Kanegasi (Post 328564)
You can. Initiate a garbage collection manually and watch your FPS.

Because a manual full run of GC is actually the same as the incremental GC that runs in the background, right? :)

Please, go ahead and do show incontrovertible evidence without manually interfering with the garbage collector, then we can continue. All these arguments are grasping.

Fizzlemizz 07-09-18 01:53 AM

This is curiosity, not a "gang-up".

Unless there is some significant optimisation (happy to be educated), even incremental GC will be called more often as there is more G being produced to C.

dssd 07-09-18 03:12 PM

GC time is proportionate to the complexity of the garbage. These are incredibly simple tables.

Fizzlemizz 07-09-18 06:49 PM

Which begs the question, why use a table in a language that allows multiple returns?

I feel like I must be missing some simple point, which wouldn't be the first time :o.

dssd 07-09-18 07:02 PM

I'd guess it's towards normalization of data. For instance, look at C_Map.GetMapLinksForMap, it returns the same 2D vector table but as a field of a subtable in a collection of tables. In that case, multiple returns don't help, but containing them in a table keeps the data consistent. Vector 2D is likely the most contentious given that it is just two fields, but it scales better the more fields the type contains.

Fizzlemizz 07-09-18 09:01 PM

I remain to be convinced that it's normalisation ;).
Code:

local left, right, top, bottom = C_Map.GetMapRectOnMap(childMapInfo.mapID, mapID)

Simca 07-19-18 05:43 AM

This issue has come up a lot in #wowuidev over the last several months. There is a WoW UI dev in there who kindly answers most of our questions and takes note of issues.

A lot of different people have argued this point with him, and I think I can quickly summarize his two main arguments for those curious:
  • They've greatly improved garbage collection performance recently, and have monitored the performance involved in using tons of small, disposable tables under the new GC implementation. It is an incredibly small performance hit.
  • Tables help normalize UI implementation and are especially useful in cases where the number of returns is variable. The other thing seems to be preference. It seems like the UI devs prefer the syntax where you pull down a table of results and use the individual parts you need rather than doing 'local _, _, thingA, _, _, _, _, thingB = SomeRandomApi()'.

I'm just paraphrasing here, but this is what I recall. In any case, it seemed like an issue they were -not- willing to budge on. One person was very adamant that they should introduce a function to return this one argument he was interested in because he polled it very frequently and was now required to pull down a table (Spell name, maybe? I forget), and the UI dev was not willing to entertain the possibility of introducing specialized functions to bypass the table returns. He was fairly adamant that there would be a near zero performance impact due to the speed of simple table disposal.

The only annoying part here is that it is basically impossible for us to independently verify their claims because as noted in previous posts, you can't really perform a partial GC on demand and record cycles spent or reliably compare the results. The only ones who can really know the exact impact of these changes are Blizzard themselves.

Kanegasi 07-19-18 07:15 AM

Quote:

Originally Posted by Simca (Post 328752)
It seems like the UI devs prefer the syntax where you pull down a table of results and use the individual parts you need rather than doing 'local _, _, thingA, _, _, _, _, thingB = SomeRandomApi()'.

I can actually agree with this part. I have used a table to get function returns before, but it was a reusable table in a custom pool function. I have always not liked using an underscore as a universal dump variable. It has caused many minor issues over the course of addon development over the years. The wall of shame link in my signature exposes why it’s an issue.

However, relying on supposed efficiency to excuse inefficiency isn’t a great coding practice. It’s like tossing bottles at a campsite because the park you’re at has a world class janitor army. That’s still littering.

JDoubleU00 07-19-18 11:44 AM

Quote:

Originally Posted by Kanegasi (Post 328760)
However, relying on supposed efficiency to excuse inefficiency isn’t a great coding practice. It’s like tossing bottles at a campsite because the park you’re at has a world class janitor army. That’s still littering.

Great analogy!

Fizzlemizz 07-19-18 12:16 PM

Quote:

Originally Posted by Simca (Post 328752)
[*]They've greatly improved garbage collection performance recently, and have monitored the performance involved in using tons of small, disposable tables under the new GC implementation. It is an incredibly small performance hit.

Did they define "tons" or give a timeframe in which "tons" were created/disposed? This is an API for public consumption and test lab or even Blizzard UI conditions rarely mimic all the real world uses.

Simca 07-21-18 05:18 AM

I have IRC logs, so let me just paste the whole conversation - the one I was thinking of when I posted that above. TheDanW is the UI dev. This conversation occurred in December 2017, after BFA alpha was out but when addons were disabled so most of us were just watching the interface files for diffs in order to speculate on what changes needed to be made.

https://paste2.org/wZGNWyGv

There's at least a few more instances of this type of conversation, but I don't really want to dig up his entire post history for people to take apart outside context, just to post a bit so you can see Blizzard's reasoning on this. Also, he mentions in one other conversation that if people are interested, he could add support to a few frequently called functions so that somebody can pass their own table as an argument in order to be reused. Might be a decent thing for somebody to suggest for GetPlayerMapPosition, though I'm not sure how viable it is due to it being a mixin (Vector2D) return.

P.S. I realize "Dec 18 17:43:56 <Simca> all new API since WoD return tables, with a few exceptions" this is quite hyperbolic. There are certainly a large number of tables returned by API functions since WoD, but there are more than 'a few exceptions'. Not sure why I decided to say it like that in retrospect.

Edit: Also, he mentions elsewhere that the optimizations for the garbage collector he is talking about were changes that went Live with Patch 7.0.3.

lightspark 07-21-18 09:08 AM

Quote:

Originally Posted by Simca (Post 328826)
Also, he mentions in one other conversation that if people are interested, he could add support to a few frequently called functions so that somebody can pass their own table as an argument in order to be reused. Might be a decent thing for somebody to suggest for GetPlayerMapPosition, though I'm not sure how viable it is due to it being a mixin (Vector2D) return.

We're in C++ land now, bois :banana:

kurapica.igas 07-22-18 10:02 PM

The blz just don't use it themselves. We only need x, y, and it throw us a table with tons of the methods, this is not all about the GC, the Mixin system fill those methods into a table with lots of the re-hash cost, and mostly we don't really use them.

Thanks to elcius, I don't need to deal with it.:banana:


All times are GMT -6. The time now is 03:46 AM.

vBulletin © 2024, Jelsoft Enterprises Ltd
© 2004 - 2022 MMOUI