Thread Tools Display Modes
09/01/15, 04:06 PM   #1
ZOS_ChipHilseberg
ZOS Staff!
Premium Member
Yes this person is from ZeniMax!
Join Date: Oct 2014
Posts: 551
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.
  Reply With Quote
09/02/15, 03:29 AM   #2
Wandamey
Guest
Posts: n/a
probably a stupid question but i suppose someone has to ask it...

what is a closure?

an example would be nice too.

Last edited by Wandamey : 09/02/15 at 03:34 AM.
  Reply With Quote
09/02/15, 03:58 AM   #3
Harven
 
Harven's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 135
Hey,
Wandamey, quick google search and here it is: http://www.lua.org/pil/6.1.html
  Reply With Quote
09/02/15, 04:10 AM   #4
Wandamey
Guest
Posts: n/a
...

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.
  Reply With Quote
09/02/15, 04:16 AM   #5
merlight
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 671
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.
  Reply With Quote
09/02/15, 04:18 AM   #6
Wandamey
Guest
Posts: n/a
Originally Posted by merlight View Post
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.



I hope you can make it 3 lines with pictures


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?

Last edited by Wandamey : 09/02/15 at 04:23 AM.
  Reply With Quote
09/02/15, 04:43 AM   #7
merlight
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 671
Ok found something that might illustrate the issue and possible solution. From MiniMap.lua function AddLocation:
Lua Code:
  1. local pinTooltipInfo = ZO_MapPin.TOOLTIP_CREATORS[MAP_PIN_TYPE_LOCATION]
  2. locationPin:SetHandler("OnMouseEnter", function(pin)
  3.     FyrMM.SetTargetScale(pin, 1.3)
  4.     if not FyrMM.SV.PinTooltips then return end
  5.     if pinTooltipInfo.tooltip then
  6.         InitializeTooltip(ZO_MapLocationTooltip, Fyr_MM, TOPLEFT, 0, 0)
  7.         pinTooltipInfo.creator(pin.m_Pin)
  8.         IsCurrentLocation(pin)
  9.     end
  10. end)

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:
  1. locationPin.pinTooltipInfo = ZO_MapPin.TOOLTIP_CREATORS[MAP_PIN_TYPE_LOCATION]
  2. locationPin:SetHandler("OnMouseEnter", locationPin_OnMouseEnter)

or in this case it can easily be obtained from ZO_MapPin in the handler itself:

Lua Code:
  1. local function locationPin_OnMouseEnter(pin)
  2.     FyrMM.SetTargetScale(pin, 1.3)
  3.     if not FyrMM.SV.PinTooltips then return end
  4.     local pinTooltipInfo = ZO_MapPin.TOOLTIP_CREATORS[pin.m_PinType]
  5.     if pinTooltipInfo.tooltip then
  6.         InitializeTooltip(ZO_MapLocationTooltip, Fyr_MM, TOPLEFT, 0, 0)
  7.         pinTooltipInfo.creator(pin.m_Pin)
  8.         IsCurrentLocation(pin)
  9.     end
  10. end)
  Reply With Quote
09/04/15, 01:02 AM   #8
Lodur
AddOn Author - Click to view addons
Join Date: Feb 2014
Posts: 108
Originally Posted by Wandamey View Post
probably a stupid question but i suppose someone has to ask it...

what is a closure?

an example would be nice too.
A closure is a combination of context and code...
  Reply With Quote
09/04/15, 04:53 AM   #9
Wandamey
Guest
Posts: n/a
Originally Posted by Fyrakin View Post
What about self.your variable?
thanks, i figured it out with votan example, and everything is uploaded.
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


Originally Posted by Lodur View Post
A closure is a combination of context and code...
that's what i understood from harven's link, but I still dont understand why it doesn't simply replace the previous one when the context is the same. Not that it prevents me from doing what must be done, but it is still cloudy unclear.
  Reply With Quote
09/04/15, 05:55 AM   #10
merlight
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 671
Originally Posted by Wandamey View Post
... I still dont understand why it doesn't simply replace the previous one when the context is the same.
Think about this for a while. The context is not the same on each run, that's the whole point of creating a closure. Outer local variables are different, completely separate from the previous run's local variables.
  Reply With Quote
09/04/15, 06:24 AM   #11
Wandamey
Guest
Posts: n/a
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

Last edited by Wandamey : 09/04/15 at 06:49 AM.
  Reply With Quote
09/04/15, 08:39 AM   #12
Baertram
Super Moderator
 
Baertram's Avatar
WoWInterface Super Mod
AddOn Author - Click to view addons
Join Date: Mar 2014
Posts: 4,989
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:
  1. button:SetHandler("OnMouseEnter", function(self)
  2. --tooltipText is a local defined variable, outside of the closure, but inside the same function which sets the handler                 
  3. tooltipText = outputFilterState(false, settingsVars.settings.splitFilters, p_FilterPanelId, buttonId, mappingVars.settingsFilterStateToText[tostring(getSettingsIsFilterOn(buttonId, p_FilterPanelId))])
  4.                     if tooltipText ~= "" then
  5.                         local showToolTip = true
  6.                         --Don't show a tooltip if the context menu for gear sets is shown at the filter button
  7.                         if contextMenu.GearSetFilter[locVars.gFilterWhere] ~= nil then
  8.                             showToolTip = contextMenu.GearSetFilter[locVars.gFilterWhere]:IsHidden()
  9.                         end
  10.                         --Don't show a tooltip if the context menu for research, deconstruction & improvement is shown at the filter button
  11.                         if showToolTip and contextMenu.ResDecImpFilter[locVars.gFilterWhere] ~= nil then
  12.                             showToolTip = contextMenu.ResDecImpFilter[locVars.gFilterWhere]:IsHidden()
  13.                         end
  14.                         if showToolTip and contextMenu.SellGuildIntFilter[locVars.gFilterWhere] ~= nil then
  15.                             showToolTip = contextMenu.SellGuildIntFilter[locVars.gFilterWhere]:IsHidden()
  16.                         end
  17.                         if showToolTip then
  18.                             ZO_Tooltips_ShowTextTooltip(button, BOTTOM, tooltipText)
  19.                         end
  20.                     end
  21.                 end)

