ESOUI

ESOUI (https://www.esoui.com/forums/index.php)
-   General Authoring Discussion (https://www.esoui.com/forums/forumdisplay.php?f=174)
-   -   2.1 and SetHandler (https://www.esoui.com/forums/showthread.php?t=5106)

ZOS_ChipHilseberg 09/01/15 04:06 PM

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.

Wandamey 09/02/15 03:29 AM

probably a stupid question but i suppose someone has to ask it...

what is a closure?

an example would be nice too.

Harven 09/02/15 03:58 AM

Hey,
Wandamey, quick google search and here it is: http://www.lua.org/pil/6.1.html ;)

Wandamey 09/02/15 04:10 AM

...

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.

merlight 09/02/15 04:16 AM

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.

Wandamey 09/02/15 04:18 AM

Quote:

Originally Posted by merlight (Post 23157)
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.


: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?

merlight 09/02/15 04:43 AM

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)

Wandamey 09/02/15 05:04 AM

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.

Baertram 09/02/15 05:30 AM

Thx for asking the question about closures (didn't know the wording neither) Wandamey, and the explanation merlight!

Reading chips comment:
Quote:

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
I'd like to repeat Wandamey's question:

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.

ZOS_ChipHilseberg 09/02/15 07:57 AM

Quote:

Originally Posted by Baertram (Post 23162)
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?

Defining a handler in XML would set it on the control at the control's creation, which in the case of a non-virtual control would be during the load screen, which would hide any cost. This is pretty much the same as doing it in Lua during the add-on loading process. The main thing to avoid would be making a new closure and calling SetHandler with it frequently. Unless you're closing over local variables with the closure you should be able to only call SetHandler once on a control (for each handler type). If you are closing over local variables then you may want to consider not doing that, and storing the values on the control instead.

Fyrakin 09/02/15 08:17 AM

Thanks for heads up, now I have something to dig through :).

Wandamey 09/02/15 08:33 AM

Quote:

Originally Posted by ZOS_ChipHilseberg (Post 23166)
Defining a handler in XML would set it on the control at the control's creation, which in the case of a non-virtual control would be during the load screen, which would hide any cost. This is pretty much the same as doing it in Lua during the add-on loading process. The main thing to avoid would be making a new closure and calling SetHandler with it frequently. Unless you're closing over local variables with the closure you should be able to only call SetHandler once on a control (for each handler type). If you are closing over local variables then you may want to consider not doing that, and storing the values on the control instead.


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?

Baertram 09/02/15 09:00 AM

Quote:

Originally Posted by Wandamey (Post 23168)
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?

Same here :( An example with

a) don't use anymore
b) use this instead

would be a great help :banana:

votan 09/02/15 09:27 AM

Quote:

Originally Posted by Wandamey (Post 23168)
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?

Avoid using variables declared out-side anonymous, nested functions, if you can pass them via member of control.
Lua Code:
  1. local var = "outside"
  2. ctl:SetHandler("WhatEver", function() d("from " .. var)
  3. -- var must be "magically" known by storing a closure.
  4. -- (Which is how large??? And stored how long???)
  5. end)
=>
Lua Code:
  1. local var = "outside"
  2. ctl.save_var = var
  3. ctl:SetHandler("WhatEver", function(self) d("from " .. self.save_var)
  4. -- self is ctl, because handler get passed the control they are assigned to.
  5. -- But now all used variables are declared inside:
  6. -- Function is "stand-alone". => no closure.
  7. -- And does not need to be nested anymore.
  8.  end)

ZOS_ChipHilseberg 09/02/15 09:32 AM

1. Best:
Lua Code:
  1. <Texture name="Sample">
  2.     <OnMouseEnter>
  3.         --Code
  4.     </OnMouseEnter>
  5. </Texture>

2. Also Good:
Lua Code:
  1. --This is run once when the addon loads
  2. function InitializeAddon(texture)
  3.     texture:SetHandler("OnMouseEnter", function()
  4.         --Code
  5.     end)
  6. end

3. Bad:
Lua Code:
  1. --This is run often
  2. function SetupTexture(texture, width, height)
  3.     texture:SetHandler("OnMouseEnter", function()
  4.         --Code
  5.     end)
  6.     texture:SetDimensions(width, height)
  7. end

Instead of 3, which keeps setting the same handler code over and over again use 1 or 2 to only do it once.

votan 09/02/15 09:42 AM

Question:
What if like this:
Bad?
Lua Code:
  1. local function DoSomeWhat()
  2.         --Code
  3. end
  4.  
  5. --This is run often
  6. function SetupTexture(texture, width, height)
  7.     texture:SetHandler("OnMouseEnter", DoSomeWhat)
  8.     texture:SetDimensions(width, height)
  9. end

Wandamey 09/02/15 09:48 AM

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
  control = wm:CreateControl(blabla)
  etc...
  control:SetHandler("blabla", function)
end

and if i got it, it's better to use 2 different textures each one with its own handler, then hide/show the one you need instead of swaping dds/Handler?

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 )

Baertram 09/02/15 10:00 AM

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?

Wandamey 09/02/15 10:14 AM

lol that cross edit... i let you do the speaking now :D

ZOS_ChipHilseberg 09/02/15 10:49 AM

Quote:

Originally Posted by votan (Post 23172)
Question:
What if like this:
Bad?
Lua Code:
  1. local function DoSomeWhat()
  2.         --Code
  3. end
  4.  
  5. --This is run often
  6. function SetupTexture(texture, width, height)
  7.     texture:SetHandler("OnMouseEnter", DoSomeWhat)
  8.     texture:SetDimensions(width, height)
  9. end

This is slightly better than the bad example. It doesn't remake the closure every time. But it's still a waste to keep setting that same exact handler code on the control repeatedly. So it would be best to set some sort of flag to only do it once.


All times are GMT -6. The time now is 03:57 PM.

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