Running LUA-Code asynchroneously
From ESOUI Wiki
Why would I need that?
Avoiding performance impact
Even if you stick to the "return early, return often"-doctrine, running anything in a loop can get the client to stutter, freeze, or even crash. By running things asynchronously, you can reduce the impact.
Once, I managed to identify an endless loop because it happened within LibAsync and sent my client into enough of a terrible stutter - but would let me read the debug messages, at least.
Avoiding flood kick
If you call too many expensive API functions within a certain amount of frames, you will be kicked from the server. I had this happening to me in [DailyAutoShare https://github.com/manavortex/DailyAutoShare/blob/master/DasChatMessage.lua#L59] - on invite and quest share.
LibAsync is a small utility library that was coded by the awesome @votan73. You can grab the latest release from [PotionMaker http://www.esoui.com/downloads/info405-PotionMakerforAlchemyCrafting.html].
All examples below will assume that LibAsync has been initialised like this:
local async = LibStub("LibAsync") local task = async:Create("yourUniqueIdentifier")
You can call most functions either via async or via task. The advantage of using task is this:
local function callMe() ... end local function callMeToo() ... end task:Call(callMe):Then(callMeToo) -- or defining the functions inline: task:Call(function() ... end):Then(function() ... end)
Note: Since defining the functions inline means that they will be defined every single time the code is run, for the sake of performance it's more viable to define them outside the function scope if you run them multiple times.
local startValue = 1 local endValue = 9000 local function someFunction(index) -- Do a single step end task:For(startValue, endValue):Do(someFunction)
local function someFunction(key, value) -- Do a single step end task:For(pairs(anyTable)):Do(someFunction)
local function someFunction(index, value) -- Do a single step end task:For(ipairs(anyTable)):Do(someFunction)
zo_callLater is ZOS's fire-and-forget way of "triggering" events. Overusing it can lead to significant performance drops.
The function takes two arguments, the first is the function you want to call, and the second is the delay in milliseconds.
local function callMeToo() d("I was called five seconds later: " .. GetTimeString()) end local function callMe() d("I was called at " .. GetTimeString()) zo_callLater(callMeToo, 5000) end callMe()
You can register a function for update with the event manager in the following way:
local identifier = "myReallyUniqueIdentifier4711" local function callMe() d("I will be repeatedly called every 5 seconds until I am unregistered again") end EVENT_MANAGER:RegisterForUpdate(identifier, callMe, 5000) EVENT_MANAGER:UnregisterForUpdate(identifier)
... which is basically the same as zo_callLater.
There is one nice advantage to RegisterForUpdate, and that is you can make a continuous throttle by reregistering the update function whenever a certain function triggers.
I use this in LUI Extended to make a "x" variable duration throttle timer for Experience Gain for example. If the player goes 5 seconds without gaining XP then the saved value is dumped, reset, and the event is unregistered. If the player gains XP again during that 5 sec window, the event is reregistered and thus the timer restarts.
local ZO_CallLaterId = 1 function zo_callLater(func, ms) local id = ZO_CallLaterId local name = "CallLaterFunction"..id ZO_CallLaterId = ZO_CallLaterId + 1 EVENT_MANAGER:RegisterForUpdate(name, ms, function() EVENT_MANAGER:UnregisterForUpdate(name) func(id) end) return id end
This wiki page was created after this forum thread