Optimized code:
Lua Code:
  1. button.tooltipText = tooltipText
  2. button:SetHandler("OnMouseEnter", function(self)
  3.     button.tooltipText = outputFilterState(false, settingsVars.settings.splitFilters, p_FilterPanelId, buttonId, mappingVars.settingsFilterStateToText[tostring(getSettingsIsFilterOn(buttonId, p_FilterPanelId))])
  4.     if self.tooltipText ~= "" then
  5.         local showToolTip = true
  6.         --Don't show a tooltip if the context menu for gear sets is shown at the filter button
  7.         if contextMenu.GearSetFilter[locVars.gFilterWhere] ~= nil then
  8.             showToolTip = contextMenu.GearSetFilter[locVars.gFilterWhere]:IsHidden()
  9.         end
  10.         --Don't show a tooltip if the context menu for research, deconstruction & improvement is shown at the filter button
  11.         if showToolTip and contextMenu.ResDecImpFilter[locVars.gFilterWhere] ~= nil then
  12.             showToolTip = contextMenu.ResDecImpFilter[locVars.gFilterWhere]:IsHidden()
  13.         end
  14.                     if showToolTip and contextMenu.SellGuildIntFilter[locVars.gFilterWhere] ~= nil then
  15.                         showToolTip = contextMenu.SellGuildIntFilter[locVars.gFilterWhere]:IsHidden()
  16.                     end
  17.         if showToolTip then
  18.             ZO_Tooltips_ShowTextTooltip(self, BOTTOM, self.tooltipText)
  19.         end
  20.     end
  21. end)

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?

Last edited by Baertram : 09/04/15 at 08:43 AM.
  Reply With Quote
09/04/15, 08:55 AM   #13
Wandamey
Guest
Posts: n/a
Originally Posted by Baertram View Post
snip

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:
  1. button.tooltipText = outputFilterState(false, settingsVars.settings.splitFilters, p_FilterPanelId, buttonId, mappingVars.settingsFilterStateToText[tostring(getSettingsIsFilterOn(buttonId, p_FilterPanelId))])


elsewhere you define this
Code:
local function MyFunction(self)

 local  tooltiptext = self.tooltiptext   -- just to keep the same variable and not mix everything up if your function is long like my arm - self here is button, cause it's called from the button

   blabla all your code from the anonymous function blabla

end
then for the Handler :
button:SetHandler("OnMouseEnter", MyFunction)
now lets the teachers look at the homework

Last edited by Wandamey : 09/04/15 at 09:06 AM.
  Reply With Quote
09/04/15, 09:06 AM   #14
merlight
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 671
Originally Posted by Baertram View Post
I changed the button variable inside the anonymous SetHandler function to use the "self".
Yes, that's good.

Originally Posted by Baertram View Post
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.
I don't see why it can't be local inside the anonymous function, you're assigning it from outputFilterState() right there. Edit: I guess you want to call outputFilterState inside the handler, not before it's set, because settings might change in-between.

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.

Last edited by merlight : 09/04/15 at 09:11 AM.
  Reply With Quote
09/04/15, 09:25 AM   #15
Baertram
Super Moderator
 
Baertram's Avatar
WoWInterface Super Mod
AddOn Author - Click to view addons
Join Date: Mar 2014
Posts: 4,989
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 ). 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.

Last edited by Baertram : 09/04/15 at 09:28 AM.
  Reply With Quote
09/11/15, 01:58 AM   #16
sirinsidiator
 
sirinsidiator's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 1,578
Originally Posted by ZOS_ChipHilseberg View Post
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.
Any chances you could add some functions for profiling to the API?
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.
  Reply With Quote
09/11/15, 02:05 AM   #17
Fyrakin
 
Fyrakin's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 129
Originally Posted by sirinsidiator View Post
Any chances you could add some functions for profiling to the API?
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.
For this purpose you can easily write a callback, but I agree we could use some profiling means, IE memory usage by add-on, cpu costs by add-on etc.
  Reply With Quote
09/11/15, 02:19 AM   #18
sirinsidiator
 
sirinsidiator's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 1,578
I could, but who knows what happens before that callback gets called. It would make the result pretty much useless IMO.
  Reply With Quote
09/11/15, 03:57 AM   #19
Fyrakin
 
Fyrakin's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 129
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.
  Reply With Quote
09/11/15, 04:31 AM   #20
merlight
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 671
Originally Posted by sirinsidiator View Post
Any chances you could add some functions for profiling to the API?
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.
GetFrameTimeMilliseconds() returns the same value during a single UI frame, yes.

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.
  Reply With Quote

ESOUI » Developer Discussions » General Authoring Discussion » 2.1 and SetHandler


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