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.
true, but how many faction tracking addons would a given player run at the same time?
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.
this seems to also affect registering inside a handler only there's no queue -- it just fails to do it.
probably a rare problem, but it seems that "loadwith" addons load inside of an "ADDON_LOADED" event handler, so it appears they can't register their own ADDON_LOADED events directly.
Nice, that definitely seems to make the most sense.
@lilsparky
I wonder if the issue is that the "ADDON_LOADED" event IS being registered, but because of the queue it isn't being registered until after the ADDON_LOADED event has already fired.
Edit:
Also related to weird event 'queuing' behavior.
I'm not exactly sure how the order of events processing is determined. I used to assume it was a simple queue. However, take the following example.
--Addon1.lua - loaded before Addon2
frame1:RegisterEvent("LOOT_OPENED")
frame1:LOOT_OPENED() -- just assume the event handler dispatched the event to this function
CloseLoot(); -- assume we decided all the loot was garbage, didn't want any of it
end
--Addon2.lua - loaded after Addon1
frame2:RegisterEvent("LOOT_OPENED")
frame2:RegisterEvent("LOOT_CLOSED")
frame2:LOOT_OPENED()
print("The loot opened!");
end
frame2:LOOT_CLOSED()
print("The loot closed!");
end
--Expected output when looting if the events were processed in a queue
The loot opened!
The loot closed!
--Actual output
The loot closed!
The loot opened!
For some reason the addons which registered the events after the first process them out of order if they're both fired in the same frame. I'm guessing for some reason the events are processed like a stack rather than a queue, or they're processed in alphabetical order?
1: frame1 get's LootOpened.
2: frame1 calls a function that trigger's LootClosed.
3: frame2 handler get's called because LootClosed fired.
4: frame1 handler closes it's LootOpened call.
5: frame2 get's called for LootOpened as we're still proccessing that event.
I wonder if the issue is that the "ADDON_LOADED" event IS being registered, but because of the queue it isn't being registered until after the ADDON_LOADED event has already fired.
no, it's actually not even registered. it gets no ADDON_LOADED events (for any other addons that load after it has) and then to make absolutely sure, i had it tell me all the frames registered to ADDON_LOADED -- the frame in question was not listed.
@OrionShock
I understand what you're saying. So it seems that any event triggered from an API call is handled by every addon before that API call returns. o.O I definitely see potential for stack overflow if this effect was chained by multiple addons.
@OrionShock
I understand what you're saying. So it seems that any event triggered from an API call is handled by every addon before that API call returns. o.O I definitely see potential for stack overflow if this effect was chained by multiple addons.
yeah, i got stack major overflows with my automated trade skill link scanner because i was closing frames in TRADE_SKILL_SHOW events and opening frames in TRADE_SKILL_CLOSE events (causing a feedback loop).
most of the time, a function will essentially be making a direct call to any event handlers registered to that event. i had assumed there was a handler thread running that dispatched things from a root level, so learning this has really helped me understand how it all works.
boy, this also causes problems for trade skill scanning in other ways.
in reaction to TRADE_SKILL_UPDATE, a mod might wish to record info on all trade skills. to be thorough, it's a good idea to make sure headers are expanded and no filtering is taking place. however, making those adjustments triggers a TRADE_SKILL_UPDATE event. to avoid recursion in the scanner, you can unregister the TRADE_SKILL_UPDATE event while resetting your filters, then re-register when done. except, now you can't i guess...?
local TRADE_SKILL_UPDATE_lock
function addon:TRADE_SKILL_UPDATE()
-- Return immediately if we are in the middle of an update
if TRADE_SKILL_UPDATE_lock then return end
-- Prevent further event to be processed
TRADE_SKILL_UPDATE_lock = true
--[[ do your stuff here ]]
-- Now that we're done, start to respond again
TRADE_SKILL_UPDATE_lock = false
end
The same one with error handling:
function addon:real_TRADE_SKILL_UPDATE(...)
-- do your stuff here
end
local TRADE_SKILL_UPDATE_lock
function addon:TRADE_SKILL_UPDATE(...)
-- Return immediately if we are in the middle of an update
if TRADE_SKILL_UPDATE_lock then return end
-- Prevent further event to be processed
TRADE_SKILL_UPDATE_lock = true
-- Catch any error and report it without breaking the locking mechanism
local ok, msg = pcall(self.real_TRADE_SKILL_UPDATE, self, ...)
if not ok then
geterrorhandler()(msg)
end
-- Now that we're done, start to respond again
TRADE_SKILL_UPDATE_lock = false
end
Another thing you can do, similar to locks, is make your own Register/Unregister/IsRegistered event functions for your frame. Make the first call to Register actually register the event. Any call to Unregister just raise a flag for your OnEvent handler to not dispatch the message. A subsequent call to Register would clear the flag. Then IsRegistered would return the status of this flag.
This solution is probably only sensible for existing large addons where you don't want to go through each event to manually add locks, and already rely on the UnregisterEvent working.
local OldCollapseSkillHeader = CollapseSkillHeader
local function CollapseSkillHeaderPreHook(arg1, arg2, ...)
-- do stuff with arg1 and arg2
return ...
end
function CollapseSkillHeader(arg1, arg2, ...)
return CollapseSkillHeaderPreHook(arg1, arg2, OldCollapseSkillHeader(arg1, arg2, ...))
end
Coming a bit late in the discussion. Be careful the name you choose because, although the function is called CollapseSkillHeaderPreHook, it's code is executed AFTER the hooked function has returned.
local original_CollapseSkillHeader = _G.CollapseSkillHeader
local function CollapseSkillHeaderPostHook = function (...)
-- you may here modify the values returned by the call to the original API
return ...
end
local function CollapseSkillHeaderPreHook = function (...)
-- you may here modify the values sent to the original API, or choose not to call the API at all
return CollapseSkillHeaderPostHook(original_CollapseSkillHeader(...))
end
_G.CollapseSkillHeader = CollapseSkillHeaderPreHook
Coming a bit late in the discussion. Be careful the name you choose because, although the function is called CollapseSkillHeaderPreHook, it's code is executed AFTER the hooked function has returned.
Yup your right, edited my previous code to avoid confusion.
Rollback Post to RevisionRollBack
To post a comment, please login or register a new account.
true, but how many faction tracking addons would a given player run at the same time?
this seems to also affect registering inside a handler only there's no queue -- it just fails to do it.
probably a rare problem, but it seems that "loadwith" addons load inside of an "ADDON_LOADED" event handler, so it appears they can't register their own ADDON_LOADED events directly.
Nice, that definitely seems to make the most sense.
@lilsparky
I wonder if the issue is that the "ADDON_LOADED" event IS being registered, but because of the queue it isn't being registered until after the ADDON_LOADED event has already fired.
Edit:
Also related to weird event 'queuing' behavior.
I'm not exactly sure how the order of events processing is determined. I used to assume it was a simple queue. However, take the following example.
For some reason the addons which registered the events after the first process them out of order if they're both fired in the same frame. I'm guessing for some reason the events are processed like a stack rather than a queue, or they're processed in alphabetical order?
frame1:RegisterEvent("LOOT_OPENED")
frame2:RegisterEvent("LOOT_OPENED")
frame2:RegisterEvent("LOOT_CLOSED")
frame1 will get the event of LootOpened before frame2.
however frame1's event hander trigger's LootClosed.
1: frame1 get's LootOpened.
2: frame1 calls a function that trigger's LootClosed.
3: frame2 handler get's called because LootClosed fired.
4: frame1 handler closes it's LootOpened call.
5: frame2 get's called for LootOpened as we're still proccessing that event.
no, it's actually not even registered. it gets no ADDON_LOADED events (for any other addons that load after it has) and then to make absolutely sure, i had it tell me all the frames registered to ADDON_LOADED -- the frame in question was not listed.
I understand what you're saying. So it seems that any event triggered from an API call is handled by every addon before that API call returns. o.O I definitely see potential for stack overflow if this effect was chained by multiple addons.
yeah, i got stack major overflows with my automated trade skill link scanner because i was closing frames in TRADE_SKILL_SHOW events and opening frames in TRADE_SKILL_CLOSE events (causing a feedback loop).
most of the time, a function will essentially be making a direct call to any event handlers registered to that event. i had assumed there was a handler thread running that dispatched things from a root level, so learning this has really helped me understand how it all works.
in reaction to TRADE_SKILL_UPDATE, a mod might wish to record info on all trade skills. to be thorough, it's a good idea to make sure headers are expanded and no filtering is taking place. however, making those adjustments triggers a TRADE_SKILL_UPDATE event. to avoid recursion in the scanner, you can unregister the TRADE_SKILL_UPDATE event while resetting your filters, then re-register when done. except, now you can't i guess...?
The same one with error handling:
This solution is probably only sensible for existing large addons where you don't want to go through each event to manually add locks, and already rely on the UnregisterEvent working.
Coming a bit late in the discussion. Be careful the name you choose because, although the function is called CollapseSkillHeaderPreHook, it's code is executed AFTER the hooked function has returned.
Yup your right, edited my previous code to avoid confusion.