the mod is for self casted aura's, basically talent procs. I want to see when my "surge of light" procs so i can get a free flash heal etc...
Aye, but there could still be any number of temporary factors that affect the buff duration (idols, trinkets, armor set bonuses..). I think your original idea was best -- get the duration from UnitAura() in your OnShow() (triggered from UNIT_AURA or some other event), store that in a local "timeleft". Then use jerry's OnUpdate() -- subtract "elapsed" from "timeleft" and update the text.
Caching the max-duration to avoid calling UnitAura() in your event handler isn't worthwhile. Calling UnitAura() in OnUpdate() would be a bad idea, but doing it in response to an event is much simpler than worrying about a caching scheme and will perform perfectly fine.
By comparison, consider that every unit frames addon already probably does dozens of UnitAura() calls on every aura update event, potentially multiplied by dozens of targets, and they run ok.
when tracking buffs of any sort, you will eventualy end up doing many calls to unitBuff(), this is normal to an extent.. Like when you get a UNIT AURA event, you'll prolly want to walk UnitAura() once to pick up or drop off the buff you no longer have. You use the OnUpdate to move the bars your tracking and update the saved timmer.
Right. I understand that, and I agree. That's why I didn't think caching the max duration would work, which was the post I was commenting on before you replied.
I think you think I'm arguing against using UnitAura(), but I'm not.
UnitAura() accounts for the differnces in talents and rank.
That's what I'm saying -- if you "remember" the max duration for an aura and just refresh it to the stored max on some COMBAT_LOG_... event, the way I think was suggested, it won't be reliable. You can't avoid a UnitAura() call at some point per application/refresh.
I've come up with a new way to calculate the expire time.
Instead of calling UnitBuff() to get the new expire time each OnShow() you can store the fixed "buff duration" for the specific buff the first time that Aura fires. Then you can calculate the expire time by GetTime() + auraDuration each time the "COMBAT_LOG_EVENT_UNFILTERED" fires for that Aura. Then OnUpdate() can just subtract GetTime() - expireTime to get timeLeft. This alleviates the need to constantly query UnitBuff every OnShow()
Won't that depends on the situation? Some buffs might have different durations depending who casts them, their talents, glyphs, set bonuses, etc, so on a new application the "total duration" might not be the same as the last time you saw it.
Also, what's the relative frequency of COMBAT_LOG_EVENT_UNFILTERED events compared to UNIT_AURA or some other indication of (de)buff change?
Okay, you're right, there's no skew to elapsed. Dunno how I got that in my head.
And yeah, allocating a local is fast, but it really doesn't give any speed advantage when you're using it to call a function. The function call is already 100x slower than accessing a global symbol, even if the function is empty; creating a local alias saves you maybe 0.1% and isn't worth the code clutter, I think. :)
"elapsed" gives you exactly the time that occured since the previous frame
Sure, of course. But the question is, how many times per frame is that delta measured? Presumably, the main loop calculates "elapsed" at the beginning of each run, and then passes that value to every OnUpdate handler. If your OnUpdate is the very first one to run in the whole loop every frame, then you'd be right that "elapsed" would be a pretty exact measurement of "time since I ran last."
But that's not the case. Other UI components (both builtin and from other addons) probably have plenty of other OnUpdate handlers, and you have no way of knowing where in the sequence yours will be run. I don't think it's out of the question for all those other OnUpdates to take some measurable time to run, in which case that introduces skew. You are given "elapsed" since the last OnUpdate loop began -- but you are not given "elapsed" since the last time your particular OnUpdate handler ran.
Again, for short duration buffs it might not matter, but any long-ish timer in a normal user's UI with lots of other handlers running is not going to be accurate by the end, I think.
That's ... interesting or you can just do this:...
Oh, I suppose, if you wanted to be all, like, simpler, or something...
I think the obsessive-superstitious-optimizer in me wants to make as few symbols as possible "visible" to the closure. So I tend to want to make closures out in file-scope, so they can't see any symbols in the function where the SetScript() is presumably happening.
But it probably doesn't matter and then your way is easier. :)
Edit: Actually, I remember now why I did it that way. I modeled it on what I'm doing in my own addon now (for a similar purpose -- updating cooldown text), except I'll eventually want to have more display options, which will then need different logic OnUpdate. Rather than check those (relatively static) settings on every OnUpdate, I'm going to generate different OnUpdate functions depending on the preferences (and then re-generate and re-assign the OnUpdate if prefs change later). That will be cleaner for me if I have my OnUpdate generated in a function (which decides which OnUpdate to generate, etc).
But if your OnUpdate will always work the same way, then your way is easier.
I strongly suggest to be as fast and concise as possible in the OnUpdate handler. So don't call functions if you don't need to.
frame:SetScript("OnShow", function (self)
self.timeLeft = select(7, UnitBuff("player", self:GetName())) - GetTime()
local string_format = string.format
frame:SetScript("OnUpdate", function (self, elapsed)
local timeLeft = self.timeLeft - elapsed
if timeLeft > 0 then
timer:SetText(string_format("%1d s", timeLeft))
self.timeLeft = timeLeft
I'm pretty new to WoW API coding myself, but if I understand correctly, it can be problematic to try to use the "elapsed" argument that way. WoW's event dispatcher may give you the elapsed time since the last OnUpdate cycle *started*, but since you don't know how long each cycle *takes*, you can't just tally it up every time you get one and treat that as an accurate measure of time. The more OnUpdate code is running (anywhere, from any addon), the more extra processing time will be taken up per frame, and the more skew you'll get by summing that "elapsed" argument.
If you're tracking very short-term buffs that might not be noticeable, but I suspect if you ran the above on a several-minute buff you'd see your buff disappear while the timer was still ticking down.
So if you want accuracy, I think you still need a GetTime() call -- but don't worry about that too much, built-in functions are implemented in C and are pretty fast. I'm not even sure that creating local references to built-in functions is any faster (local pointers to Lua-defined global functions certainly is); I'm actually working on a utility to profile that sort of thing right now.
Another consideration is that creating variables, even locals, takes some time (the interpreter has to reserve new space in the local scope's stack), so it might be worth setting up any locals you need in a custom-built scope.
I'd do something like:
frame:SetScript("OnShow", function (self)
-- expires is like timeLeft except we don't subtract GetTime(),
-- we keep it as an actual timestamp of expiry to compare to GetTime() later
self.expires = select(7, UnitBuff("player", self:GetName()))
local function Generate_OnUpdate_Handler()
-- create a closure on necessary vars, so we don't have to reallocate them
-- on each OnUpdate call
return function(self) -- the function that will become OnUpdate
timeleft = self.expires - GetTime()
if timeleft > 0 then
self:SetText(format("%1d s", timeleft)) -- WoW gives format==string.format