2.1 and SetHandler
We've been profiling some UI code since 2.1 went live and we've noticed some spikes with SetHandler on controls. Especially when the code is frequently making a new anonymous closure and passing that to SetHandler. In a lot of cases this can be avoided during play time by setting the handler on the control in the XML or on a template (virtual = true) in the XML so that cost is paid during the load. The handler closure should also only be made once instead of every time SetHandler is called, by storing the closure in a local or on a table.
|
probably a stupid question but i suppose someone has to ask it...
what is a closure? an example would be nice too. |
Hey,
Wandamey, quick google search and here it is: http://www.lua.org/pil/6.1.html ;) |
...
Is there a difference between setting the Handler in a xml or setting on load with a control creation in lua? now that i see what a closure is (in the great lines) i'd still like an example... i mean related to what Chip said. |
I'll try to provide a concrete example from some add-on, I'll probably use Fyrakin's MiniMap because it seems to have been hit hard by this.
|
Quote:
:eek: I hope you can make it 3 lines with pictures :D the big picture i see here is : store your functions first. but how do i pass a variable to it without using an Anonymous func again to call it? -- maybe the table? |
Ok found something that might illustrate the issue and possible solution. From MiniMap.lua function AddLocation:
Lua Code:
The anonymous function uses pinTooltipInfo from outer scope, thus each time this runs, a new closure holding that local variable must be created. Possible solution: define the handler function outside AddLocation, and change it to obtain pinTooltipInfo in a different way -- either make it a member of an argument it gets, like this: Lua Code:
or in this case it can easily be obtained from ZO_MapPin in the handler itself: Lua Code:
|
what if i have something iterated like this
for k,v in pairs(table) do control[k]:SetHandler("OnWhatever", function(self) myfunc(self, k, v.data) end) end like most of user tooltips could do for example. |
Thx for asking the question about closures (didn't know the wording neither) Wandamey, and the explanation merlight!
Reading chips comment: Quote:
Is there a difference between setting the handler via XML, or creating the controls in the lua script and assigning the handlers there? In what cases should we use XML definitions then. And how are we supposed to define it in an XML file? Maybe we can get an example of an anonymous closure that is creating spikes, and the same thing fixed in lua script, and in addition fixed in XML instead? This would be great. |
Quote:
|
Thanks for heads up, now I have something to dig through :).
|
Quote:
whaow... how to scare the noobs. sry but i still need a translation. are you saying "stop using anonymous functions for your handlers" or there is more to it? |
Quote:
a) don't use anymore b) use this instead would be a great help :banana: |
Quote:
Lua Code:
Lua Code:
|
1. Best:
Lua Code:
2. Also Good: Lua Code:
3. Bad: Lua Code:
Instead of 3, which keeps setting the same handler code over and over again use 1 or 2 to only do it once. |
Question:
What if like this: Bad? Lua Code:
|
aahh that's less scary.
if for example i set up my control with the anchors and all this like this, it's ok even if created in the course of the game? : (will create only once) Code:
if not (control) then and votan, thanks for the workaround... i learned something like that not long ago, still need to work on it :D Edit: btw, how to "remove" a Handler? Setting it on nil will delete the previous "closure" too? i suppose not if rewriting it just creates more ( i may not get exactly what a closure is but i hope you get the idea of the question ) |
Thanks for the examples, they really helped.
I hope using the local function for the handler, like votan asked, is ok too. And I also hope the ~= nil check in Wandamey's example is alright, as the handler should only be assigned once then. Using 2 different texture controls (and showing texture 1 as you are hiding texture 2), instead of only 1 texture control where you just change the dds file, only makes sense if the handler changes too. But even then I'd think removing the old handler and setting a new one is better then creating a second texture control in total? |
lol that cross edit... i let you do the speaking now :D
|
Quote:
|
i'm not sure of the protocole here... do I have to sacrifice a Virgin or something to have my questions looked at?
I'm starting to feel a little butthurt tbh. yeah top post of a second page. ok. nvm. forget it. |
/offtop
Quote:
Quote:
|
Quote:
i suppose Code:
i repeat my question about "removing" a Handler (seems we got 2 different answers with baertram, maybe it would be a good thing to clear it) is niling a handler as bad as overwrite it? |
Quote:
|
so basically, i could patch anything very easily if the same function is called all the programm long by doing this (or just tweak the function with conditions if this has to change):
Lua Code:
|
You just need to avoid creating a thousand closures. Once you do that, you will notice that your code doesn't need to call SetHandler a thousand times, because all the calls would be identical. Aim for removing unnecessary closures. Set the handler once after constructing the control.
If you need something more complex, like switching an OnUpdate handler on when resizing and off when it's done, you naturally do have to call SetHandler multiple times, but chances are you don't need to create multiple closures. |
Quote:
|
OK, I just changed texture handlers in My add-on using pre-declared functions and setting handlers only when new controls are created. I expected to see much better results, though performance improvement is visible but not as I hoped :(.
|
My question is, what changed in the IC update that made this so much more of a problem than it was previously? Of course we should not be creating unnecessary closures, but it seems like the game is handling the issue with less grace than it should (or at least less than it was previously).
|
Quote:
(yeah I feel guilty for last week-end now) |
Quote:
|
haha, I like "OnTextureLodead". This explains in short why the peaks happen :D
|
Quote:
|
Is there any example for designing a UI with XML and using it in LUA with my Addon ?
I never was happy to create this all in LUA. |
Quote:
|
Thanks, that will help. Is there a possibilty to use array names for XML elements?
|
Quote:
|
Quote:
So, as you suggested I changed sethandler setting during add-on init for most of the texture add-on would use (handlers mostly for tooltips). I even moved anonymous functions to predeclared ones. I didn't see any change in resource consumption and I haven't noticed any signifficant performance improvement. SO, what else you would suggest us to do? I as many others would like to have minimap functionality, but at current state it is to say the least is not usable. Follow up. I managed to address the performance issues. Even though I no longer use my own tables to store and manage custom pin data I still observed fast collectgarbage("count") build up. |
Quote:
|
Quote:
|
Quote:
I was afraid i wouldn't use properly the "self", but i would have used an addon variable to temporary store it if it hadn't worked. surprisingly, it went so flawlessly that I had to recheck 3 times that i didn't edit in the pts folder instead of the live one :D Quote:
|
Quote:
|
it's a bit hard to tell i I don't know how it is stored and processed in the first place.
i mean, the handler has an identifier : control/"OnWhatever", i thought what you store in it would replace the old one, and eventually replace the previous results with the new ones when ran again, why would the memory keep the old runs ad vitam aeteram? edit.. well ok, different variables... it doesn't explain why rewriting a stored procedure like in the example you gave me or like votan asked is that bad. (i see it can be better by not running unecessary calls, but it aint the end of the world either, i mean, ZOS_DanB even recommanded to do an update all session long when DerBombo asked for an EVENT that triggers at the end of the session...) re edit : to understand why i'm a bit puzzled, maybe i should add that until now I thought that the interest of having local vars was that (i believed) they were trashed after the function was run |
Quote:
Quote:
|
Quote:
as soon as my head get rif of this picture, it should be way more clear. |
To see if I understood everything correct about the performance stuff I'd like to show you an example and ask, how I need to change it (including ideas).
Current code: Lua Code:
Optimized code: Lua Code:
I changed the button variable inside the anonymous SetHandler function to use the "self". And I changed the local tooltipText variable usage inside the anonymous SetHandler function to use the self.tooltipText variable now, which was passed to the button control outside the closure. But what about all the other variables used inside the anonymous SetHandler function, like "buttonId" (which is a parameter of the calling function, which is setting the handler), "settingsVars.settings.splitFilters" (which is an addon wide known local variable set as the settings in LAM 2.0 panel get changed), "p_FilterPanelId" (which is a parameter of the calling function too)? Do I need to pass them to the button control too and use the self.variableName inside the anonymouse function then? |
Quote:
i think you should define : in the first example button.tooltiptext = tooltiptext -- Edit : and define it here, not in the function btw with Lua Code:
elsewhere you define this Code:
local function MyFunction(self) Quote:
|
Quote:
Quote:
outputFilterState, buttonId, settingsVars... everything that's local above the anonymous function definition goes into the closure's context. I think you don't need to worry about that if you're only doing the whole thing once per user action. I'm saying this because that's what I do. For example here: https://github.com/merlight/eso-merT...window.lua#L52. This function is called once after window creation. There's one closure for savePos and one for resizeStart, and they're used for handlers on lines 58-60. But each time resizeStart is called, it creates a new closure for OnUpdate handler. Does it bother me? I could put self or panel inside control and define that handler outside. But resizeStart is only called once per click on control border, that's not enough to force me to make it a little bit more convoluted. |
Thanks for the hints. I'll try to change it to local functions instead of the anonymous then too.
Ok maybe it is easier to understand where the current variables are used, and how they are used, if you take a look at the current version of FCOItemSaver and look inside the function "AddOrChangeFilterButton(...)" in source code line 6300. The SetHandler call I described above is in line 6336. The tooltipText could be inside the closure too, yes. I don't know why I defined it outside in the past as it will be overwritten by the outputFilterState(...) function again. I guess I had some reason and need to change and test to find it again (or drop it :D). Thanks. But you're right: I'm only setting the handler, and updating the tooltip text, if the user decided to use the tooltip for that button. So if he changes the settings and moves the mouse over the existing button the handler needs to be updated to hide the tooltip instead. I guess I need to NIL the handler then too before changing it. This example here, function AddOrChangeFilterButton(...), is called everytime you open the crafting station, mail panel, player trade panel, guild bank, bank to update the inventory filter buttons. So it is not used constantly but after a user action, right. |
shouldn't buttonId be a member of self at some point?
(haven't looked at the whole code yet and i don't know when or how you declare it, but thats why i suggested to define your tooltip text outside btw) |
Defining it outside won't work as the settings could be changed after the handler was set.
And buttonId is a parameter of the calling function AddOrChangeFilterButton. So I guess, and that is what I asked :-), yes: it should be connected to the button control too. |
Quote:
i should look at your code first, just don't listen to me. am just writing out loud. ok, i looked at it... i saw keanu reeves and a colored blob at first but still 2 solutions maybe? either just redefine button.buttonId = buttonId (same with the panelId i suppose) or button.tooltipText = function(blabla) <-- put this outside of your check : if (checkIfButtonexists == nil) , so it's updated when it needs to be. And, the most important here : wait for another advice than mine :p |
Quote:
It would be enough if we had a function that gave us an updated time inside a function in order to allow us to write our own tools. As far as I have seen all time functions only update between execution, so if I called GetGameTimeMilliseconds() twice with some heavy processing in between I would get the same value from both calls. |
Quote:
|
I could, but who knows what happens before that callback gets called. It would make the result pretty much useless IMO.
|
I would make a callback logger and call it whenever I want to log something. Process with many forks can do the callback with a parameter where it can send some essential info. If something and somewhere happens logger might have a clue if it get a sensible info through parameter.
|
Quote:
GetGameTimeMilliseconds() returns actual time since the game was started, so it basically works for measuring time spent within a function, but the results are not very consistent. They depend on thread scheduling (how much CPU time the Lua VM is given), how it struggles with memory allocations etc. If you want to evaluate whether func1 is more efficient than func2, you'll have to call them hundreds of thousands of times to get some usable averages. |
Quote:
What I did was something like this: Lua Code:
Best case we would get a value in UserSettings.txt which enables a profiling event that returns data for each frame. EVENT_FRAME_EXECUTION_TIMES ( integer frameTime, integer cpuPercent, integer memoryAllocation, object callstack, object addonData ) Callstack would contain all calls that happened during the frame in tree form: Lua Code:
Lua Code:
|
Profiling event would certainly help to narrow down the most problematic pieces.
|
I just found in MailLooter I was calling SetHandler 4 times in my ZO_ScrollList row setup function. This can be called a lot if your scrolling through the list...
Since ZO_ScrollList uses a row template - the only way to fix this is in XML. Just a heads up for anyone else doing what I did with a ZO_ScrollList. |
All times are GMT -6. The time now is 12:02 PM. |
vBulletin © 2024, Jelsoft Enterprises Ltd
© 2014 - 2022 MMOUI