I'll keep it short and elaborate if needed. I'm trying to unregister an event, but it won't. I check "immediately" after unregistering it to see if it actually is, and it's not. I do this in the event handler, which seems to/may be of some relevance.
Here is the code. I'm unregistering at line 44, to prevent it from firing during the loop from Collapse/ExpandFactionHeader. (the 'break' is just there temporarily) Line 61 print 1 every time the event fires.
Unregistering in event handlers seems to have changed in 3.3.3 and is now queued until the next frame. So it actually does unregister, just takes until all event handlers are done running.
If you absolutely need code to run as a result of UPDATE_FACTION event, but while IsEventRegistered("UPDATE_FACTION") returns false, here are two possible workarounds.
First (I have not tried this one) is to set a boolean flag in your addon after you Unregister the event. Then have the OnUpdate handler of a shown frame wait for both this flag to be true, and IsEventRegistered("UPDATE_FACTION") to be false. I think it would happen on the next update. Then in this OnUpdate handler finish the work that you needed done.
A different way (which I do use in one addon for a different event issue), after you Unregister the event. Register your addon for CHAT_MSG_ADDON, and use SendAddonMessage to send the player a whisper. Note that the "target" argument of SendAddonMessage can't take a 'UnitID,' so you have to use UnitName("player"). Then once CHAT_MSG_ADDON fires, check the prefix, message, channel, and sender arguments to ensure it was from your addon. Then do the work you need to do, since IsEventRegistered("UPDATE_FACTION") should be false now.
The reasoning behind the second way is it forces your addon to fire a custom event (similar to WM_USER of Windows API) sometime after all the current events are processed by every loaded addon (I don't know if it's the next frame, or sometime later, but it is almost immediate). The downside is that you can't do it often, because you don't want to flood the chat. The addon I use it in only does it at least once every 10 seconds in the worst case.
Why use such a complicated solution? The simplest:
[B]local FLAG = false[/B]
function addon:UPDATE_FACTION()
self.n = (self.n or 0) + 1
-- print("lol", self.n)
if GetNumFactions() > 0 [B]and not FLAG [/B]then
if not self.factionsRead then
[B]FLAG = true[/B]
self.factionsRead = true
-- Do your stuff here
else
self:UpdateFactions()
end
print(self:IsEventRegistered("UPDATE_FACTION"))
[B]FLAG = false[/B]
end
end
Thanks for all replies. I guess I'm gonna go with your solution, Xinhuan. That makes sense.
However I found that event wasn't unregistered at all. The following code will _not_ unregister it, supposedly because the event is triggered immediately after:
local frame = ExperimentalFrame or CreateFrame("Frame", "ExperimentalFrame", UIParent)
frame:RegisterEvent("UPDATE_FACTION")
frame:SetScript("OnEvent", function(self, event)
self:UnregisterEvent(event)
ExpandFactionHeader(1)
ChatFrame1:AddMessage(event.." triggered.", self:IsEventRegistered(event))
end)
Lombra, Xinhuan's code didn't work as you expected because of what was already pointed out by Nevcairiel and I.
I don't necessarily believe it's a bug, just the way events work now.
Your event WILL be unregistered, it just won't be unregistered while the event is currently firing (i.e. it'll be unregistered in the next frame as Nevcairiel says).
If you absolutely need code to run as a result of UPDATE_FACTION being fired, but while IsEventRegisterted("UPDATE_FACTION") returns false (I don't know exactly why you would need this) then see my long post.
Sorry, there must've been some misunderstanding involved. Xinhuan's code does what I need.
The real problem is that in my UPDATE_FACTION code, I run code that triggers the event again, which runs the triggering code again, and again, etc. By setting and checking that flag, I still can't prevent the event from firing, however I can prevent the code that triggers the event again from running more than once.
I understand what you're saying about events not being properly unregistered until the next frame, but that does not seem to be true in the code I posted above. Try it, and expand or collapse any faction header. The print should happen every time.
I don't need to run my UPDATE_FACTION code whilst the event is not registered (which will never be the case, anyway). I didn't make that part clear or you misunderstood, sorry.
Ok. Where I was getting confused was with you Unregistering the event, and then checking if it was registered in the same function. I thought there may have been some reason where you actually needed the event to be unregistered while that function was running.
In that case, yes, Xinhuan's code is perfect for what you need.
I only assumed it wasn't when you made a subsequent post stating there was a still a problem with the unregistration.
could always just write the function to handle the recursion.
IE when you call a function that triggers the event just bail out. then when the event triggers on subsequent times it'll eventually get to a point where you can do what you need it to do.
local GetNumFactions, select, GetFactionInfo, ExpandFactionHeader, CollapseFactionHeader = GetNumFactions, select, GetFactionInfo, ExpandFactionHeader, CollapseFactionHeader
--if you call a function a bajillion times, local it :)
frame:SetScript("OnEvent", function(self, event, ...)
for i = GetNumFactions() do
local name, _, _, _, _, _, _, _, isHeader, isCollapsed, = GetFactionInfo(i)
if isHeader then
if db.factions[name] then
if isCollapsed then
ExpandFactionHeader(i)
return
end
else
CollapseFactionHeader(i)
return
end
end
end
end)
This method allows for the event recursion to work like the default UI deals with it.
Also, if your going to hook blizzard functions, always a good idea to use a vargArg IE:
(I know it's one of those functions that really has no other logical args being passed to it, but it's good form regardless)
local OldCollapseSkillHeader = CollapseSkillHeader
function CollapseSkillHeader(index, [B]...[/B])
OldCollapseSkillHeader(index, [B]...[/B])
self:UpdateSkills()
end
This way things to randomly break when blizzard decides to change something at random.
If you're going to promote "safe" hooking (good for you), you need to do it completely... that means handling returns as well as args.
I don't want to go off-topic, but I want to ask since you brought it up.
In OrionShock's hooking example, what's the best way to handle the return of OldCollapseSkillHeader if you were to assume that at some point in the future Blizzard might decide to make the function return multiple values.
Is there an efficient way to temporarily store the list of return values, then return them later? Or would you have to know the number of return values, and make a sufficient amount of locals? Also this is assuming that you don't want your new function to return immediately after calling the old function.
local OldCollapseSkillHeader = CollapseSkillHeader
function CollapseSkillHeader(index, ...)
local ret = {OldCollapseSkillHeader(index, ...)}
self:UpdateSkills()
return unpack(ret)
end
If you're going to promote "safe" hooking (good for you), you need to do it completely... that means handling returns as well as args.
ah, yes... some things are not processing correctly atm.
@pigmonkey
That would depend on if you are pre or post hooking it. In context of the OP's code, it would be a post hook, as the info needed in the update func won't be available till after the blizzard function is run.
This would be a pre-hook method:
local OldCollapseSkillHeader = CollapseSkillHeader
function CollapseSkillHeader(index, ...)
-- do stuff here
return OldCollapseSkillHeader(index, ...)
end
as for creating a table every function call.. that could get expensive after a while. tho it's a trade off of mem vs speed.
do
local func fillTbl(tbl, ....)
wipe(tbl)
for i = 1, select("#", ...) do
tbl[i] = select(i, ...)
end
return tbl
end
local OldCollapseSkillHeader, tempTbl = CollapseSkillHeader, {}
function CollapseSkillHeader(...)
fillTbl(tempTbl, OldCollapseSkillHeader(...) )
--Call Addon func to do it's thing
return unpack(tempTbl)
end
end
---Yay for exotic solutions :) This code snippit should not really be used
--unless you seriously need to, like all things, if your hooking like this you
--may want to reconsider wtf your doing.
could always just write the function to handle the recursion.
IE when you call a function that triggers the event just bail out. then when the event triggers on subsequent times it'll eventually get to a point where you can do what you need it to do.
This method allows for the event recursion to work like the default UI deals with it.
The problem here is that the recursion in this case can actually stack overflow (especially if more than one addon is responding to that event). It's almost always better not rely on recursion because a second addon can interfere horribly.
local OldCollapseSkillHeader = CollapseSkillHeader
local function CollapseSkillHeaderPostHook(arg1, arg2, ...)
-- do stuff with arg1 and arg2
--arg1 and arg2 are the arguments sent to CollapseSkillHeader, while ... is the return from CollapseSkillHeader
return ...
end
function CollapseSkillHeader(arg1, arg2, ...)
return CollapseSkillHeaderPostHook(arg1, arg2, OldCollapseSkillHeader(...))
end
Here is the code. I'm unregistering at line 44, to prevent it from firing during the loop from Collapse/ExpandFactionHeader. (the 'break' is just there temporarily) Line 61 print 1 every time the event fires.
Am I missing something?
First (I have not tried this one) is to set a boolean flag in your addon after you Unregister the event. Then have the OnUpdate handler of a shown frame wait for both this flag to be true, and IsEventRegistered("UPDATE_FACTION") to be false. I think it would happen on the next update. Then in this OnUpdate handler finish the work that you needed done.
A different way (which I do use in one addon for a different event issue), after you Unregister the event. Register your addon for CHAT_MSG_ADDON, and use SendAddonMessage to send the player a whisper. Note that the "target" argument of SendAddonMessage can't take a 'UnitID,' so you have to use UnitName("player"). Then once CHAT_MSG_ADDON fires, check the prefix, message, channel, and sender arguments to ensure it was from your addon. Then do the work you need to do, since IsEventRegistered("UPDATE_FACTION") should be false now.
The reasoning behind the second way is it forces your addon to fire a custom event (similar to WM_USER of Windows API) sometime after all the current events are processed by every loaded addon (I don't know if it's the next frame, or sometime later, but it is almost immediate). The downside is that you can't do it often, because you don't want to flood the chat. The addon I use it in only does it at least once every 10 seconds in the worst case.
However I found that event wasn't unregistered at all. The following code will _not_ unregister it, supposedly because the event is triggered immediately after:
I don't necessarily believe it's a bug, just the way events work now.
Your event WILL be unregistered, it just won't be unregistered while the event is currently firing (i.e. it'll be unregistered in the next frame as Nevcairiel says).
If you absolutely need code to run as a result of UPDATE_FACTION being fired, but while IsEventRegisterted("UPDATE_FACTION") returns false (I don't know exactly why you would need this) then see my long post.
The real problem is that in my UPDATE_FACTION code, I run code that triggers the event again, which runs the triggering code again, and again, etc. By setting and checking that flag, I still can't prevent the event from firing, however I can prevent the code that triggers the event again from running more than once.
I understand what you're saying about events not being properly unregistered until the next frame, but that does not seem to be true in the code I posted above. Try it, and expand or collapse any faction header. The print should happen every time.
I don't need to run my UPDATE_FACTION code whilst the event is not registered (which will never be the case, anyway). I didn't make that part clear or you misunderstood, sorry.
In that case, yes, Xinhuan's code is perfect for what you need.
I only assumed it wasn't when you made a subsequent post stating there was a still a problem with the unregistration.
IE when you call a function that triggers the event just bail out. then when the event triggers on subsequent times it'll eventually get to a point where you can do what you need it to do.
This method allows for the event recursion to work like the default UI deals with it.
(I know it's one of those functions that really has no other logical args being passed to it, but it's good form regardless)
This way things to randomly break when blizzard decides to change something at random.
I don't want to go off-topic, but I want to ask since you brought it up.
In OrionShock's hooking example, what's the best way to handle the return of OldCollapseSkillHeader if you were to assume that at some point in the future Blizzard might decide to make the function return multiple values.
Is there an efficient way to temporarily store the list of return values, then return them later? Or would you have to know the number of return values, and make a sufficient amount of locals? Also this is assuming that you don't want your new function to return immediately after calling the old function.
Though I have no clue if it's overly expensive.
ah, yes... some things are not processing correctly atm.
@pigmonkey
That would depend on if you are pre or post hooking it. In context of the OP's code, it would be a post hook, as the info needed in the update func won't be available till after the blizzard function is run.
This would be a pre-hook method:
as for creating a table every function call.. that could get expensive after a while. tho it's a trade off of mem vs speed.
sorry for the off topic.
For generic post-hooks, i'm still not sure if it isnt easier to just use a securehook then to worry about the return values.
The problem here is that the recursion in this case can actually stack overflow (especially if more than one addon is responding to that event). It's almost always better not rely on recursion because a second addon can interfere horribly.