I can't fathom what is going on at all here. I isolated the problem down to this sample addon. These few lines of code plus the latest Ace3 are the only two addons I have running right now.
local core = LibStub("AceAddon-3.0"):NewAddon("Test", "AceEvent-3.0")
local frame = CreateFrame("Frame")
frame:SetScript("OnEvent", function(self, ...) print("CustomFrame:", ...) end)
function core:OnEnable()
print("Test OnEnable")
self:RegisterEvent("ADDON_LOADED", function(...) print("AceEvent:", ...) end)
frame:RegisterEvent("ADDON_LOADED")
LoadAddOn("Blizzard_GuildUI")
end
Here I have two event handlers set up: one using AceEvent-3.0 and one using a custom frame with a SetScript. If you run this script, you should see output for each addon that is loaded, but the only thing that prints is "Test OnEnable". Neither event handler receives an ADDON_LOADED event. If you move the RegisterEvent() call out of OnEnable, it will work correctly. Even stranger though, if you comment out the LoadAddOn call, once again everything works fine.
I discovered this problem when someone used one of my addons that registered ADDON_LOADED in OnEnable with an unrelated addon that called LoadAddOn in its OnEnable handler, causing my event to never fire.
You're registering the event then triggering it within the same function - the Blizzard event system never gets a chance to process the event because control of the client is still inside of your function. Register the event in OnInitialize() and I bet it works just fine when you load the AddOn in OnEnable().
Maybe I wasn't clear, but it doesn't fire for any future ADDON_LOADED events, not just the one that was specifically being loaded. Visiting the guild bank or opening the achievements pane for the first time will not produce anything like they should.
And you're right, moving it to OnInitialize would work fine, but I am mostly curious what is going on, as it doesn't seem intended.
Actually, now that I tested it, I can confirm that the problem persists. For example, change the above code from OnEnable to OnInitialize and the exact same thing happens.
That said, this might still fix the original problem where the LoadAddOn call is in another addon. For example, if we moved both the RegisterEvent and LoadAddOn calls to OnInitialize in each addon, it would work fine.
Printing out the loading steps in AceAddon-3.0 proves this, I think. Each time the "onEvent" function is called, it processes the queues for initialize and enable. Through printing, you can note that each addon is initialized in a (usually?) different event call, while at the end, all addons are enabled at the same time. My theory then, is that for some reason, using RegisterEvent("ADDON_LOADED") and LoadAddOn() in the same event execution path will cause it to not correctly register.
One more thing to report. If you iterate over GetFramesRegisteredForEvent() after the code I initially posted, you will note that none of the frames are listed, including AceEvent30. If you have another AceEvent-3.0 addon that registers ADDON_LOADED earlier and successfully, obviously all other Ace3 addons will work fine. However, if the addon that registers ADDON_LOADED first fails, no other addon will get dispatches from AceEvent because it thinks it is already registered.
I should also note that anything registered after the LoadAddOn() is called will still be successful. Only ADDON_LOADED registrations before a LoadAddOn call in the same execution path will fail.
Uhm, why are you loading addons this way in the first place. Declare proper deps in your TOC and the system will load the addons for you automatically.
For what it's worth: in my personal experience, calling LoadAddOn during any startup events inevitably leads to confusion, regret, failure, Justin Bieber, weeping and wailing and gnashing of teeth. You might consider two other approaches instead:
Ideally, wait until the first time you would be using symbols from that other addon, then load it at that point. Alternatively, if you really need the module loaded quickly instead of waiting, then instead of calling LoadAddOn, set a timer to call it after a few seconds.
For the builtin modules, there are wrapper functions, most of which are one-liners admittedly not providing extra functionality; in your case it would be GuildFrame_LoadUI().
@pigmonkey: I do not think you can fix this in your addon so tell the author of the addon that calls LoadAddOn during OnEanble not to do it. It probably messes up another addons anyway. (What is its that by the way ?)
I think the bottom line is that registering is pretty safe in OnInitialize, since AceAddon should usually (always?) only initialize one addon at a time.
However, another surprising note that I didn't investigate too thoroughly yet. When I run this code in contrast to the code in the original post, it works. As far as I know, this is simulating the OnEnable in AceAddon (only as far as which events trigger which functions), but for some reason it is different:
local aceFrame = CreateFrame("Frame")
local addonFrame = CreateFrame("Frame")
-- Simulating Ace framework
aceFrame:SetScript("OnEvent", function(self, event, ...)
addonFrame:OnEnable(...)
end)
aceFrame:RegisterEvent("PLAYER_LOGIN")
-- Simulating addon
addonFrame:SetScript("OnEvent", function(self, event, ...) print("addonFrame:", ...) end)
function addonFrame:OnEnable(...)
self:RegisterEvent("ADDON_LOADED")
LoadAddOn("Blizzard_AuctionUI")
-- ADDON_LOADED is successfully registered and prints as expected
end
I thought it might have something to do with the safecall, but even removing that in AceAddon does not change it. It sounds like there is something in between this example and the first one I posted that causes this bug.
Okay, I think I finally resolved it after cutting down AceAddon bit by bit.
Firstly, my summarized description of the bug: If you call LoadAddOn() before PLAYER_LOGIN has fired, any frames that attempted to register ADDON_LOADED in the execution path before the LoadAddOn() call will silently fail to register.
And here's the reason why I observed it in AceAddon:
For some reason, IsLoggedIn() is reporting true before PLAYER_LOGIN fires. Doing a simple print during both the iteration of the enablequeue and when PLAYER_LOGIN fires, you can note that the enablequeue is done first with a difference in GetTime(), which likely means they're at least 1 frame apart. In most cases this difference of API and event might not matter, but as far as I can tell this is what is causing the above bug.
I am not sure of this is related to your problem... but, as far as I know, GetTime() is not affected by rendering frames in any way. Most, if not all, addon code is executed between frames, yet GetTime() can return different values. For example, most of what happen before the initial login can span over several seconds, though you are still seeing the loading screen, and GetTime() will return different values.
However, the return of query functions like IsLoggedIn() are sometimes not up-to-date though the corresponding event fired.
What you said might be possible but I would not use GetTime() return value as an evidence.
PS: if you want to test it, just update an upvalue in an onupdate handler :
local f = CreateFrame()
local frameCount = 0
f:SetScript('OnUpdate', function() frameCount = frameCount + 1 end)
f:SetScript('OnEvent', function(_, ...) print(GetTime(), frameCount, ...) end)
f:RegisterEvent('ADDON_LOADED')
f:RegisterEvent('PLAYER_LOGIN')
Ah, okay, I was under the assumption that GetTime() only updated once every frame. Using your script it appears that they both fire on frame 0 regardless of how many addons are loaded, which makes sense.
Be that as it is, the order in the chat log always prints:
Enabling Addons (IsLoggedIn() returned true)
PLAYER_LOGIN fired
Another point that might help you: registered callbacks (using setscript) seem to be called in order they was registered (I won't bet my first born on this though). So if AceAddon creates its frame and registers its events before your code, it is called before. Moreover, AceEvent and AceAddon have their own event handlers (so AceAddon does not depend on AceEvent).
And last but not least, the algorithm of AceAddon initialization is this :
Each time PLAYER_LOGIN *or* ADDON_LOADED happens :
- while there are addons to initialize, initialize them, handling recursion (addons loaded or modules created during the execution of OnInitialize),
- if IsLoggedIn() returns true, while there are addons to enable, enable them, handling "enable recursion" (addons/modules enabled during the execution of OnEnable)
Note that ADDON_LOADED happens before and after PLAYER_LOGIN (LoadOnDemand).
Actually I'm not even sure what happens when you call LoadAddOn (does it trigger and resolve another ADDON_LOADED before returning LoadAddOn ? do the listener registered just before LoadAddOn catch the event ?). In my memory, nasty things happen with the dependencies handled by the game client itself. I remember talking about this with Tekkub on these forums.
Yeah, basically IsLoggedIn() returns true before PLAYER_LOGIN fires, which means an ADDON_LOADED event can trigger the enabling of addons, and while this probably doesn't matter 99% of the time, it makes a difference here for whatever reason.
There is no real solution (unless Ace3 removes IsLoggedIn() completely and instead sets a flag when the PLAYER_LOGIN event fires), but there are easy ways to prevent it, such as registering ADDON_LOADED in OnInitialize only or not using LoadAddOn anywhere in OnInitialize or OnEnable.
i skimmed this thread, but if i understand, you're running into a problem trying to register inside the event being called. blizz changed the registry system a while back to delay unregistering until after the event handler had exited, but they seemed to simply disable the ability to add a new registration while inside an event handler.
so if you have an addon responding to an ADDON_LOADED event and it manually loads an addon that registers an ADDON_LOADED event handler, that second addon will never see the event because its handler was never actually registered due to the ADDON_LOADED event being active at the time the register function was called.
presumably, this is true for other events. unless they've fixed it, anyways... it was a while ago.
Okay, once more. I need to work on my explanations.
The event is never registered at all. This is especially troubling with AceEvent, because after it thinks it registered it, it will not try again for any subsequent addons yet it will never receive any calls to pass on to the addons.
And also remember that this is not necessarily in your addon. If you are registering the event and someone else is loading the addon and you both use Ace, there is a chance that you will be affected.
Rollback Post to RevisionRollBack
To post a comment, please login or register a new account.
Here I have two event handlers set up: one using AceEvent-3.0 and one using a custom frame with a SetScript. If you run this script, you should see output for each addon that is loaded, but the only thing that prints is "Test OnEnable". Neither event handler receives an ADDON_LOADED event. If you move the RegisterEvent() call out of OnEnable, it will work correctly. Even stranger though, if you comment out the LoadAddOn call, once again everything works fine.
I discovered this problem when someone used one of my addons that registered ADDON_LOADED in OnEnable with an unrelated addon that called LoadAddOn in its OnEnable handler, causing my event to never fire.
What is going on here?
And you're right, moving it to OnInitialize would work fine, but I am mostly curious what is going on, as it doesn't seem intended.
That said, this might still fix the original problem where the LoadAddOn call is in another addon. For example, if we moved both the RegisterEvent and LoadAddOn calls to OnInitialize in each addon, it would work fine.
Printing out the loading steps in AceAddon-3.0 proves this, I think. Each time the "onEvent" function is called, it processes the queues for initialize and enable. Through printing, you can note that each addon is initialized in a (usually?) different event call, while at the end, all addons are enabled at the same time. My theory then, is that for some reason, using RegisterEvent("ADDON_LOADED") and LoadAddOn() in the same event execution path will cause it to not correctly register.
I should also note that anything registered after the LoadAddOn() is called will still be successful. Only ADDON_LOADED registrations before a LoadAddOn call in the same execution path will fail.
Ideally, wait until the first time you would be using symbols from that other addon, then load it at that point. Alternatively, if you really need the module loaded quickly instead of waiting, then instead of calling LoadAddOn, set a timer to call it after a few seconds.
For the builtin modules, there are wrapper functions, most of which are one-liners admittedly not providing extra functionality; in your case it would be GuildFrame_LoadUI().
I think the bottom line is that registering is pretty safe in OnInitialize, since AceAddon should usually (always?) only initialize one addon at a time.
However, another surprising note that I didn't investigate too thoroughly yet. When I run this code in contrast to the code in the original post, it works. As far as I know, this is simulating the OnEnable in AceAddon (only as far as which events trigger which functions), but for some reason it is different:
I thought it might have something to do with the safecall, but even removing that in AceAddon does not change it. It sounds like there is something in between this example and the first one I posted that causes this bug.
Firstly, my summarized description of the bug:
If you call LoadAddOn() before PLAYER_LOGIN has fired, any frames that attempted to register ADDON_LOADED in the execution path before the LoadAddOn() call will silently fail to register.
And here's the reason why I observed it in AceAddon:
For some reason, IsLoggedIn() is reporting true before PLAYER_LOGIN fires. Doing a simple print during both the iteration of the enablequeue and when PLAYER_LOGIN fires, you can note that the enablequeue is done first with a difference in GetTime(), which likely means they're at least 1 frame apart. In most cases this difference of API and event might not matter, but as far as I can tell this is what is causing the above bug.
However, the return of query functions like IsLoggedIn() are sometimes not up-to-date though the corresponding event fired.
What you said might be possible but I would not use GetTime() return value as an evidence.
PS: if you want to test it, just update an upvalue in an onupdate handler :
Be that as it is, the order in the chat log always prints:
Enabling Addons (IsLoggedIn() returned true)
PLAYER_LOGIN fired
And last but not least, the algorithm of AceAddon initialization is this :
Each time PLAYER_LOGIN *or* ADDON_LOADED happens :
- while there are addons to initialize, initialize them, handling recursion (addons loaded or modules created during the execution of OnInitialize),
- if IsLoggedIn() returns true, while there are addons to enable, enable them, handling "enable recursion" (addons/modules enabled during the execution of OnEnable)
Note that ADDON_LOADED happens before and after PLAYER_LOGIN (LoadOnDemand).
Actually I'm not even sure what happens when you call LoadAddOn (does it trigger and resolve another ADDON_LOADED before returning LoadAddOn ? do the listener registered just before LoadAddOn catch the event ?). In my memory, nasty things happen with the dependencies handled by the game client itself. I remember talking about this with Tekkub on these forums.
There is no real solution (unless Ace3 removes IsLoggedIn() completely and instead sets a flag when the PLAYER_LOGIN event fires), but there are easy ways to prevent it, such as registering ADDON_LOADED in OnInitialize only or not using LoadAddOn anywhere in OnInitialize or OnEnable.
so if you have an addon responding to an ADDON_LOADED event and it manually loads an addon that registers an ADDON_LOADED event handler, that second addon will never see the event because its handler was never actually registered due to the ADDON_LOADED event being active at the time the register function was called.
presumably, this is true for other events. unless they've fixed it, anyways... it was a while ago.
Anyway, calling LoadAddOn in addon OnInitialize and OnEnable seems a very bad idea.
if your addon interacts with other addons, it seems like ensuring they're loaded as part of your initialization makes sense.
The event is never registered at all. This is especially troubling with AceEvent, because after it thinks it registered it, it will not try again for any subsequent addons yet it will never receive any calls to pass on to the addons.
And also remember that this is not necessarily in your addon. If you are registering the event and someone else is loading the addon and you both use Ace, there is a chance that you will be affected.