mckenziemc@10: -- DependencyLoader mckenziemc@0: -- mckenziemc@0: mckenziemc@15: -- TODO: disable bootstrap if we load successfully? mckenziemc@12: mckenziemc@14: -- TODO: implement a feature to disable unneeded libraries when a parent mckenziemc@14: -- is disabled? mckenziemc@14: mckenziemc@0: local addonName, addonTable = ... mckenziemc@0: mckenziemc@0: mckenziemc@15: local DependencyLoader = LibStub("AceAddon-3.0"):NewAddon(addonName, "AceHook-3.0", "AceEvent-3.0") mckenziemc@0: _G[addonName] = DependencyLoader mckenziemc@0: mckenziemc@10: addonTable.interface = DependencyLoader mckenziemc@10: mckenziemc@15: local classes = addonTable.classes mckenziemc@15: mckenziemc@10: mckenziemc@12: local LibPrint = LibStub("LibPrint-1.0") mckenziemc@10: local LibScriptLink = LibStub("LibScriptLink-1.0") mckenziemc@10: mckenziemc@12: DependencyLoader.printStream = LibPrint:NewStream("DependencyLoader", "DpLdr", print) mckenziemc@12: DependencyLoader.debugStream = LibPrint:NewStream("DependencyLoader", "DpLdr", "mcm") mckenziemc@0: mckenziemc@15: DependencyLoader.queuedEnables = {} -- addons queued to be enabled after PLAYER_LOGIN mckenziemc@15: mckenziemc@0: mckenziemc@0: function DependencyLoader:Print(...) mckenziemc@0: self.printStream:Print(...) mckenziemc@0: end mckenziemc@0: mckenziemc@0: mckenziemc@0: function DependencyLoader:Debug(...) mckenziemc@0: self.debugStream:Print(...) mckenziemc@0: end mckenziemc@0: mckenziemc@0: mckenziemc@0: function DependencyLoader:OnInitialize() mckenziemc@10: self:Debug("Initializing", addonName) mckenziemc@0: self:Enable() mckenziemc@0: end mckenziemc@0: mckenziemc@0: mckenziemc@0: function DependencyLoader:OnEnable() mckenziemc@15: -- this may get called early so don't rely on mckenziemc@15: -- it as an indicator for PLAYER_LOGIN mckenziemc@15: mckenziemc@15: self:RegisterEvent("PLAYER_LOGIN") mckenziemc@15: mckenziemc@12: self:RawHook("LoadAddOn", true) mckenziemc@12: self:RawHook("EnableAddOn", true) mckenziemc@12: mckenziemc@12: self:PrepareAllAddons() mckenziemc@12: mckenziemc@10: self:Print("Enabled", addonName) mckenziemc@0: end mckenziemc@0: mckenziemc@10: mckenziemc@15: function DependencyLoader:PLAYER_LOGIN(...) mckenziemc@15: self:Debug(...) mckenziemc@15: mckenziemc@15: self:ProcessEnableQueue() mckenziemc@15: end mckenziemc@15: mckenziemc@15: mckenziemc@0: function DependencyLoader:OnDisable() mckenziemc@0: self:Print("Disabled", addonName) mckenziemc@0: end mckenziemc@0: mckenziemc@0: mckenziemc@10: -- Does not consider user settings or addon errata. mckenziemc@10: function DependencyLoader:IsForceLoadAvailable() mckenziemc@15: if IsLoggedIn() then mckenziemc@15: return false mckenziemc@15: else mckenziemc@15: return true mckenziemc@15: end mckenziemc@10: end mckenziemc@10: mckenziemc@10: mckenziemc@10: function DependencyLoader:IsForceLoadAllowed() mckenziemc@10: -- TODO: check user settings mckenziemc@10: return true mckenziemc@10: end mckenziemc@10: mckenziemc@10: mckenziemc@10: function DependencyLoader:CanForceLoad() mckenziemc@10: return self:IsForceLoadAvailable() and self:IsForceLoadAllowed() mckenziemc@10: end mckenziemc@10: mckenziemc@0: mckenziemc@0: -- Enables any dependencies needed by the addons mckenziemc@0: -- that have already been enabled mckenziemc@12: function DependencyLoader:PrepareAllAddons() mckenziemc@10: local requestReload = false mckenziemc@10: mckenziemc@0: for i=1, GetNumAddOns() do mckenziemc@15: local addon = classes.Addon:Get(i) mckenziemc@0: mckenziemc@10: -- TODO: what if an addon was loaded but its deps were then disabled? mckenziemc@10: if addon:IsEnabled() and not addon:IsLoaded() then mckenziemc@12: self:EnableAddOn(addon:GetName()) mckenziemc@0: end mckenziemc@0: end mckenziemc@10: mckenziemc@12: --[[ mckenziemc@10: if requestReload then mckenziemc@10: local message = LibScriptLink:NewLink(ReloadUI) .. " to reload your UI." mckenziemc@10: self:Print(message) mckenziemc@10: end mckenziemc@12: ]] mckenziemc@0: end mckenziemc@10: mckenziemc@10: mckenziemc@15: -- FIXME: use pcall in EnableAddOn and LoadAddOn, so that if my part errors, mckenziemc@15: -- it can still use the unhooked version mckenziemc@15: mckenziemc@10: function DependencyLoader:EnableAddOn(...) mckenziemc@12: local id = ... mckenziemc@12: mckenziemc@15: self:Debug("EnableAddOn", ...) mckenziemc@12: mckenziemc@15: -- even though we know EnableAddOn can cause force-loading before PLAYER_LOGIN, mckenziemc@15: -- DO NOT attempt to "fix" it: another addon that -does- know about mckenziemc@15: -- the different behavior might call our hook. mckenziemc@15: mckenziemc@15: if classes.Addon:Exists(id) then mckenziemc@15: local addon = classes.Addon:Get(id) mckenziemc@15: local tree = classes.Tree:Get(addon) mckenziemc@12: mckenziemc@15: local requestReload = false mckenziemc@12: mckenziemc@15: if self:CanForceLoad() then mckenziemc@15: -- NOTE: if we can force-load, then will enabling LoD addons cause them to load too? mckenziemc@15: -- A: no, they will still wait for LoadAddOn mckenziemc@15: mckenziemc@15: -- Can the addon be loaded on demand if force-loading is mckenziemc@15: -- allowed for its dependencies mckenziemc@15: -- if so, enable all deps and force-load if nec. mckenziemc@15: -- deps will get enabled if all parents are lod, force-loaded mckenziemc@15: -- if any parent can't be loaded on demand mckenziemc@15: -- else mckenziemc@15: -- if the addon is not loadable on demand but the tree can be mckenziemc@15: -- force-loaded, then force-load it all mckenziemc@15: -- deps will all get loaded since req. for root to load mckenziemc@15: -- else mckenziemc@15: -- if it can be loaded with a reloadui then prepare after login mckenziemc@15: -- else mckenziemc@15: -- it can't be loaded, maybe tell the user mckenziemc@15: mckenziemc@15: if tree:CanForceLoad() then mckenziemc@15: if addon:CanLoD() then mckenziemc@15: tree:PrepareForLoD() mckenziemc@15: else mckenziemc@15: tree:ForceLoad() mckenziemc@15: end mckenziemc@15: elseif tree:CanLoad() then mckenziemc@15: tree:PrepareForLoad() mckenziemc@15: requestReload = true mckenziemc@15: else mckenziemc@15: -- TODO: tell user mckenziemc@15: end mckenziemc@15: mckenziemc@15: mckenziemc@15: --[[ mckenziemc@15: if tree:CanLoDWithForce() then mckenziemc@15: tree:PrepareForLoD() mckenziemc@15: elseif tree:CanForceLoad() then mckenziemc@15: tree:ForceLoad() mckenziemc@15: elseif tree:CanLoad() then mckenziemc@15: tree:PrepareForLoad() mckenziemc@15: requestReload = true mckenziemc@15: else mckenziemc@15: -- TODO: tell user mckenziemc@15: end mckenziemc@15: ]] mckenziemc@12: else mckenziemc@15: -- if it can be loaded on demand (deps are loaded or LoD) then mckenziemc@15: -- prepare it (enable all deps) mckenziemc@15: -- else mckenziemc@15: -- if it can be loaded (and isn't already) then mckenziemc@15: -- if force loading is available, we have to wait, then enable everything mckenziemc@15: -- else mckenziemc@15: -- prepare for reload (TODO: move this check and similar to PLAYER_LOGOUT) mckenziemc@15: -- else mckenziemc@15: -- can't be loaded, maybe tell the user mckenziemc@15: mckenziemc@15: if tree:CanLoD() then mckenziemc@15: tree:PrepareForLoad() mckenziemc@15: -- don't actually intend to reload, just enable everything mckenziemc@15: elseif tree:CanLoad() then mckenziemc@15: tree:PrepareForLoad() mckenziemc@15: requestReload = true mckenziemc@15: else mckenziemc@15: -- TODO: tell user mckenziemc@15: end mckenziemc@12: end mckenziemc@12: mckenziemc@15: if requestReload then mckenziemc@15: self:RequestReload() mckenziemc@12: end mckenziemc@12: end mckenziemc@12: mckenziemc@15: -- propogate the call even if it doesn't exist or deps are unavailable mckenziemc@12: return self.hooks.EnableAddOn(...) mckenziemc@10: end mckenziemc@12: mckenziemc@12: mckenziemc@12: mckenziemc@12: --- Prepares the addon tree rooted at the specified addon mckenziemc@12: function DependencyLoader:LoadAddOn(...) mckenziemc@12: local id = ... mckenziemc@12: mckenziemc@15: self:Debug("LoadAddOn", ...) mckenziemc@12: mckenziemc@15: if classes.Addon:Exists(id) then mckenziemc@15: local addon = classes.Addon:Get(id) mckenziemc@15: local tree = classes.Tree:Get(addon) mckenziemc@12: mckenziemc@12: if tree:CanLoD() then mckenziemc@12: tree:PrepareForLoD() mckenziemc@12: elseif tree:CanLoad() then mckenziemc@12: tree:PrepareForReload() mckenziemc@12: end mckenziemc@12: end mckenziemc@12: mckenziemc@12: -- call even if it can't be loaded so regular returns appear mckenziemc@12: return self.hooks.LoadAddOn(...) mckenziemc@12: end mckenziemc@15: mckenziemc@15: mckenziemc@15: function DependencyLoader:RequestReload() mckenziemc@15: -- TODO: this should only run once mckenziemc@15: end mckenziemc@15: mckenziemc@15: mckenziemc@15: function DependencyLoader:QueueEnable(name) mckenziemc@15: self.queuedEnables[name] = true mckenziemc@15: end mckenziemc@15: mckenziemc@15: mckenziemc@15: function DependencyLoader:RawEnableAddOn(...) mckenziemc@15: return self.hooks.EnableAddOn(...) mckenziemc@15: end mckenziemc@15: mckenziemc@15: mckenziemc@15: function DependencyLoader:RawLoadAddOn(...) mckenziemc@15: return self.hooks.LoadAddOn(...) mckenziemc@15: end mckenziemc@15: mckenziemc@15: mckenziemc@15: function DependencyLoader:ProcessEnableQueue() mckenziemc@15: for addon in pairs(self.queuedEnables) do mckenziemc@15: self:RawEnableAddOn(addon) mckenziemc@15: self.queuedEnables[addon] = nil mckenziemc@15: end mckenziemc@15: end