Timer facility - allfoxwy/UnitXP_SP3 GitHub Wiki
/script local timerID = UnitXP("timer", "arm", 1000, 3000, "callbackFunctionNameString");
/script UnitXP("timer", "disarm", timerID);
/script local runningTimersCount = UnitXP("timer", "size");
Vanilla way doing periodic work is to use GetTime() in an OnUpdate() function and check if the time is come. This is basically doing busy waiting. And because of the game is single-threaded, these timer pulling call would cost FPS. Mostly these function call are useless. For example on a 60 Hz display, we need triggering an event every second, then there would be 59 useless function call before every 1 useful call. Blizz later added C_Timer facility in patch 6.0.2 to solve this problem.
This mod adding a new timer facility to the game. These timers are running in a separated thread so that their pulling call would not block game thread. When a timer triggers, it would call the corresponding Lua callback in game thread. The callback is passed with a single parameter which is timer ID
. It is safe to arm
or to disarm
timers in callbacks.
The arm
method in above example has 2 numberic parameter: The first 1000
means the timer would goes off when 1000ms after the arm
method. The second 3000
means the timer would repeat every 3000ms after first trigger. If we pass a 0
to second numberic parameter, the timer would only goes off for once then disarm itself. timerID
starts from 1. The arm
method return 0 for error.
timerID
is a 32-bits unsigned integer which should be able to safely store in Lua's number type. As by default Lua should use double-precision float for numbers and it would have 52-bits in its fraction part.
callbackFunctionNameString
is the name of a Lua function. Callback function need to be in global scope.
When disarm
a timer, its timerID
would not be reused. This should be fine as 32-bits is a lot of IDs.
Note that disarm
a timer means it would not be triggered in future, however if it is already triggered and its Lua callback is already in execution queue, this callback is still going to be fired later.
This behavior would not crash the game or cause Lua error as callback is encapsulated by a pcall() ignoring error.
However, consider that we are making an AddOn for Warrior Revenge, we might set local revengeAvailable = true
then arm
a 5-seconds Timer for later revengeAvailable = false
. During this 5 seconds, we used Revenge and got another Block. At this point we should disarm
the former Timer and arm
a new one. The problem araise: it is possible the old Timer already put its callback in in execution queue before we disarm
it. The solution is that timer ID
would not be reused so we could double check the timer ID
in callback function to make sure we are acting on the new Timer.
Beware that the timer is running in a separated thread so game's /reload
would NOT disarm a repeating timer. AddOns need to take care of their own repeating timer in PLAYER_LOGOUT
event and call disarm
method to shut down cleanly.
size
method would return how many timers are running.
Timer accuracy is influenced by FPS and operating system's scheduling. There would be at most 1 callback for each timer in execution queue. And it is possible that there could be no timer callback during a rendering frame. AddOns should NOT expect precise timing. This situation is same for GetTime() either: when FPS is low, AddOn might miss GetTime() when the timing is come.
OnUpdate() and Timer
As Timer requires Lua AddOns forming a different structure to make full use of it. This usually is not a trivial work.
However, even simply link AddOn's OnUpdate() function with a Timer instead of game's UIFrame:OnUpdate should provide benefits:
- AddOn might not need a FPS-speed OnUpdate(). We could use a slower Timer for it.
- UnitXP_SP3 would line up triggered callbacks in a FIFO queue. For each callback in the queue, UnitXP_SP3 would check time before execution. If callbacks already used up 1/80 second during a single rendering frame, those remaining callbacks in queue would be delayed to next frame. This behavior should smooth some graphical stutter, as repeating Lua code now have a loose time threshold to follow.