Mercurial > wow > dependencyloader
changeset 15:a46bf694050c
cleaned up Tree's methods a bit and improved documentation
Addon:Exists will now return false for Blizzard addons (needs to be handled better)
Addon.lua will now use the raw hooks from the interface module
fixed the inverted returns from IsForceLoadAvailable
EnableAddOn and LoadAddOn hooks will now skip the extra processing if the addon does not exist or is a Blizzard addon
moved the EnableAddOn queing to the interface
author | mckenziemc |
---|---|
date | Sat, 11 Dec 2010 01:48:39 -0800 |
parents | 78b28ebdc169 |
children | 1d8898cd1c82 |
files | DependencyLoader/Addon.lua DependencyLoader/DependencyLoader.lua DependencyLoader/Tree.lua |
diffstat | 3 files changed, 299 insertions(+), 262 deletions(-) [+] |
line wrap: on
line diff
--- a/DependencyLoader/Addon.lua Fri Dec 10 05:29:22 2010 -0800 +++ b/DependencyLoader/Addon.lua Sat Dec 11 01:48:39 2010 -0800 @@ -10,14 +10,11 @@ local Addon, addon = addonTable:NewClass("Addon") -Addon.enableAddon = EnableAddOn -- TODO: use raw hook from main module? - Addon.addons = {} Addon.nameToIndex = {} --- Internal function --- Creates a new Addon object +--- (private) Creates a new Addon object -- @param id Name or index of the addon. function Addon:New(id) local instance = {} @@ -55,6 +52,8 @@ -- @param id Name or index of the addon to retrieve. -- @return The Addon object, or nil if not found. function Addon:Get(id) + assert(id) + if not self:Exists(id) then return nil end @@ -94,6 +93,10 @@ return false end elseif type(addon) == "string" then + if addon:match("Blizzard_") then + return false + end + local status = select(6, GetAddOnInfo(addon)) if status == "MISSING" then @@ -102,7 +105,8 @@ return true end else - error() + local message = string.format("Unexpected argument type: \"%s\", value: %s", type(addon), addon or "nil") + error(message) end end @@ -129,6 +133,15 @@ end +function addon:IsLoaded() + if IsAddOnLoaded(self.index) then + return true + else + return false + end +end + + --- Checks if the addon is loadable. -- Considers interface issues, missing, etc. (NYI) -- Does not check dependencies. @@ -148,12 +161,14 @@ end +-- can this addon be force-loaded after the point where the client would enable it? function addon:CanForceLoadAfter() -- TODO: check Errata module return false end +-- can this addon be force-loaded before the point where the client would enable it? function addon:CanForceLoadBefore() -- TODO: check Errata module return false -- TODO: check if there's any reason addons can't be forceloaded @@ -162,13 +177,17 @@ function addon:CanForceLoad() -- FIXME: check if this would've already been loaded - return self:CanForceLoadAfter() + return self:CanForceLoadAfter() or self:CanLoD() + -- FIXME: should CanLoD() be in here? end function addon:Enable() - -- FIXME: delay this till after PLAYER_LOGIN or it'll get force-loaded - Addon.enableAddon(self.name) + if IsLoggedIn() then + addonTable.interface:QueueEnable(self.name) + else + addonTable.interface:RawEnableAddOn(self.name) + end end @@ -176,8 +195,8 @@ function addon:Load() assert(self:CanLoD()) - EnableAddOn(self.name) - LoadAddOn(self.name) + addonTable.interface:RawEnableAddOn(self.name) + addonTable.interface:RawLoadAddOn(self.name) end @@ -185,7 +204,7 @@ assert(self:CanForceLoad()) -- TODO: make sure force-loading is available at this time - EnableAddOn(self.name) -- This should cause the game to also load this addon + addonTable.interface:RawEnableAddOn(self.name) -- This should cause the game to also load this addon end @@ -208,11 +227,3 @@ return unpack(embeds) end - -function addon:IsLoaded() - if IsAddOnLoaded(self.index) then - return true - else - return false - end -end
--- a/DependencyLoader/DependencyLoader.lua Fri Dec 10 05:29:22 2010 -0800 +++ b/DependencyLoader/DependencyLoader.lua Sat Dec 11 01:48:39 2010 -0800 @@ -1,6 +1,7 @@ -- DependencyLoader -- +-- TODO: disable bootstrap if we load successfully? -- TODO: implement a feature to disable unneeded libraries when a parent -- is disabled? @@ -8,11 +9,13 @@ local addonName, addonTable = ... -local DependencyLoader = LibStub("AceAddon-3.0"):NewAddon(addonName, "AceHook-3.0") +local DependencyLoader = LibStub("AceAddon-3.0"):NewAddon(addonName, "AceHook-3.0", "AceEvent-3.0") _G[addonName] = DependencyLoader addonTable.interface = DependencyLoader +local classes = addonTable.classes + local LibPrint = LibStub("LibPrint-1.0") local LibScriptLink = LibStub("LibScriptLink-1.0") @@ -20,6 +23,8 @@ DependencyLoader.printStream = LibPrint:NewStream("DependencyLoader", "DpLdr", print) DependencyLoader.debugStream = LibPrint:NewStream("DependencyLoader", "DpLdr", "mcm") +DependencyLoader.queuedEnables = {} -- addons queued to be enabled after PLAYER_LOGIN + function DependencyLoader:Print(...) self.printStream:Print(...) @@ -38,6 +43,11 @@ function DependencyLoader:OnEnable() + -- this may get called early so don't rely on + -- it as an indicator for PLAYER_LOGIN + + self:RegisterEvent("PLAYER_LOGIN") + self:RawHook("LoadAddOn", true) self:RawHook("EnableAddOn", true) @@ -47,6 +57,13 @@ end +function DependencyLoader:PLAYER_LOGIN(...) + self:Debug(...) + + self:ProcessEnableQueue() +end + + function DependencyLoader:OnDisable() self:Print("Disabled", addonName) end @@ -54,7 +71,11 @@ -- Does not consider user settings or addon errata. function DependencyLoader:IsForceLoadAvailable() - return IsLoggedIn() and true or false + if IsLoggedIn() then + return false + else + return true + end end @@ -75,7 +96,7 @@ local requestReload = false for i=1, GetNumAddOns() do - local addon = addonTable.classes.Addon:Get(i) + local addon = classes.Addon:Get(i) -- TODO: what if an addon was loaded but its deps were then disabled? if addon:IsEnabled() and not addon:IsLoaded() then @@ -92,65 +113,96 @@ end +-- FIXME: use pcall in EnableAddOn and LoadAddOn, so that if my part errors, +-- it can still use the unhooked version + function DependencyLoader:EnableAddOn(...) local id = ... - local addon = addonTable.classes.Addon:Get(id) - local tree = addonTable.classes.Tree:Get(addon) - local requestReload = false + self:Debug("EnableAddOn", ...) - if self:CanForceLoad() then - -- NOTE: if we can force-load, then will enabling LoD addons cause them to load too? - -- A: no, they will still wait for LoadAddOn + -- even though we know EnableAddOn can cause force-loading before PLAYER_LOGIN, + -- DO NOT attempt to "fix" it: another addon that -does- know about + -- the different behavior might call our hook. + + if classes.Addon:Exists(id) then + local addon = classes.Addon:Get(id) + local tree = classes.Tree:Get(addon) - -- Can the addon be loaded on demand if force-loading is - -- allowed for its dependencies - -- if so, enable all deps and force-load if nec. - -- deps will get enabled if all parents are lod, force-loaded - -- if any parent can't be loaded on demand - -- else - -- if the addon is not loadable on demand but the tree can be - -- force-loaded, then force-load it all - -- deps will all get loaded since req. for root to load - -- else - -- if it can be loaded with a reloadui then prepare after login - -- else - -- it can't be loaded, maybe tell the user + local requestReload = false - if tree:CanLoDWithForce() then - tree:PrepareForLoD() - elseif tree:CanForceLoad() then - tree:ForceLoad() - elseif tree:CanLoad() then - tree:PrepareForReload() - requestReload = true + if self:CanForceLoad() then + -- NOTE: if we can force-load, then will enabling LoD addons cause them to load too? + -- A: no, they will still wait for LoadAddOn + + -- Can the addon be loaded on demand if force-loading is + -- allowed for its dependencies + -- if so, enable all deps and force-load if nec. + -- deps will get enabled if all parents are lod, force-loaded + -- if any parent can't be loaded on demand + -- else + -- if the addon is not loadable on demand but the tree can be + -- force-loaded, then force-load it all + -- deps will all get loaded since req. for root to load + -- else + -- if it can be loaded with a reloadui then prepare after login + -- else + -- it can't be loaded, maybe tell the user + + if tree:CanForceLoad() then + if addon:CanLoD() then + tree:PrepareForLoD() + else + tree:ForceLoad() + end + elseif tree:CanLoad() then + tree:PrepareForLoad() + requestReload = true + else + -- TODO: tell user + end + + + --[[ + if tree:CanLoDWithForce() then + tree:PrepareForLoD() + elseif tree:CanForceLoad() then + tree:ForceLoad() + elseif tree:CanLoad() then + tree:PrepareForLoad() + requestReload = true + else + -- TODO: tell user + end + ]] else - -- TODO: tell user + -- if it can be loaded on demand (deps are loaded or LoD) then + -- prepare it (enable all deps) + -- else + -- if it can be loaded (and isn't already) then + -- if force loading is available, we have to wait, then enable everything + -- else + -- prepare for reload (TODO: move this check and similar to PLAYER_LOGOUT) + -- else + -- can't be loaded, maybe tell the user + + if tree:CanLoD() then + tree:PrepareForLoad() + -- don't actually intend to reload, just enable everything + elseif tree:CanLoad() then + tree:PrepareForLoad() + requestReload = true + else + -- TODO: tell user + end end - else - -- if it can be loaded on demand (deps are loaded or LoD) then - -- prepare it (enable all deps) - -- else - -- if it can be loaded (and isn't already) then - -- if force loading is available, we have to wait, then enable everything - -- else - -- prepare for reload (TODO: move this check and similar to PLAYER_LOGOUT) - -- else - -- can't be loaded, maybe tell the user - if tree:CanLoD() then - tree:PrepareForReload() - -- don't actually intend to reload, just enable everything - elseif tree:CanLoad() then - tree:PrepareForReload() - requestReload = true - else - -- TODO: tell user + if requestReload then + self:RequestReload() end end - -- TODO: requestReload - + -- propogate the call even if it doesn't exist or deps are unavailable return self.hooks.EnableAddOn(...) end @@ -160,16 +212,11 @@ function DependencyLoader:LoadAddOn(...) local id = ... - local isBlizzardAddon = false + self:Debug("LoadAddOn", ...) - if type(id) == "string" and string.match(id, "Blizzard_") then - self:Debug("Asked to load Blizzard addon", id, ", skipping") - isBlizzardAddon = true - end - - if not isBlizzardAddon then - local addon = addonTable.classes.Addon:Get(id) - local tree = addonTable.classes.Tree:Get(addon) + if classes.Addon:Exists(id) then + local addon = classes.Addon:Get(id) + local tree = classes.Tree:Get(addon) if tree:CanLoD() then tree:PrepareForLoD() @@ -181,3 +228,31 @@ -- call even if it can't be loaded so regular returns appear return self.hooks.LoadAddOn(...) end + + +function DependencyLoader:RequestReload() + -- TODO: this should only run once +end + + +function DependencyLoader:QueueEnable(name) + self.queuedEnables[name] = true +end + + +function DependencyLoader:RawEnableAddOn(...) + return self.hooks.EnableAddOn(...) +end + + +function DependencyLoader:RawLoadAddOn(...) + return self.hooks.LoadAddOn(...) +end + + +function DependencyLoader:ProcessEnableQueue() + for addon in pairs(self.queuedEnables) do + self:RawEnableAddOn(addon) + self.queuedEnables[addon] = nil + end +end
--- a/DependencyLoader/Tree.lua Fri Dec 10 05:29:22 2010 -0800 +++ b/DependencyLoader/Tree.lua Sat Dec 11 01:48:39 2010 -0800 @@ -12,15 +12,18 @@ local Tree, tree = addonTable:NewClass("Tree") +local classes = addonTable.classes + + Tree.trees = {} --- internal --- Creates a new tree object +--- (private) Creates a new tree object. +-- Assumptions: root addon exists and is not a Blizzard addon. -- @param root Name, index, or Addon object of the root addon. function Tree:New(root) if type(root) ~= "table" then - root = addonTable.classes.Addon:Get(root) + root = classes.Addon:Get(root) end local instance = {} @@ -33,10 +36,11 @@ --- Retrieves the tree rooted at the specified addon +-- Assumptions: root addon exists and is not a Blizzard addon -- @param root Name, index, or Addon object of the root. function Tree:Get(root) if type(root) ~= "table" then - root = addonTable.classes.Addon:Get(root) + root = classes.Addon:Get(root) end local tree = self.trees[root] @@ -51,28 +55,40 @@ end --- Checks if the tree rooted at the specified addon +-- NOTE: All tree:Can functions should check the root too. +-- That way, the hooks don't have to do "if addon:Can_ and tree:Can_", +-- plus the tree can cache info that includes the capabilities of the +-- root addon. + + +--- Checks if the tree rooted at the specified addon -- can be loaded if all dependencies are enabled -- and the UI is reloaded. -- Basically makes sure all dependencies are installed. function tree:CanLoad() - if not self.root:CanLoad() then + local root = self.root + + if root:IsLoaded() then + return true + end + + if not root:CanLoad() then return false end -- check all the dependencies - local dependencies = {self.root:GetDependencies()} + local dependencies = {root:GetDependencies()} + for i, depName in pairs(dependencies) do - local dep = addonTable.classes.Addon:Get(depName) - - if not dep:CanLoad() then + if not classes.Addon:Exists(depName) then return false end - local depTree = Tree:Get(depName) + local dep = classes.Addon:Get(depName) + local depTree = Tree:Get(dep) if not depTree:CanLoad() then - return false -- missing dependency + return false end end @@ -81,25 +97,29 @@ --- Checks if the tree can be loaded on demand. --- Does not consider force-loading; dependencies must --- be enabled and LoD, or loaded +-- Does not allow for force-loading; dependencies must +-- already be loaded, or enabled and LoD. function tree:CanLoD() - -- since this will be used recursively, return true if - -- this is already loaded. - if self.root:IsLoaded() then + local root = self.root + + if root:IsLoaded() then return true end - -- true if all dependencies are loaded or LoD - - if not self.root:CanLoD() then + if not root:CanLoD() then return false end -- now check dependencies recursively - local dependencies = {self.root:GetDependencies()} + local dependencies = {root:GetDependencies()} + for i, depName in pairs(dependencies) do - local depTree = Tree:Get(depName) + if not classes.Addon:Exists(depName) then + return false + end + + local dep = classes.Addon:Get(depName) + local depTree = Tree:Get(dep) if not depTree:CanLoD() then return false @@ -110,31 +130,35 @@ end ---- Checks if this tree can be loaded on demand as long as --- force-loading is allowed for preparing dependencies. -function tree:CanLoDWithForce() - if not self.root:CanLoD() then +--- Checks if this tree can be force-loaded. +-- Does not check user settings nor if force-loading is actually available. +-- @return canForceLoad True if this tree can be force loaded, false otherwise +function tree:CanForceLoad() + local root = self.root + -- TODO: remove redundencies (for now they're here for design flexibility) + + -- this addon must be loaded, able to load-on-demand, or able to force-load + if root:IsLoaded() then + return true + end + + if not root:CanForceLoad() then return false end -- now check dependencies recursively - local dependencies = {self.root:GetDependencies()} + local dependencies = {root:GetDependencies()} + for i, depName in pairs(dependencies) do - local dep = addonTable.classes.Addon:Get(depName) - local depTree = Tree:Get(depName) - - if not dep:CanLoad() then + if not classes.Addon:Exists(depName) then return false end - if dep:CanLoD() then - if not depTree:CanLoDWithForce() then - return false - end - elseif dep:CanForceLoad() then - if not depTree:CanForceLoad() then - return - end + local dep = classes.Addon:Get(depName) + local depTree = Tree:Get(dep) + + if not depTree:CanForceLoad() then + return false end end @@ -142,94 +166,31 @@ end +--- Prepares this tree to be loaded, whether through +-- a ui reload or by loading on demand. +-- Assumptions: CanLoad is true for this tree +function tree:PrepareForLoad() + local root = self.root + + -- The Addon class will take care of delaying enables + -- till after PLAYER_LOGIN if necessary. + + root:Enable() + + -- enable dependencies + local dependencies = {root:GetDependencies()} + + for i, depName in pairs(dependencies) do + Tree:Get(depName):PrepareForLoad() + end ---- Checks if this tree can be force-loaded. --- Does not check user settings nor if force-loading is actually available. --- @return canForceLoad True if this tree can be force loaded, false otherwise -function tree:CanForceLoad() - -- TODO: remove redundencies (for now they're here for design flexibility) + -- prepare embeds, if they are available separately + local embeds = {root:GetEmbeds(addon)} - -- this addon must be loaded, able to load-on-demand, or able to force-load - if self.root:IsLoaded() then - return true - end - - if not self.root:CanLoad() then - return false - end - - if not self.root:CanForceLoad() then - return false - end - - -- now check dependencies recursively - local dependencies = {self.root:GetDependencies()} - for i, depName in pairs(dependencies) do - local dep = addonTable.classes.Addon:Get(depName) - local depTree = Tree:Get(depName) - - if not dep:CanLoad() then - return false + for i, embedName in pairs(embeds) do + if classes.Addon:Exists(embedName) then + Tree:Get(embedName):PrepareForLoad() end - - if not dep:CanLoD() and not dep:CanForceLoad() then - return false - end - - if not depTree:CanForceLoad() then - return false - end - end - - return -end - - - ---[[ --- Loads the tree rooted at the specified addon. --- FIXME: load the root too or not? --- Supports both LoD addons and those that require force-loading. -function tree:Load(addon) - -- don't check if the tree can actually be loaded. - -- external code should do that itself to check if it - -- should even call this at all. - - if self:ForceLoadAvailable() then - -- LoD trees can also be force-loaded - self:ForceLoadTree(addon) - else - self:LoadLoDTree(addon) - end -end -]] - - -function tree:PrepareForReload() - -- if force-loading is enabled then we have to queue this if the addon isn't lod - local function callback() - self.root:Enable() - - -- check dependencies - local dependencies = {self.root:GetDependencies()} - for i, depName in pairs(dependencies) do - Tree:Get(depName):PrepareForReload() - end - - -- prepare embeds, if they are available separately - local embeds = {self.root:GetEmbeds(addon)} - for i, embedName in pairs(embeds) do - Tree:Get(embedName):PrepareForReload() - end - end - - if IsLoggedIn() then - callback() - else - -- TODO: replace with cleaner alternative? - local frame = CreateFrame("Frame") - frame:SetScript("OnEvent", callback) - frame:RegisterEvent("PLAYER_LOGIN") end end @@ -237,89 +198,79 @@ -- I think the problem this solves is a major issue with -- migrating to separate libs. think about it more and document -- here and in project description + +--- Force-loads this addon tree. If a subtree can be loaded on +-- demand, this function will enable but not load the subtree. +-- Assumptions: the root addon should be force-loaded, not just enabled. +--function tree:ForceLoad() + +-- Assumptions: the root is loadable-on demand, and force-load is available. +-- and tree can be force-loaded function tree:PrepareForLoD() - -- assume root is LoD + local root = self.root - -- check dependencies - local dependencies = {self.root:GetDependencies(addon)} + -- prepare dependencies first (for consistency) + local dependencies = {root:GetDependencies()} + for i, depName in pairs(dependencies) do - local depTree = Tree:Get(depName) - depTree:PrepareForLoD() + local dep = classes.Addon:Get(depName) - --[[ - if dep:CanLoD() then - -- don't load it now but make sure its dependencies are prepared - self:PrepareLoDTree(depName) - else - -- FIXME: if it's already loaded - -- force-load it now so we can load the parent on demand - self:ForceLoadTree(depName) + if not dep:IsLoaded() then + if dep:CanLoD() then + -- don't load it now but make sure its dependencies are prepared + Tree:Get(dep):PrepareForLoD() + else + -- Based on our assumption about the tree, this addon + -- should be able to force-load. + -- force-load it now so we can load the parent on demand + Tree:Get(dep):ForceLoad() + end end - ]] end -- prepare embeds, if they are available separately - local embeds = {self.root:GetEmbeds(addon)} -- FIXME: addon? + local embeds = {root:GetEmbeds()} + for i, embedName in pairs(embeds) do - local embedTree = Tree:Get(embedName) - embedTree:PrepareForLoD() - - --[[ - if embed then - if embed:CanLoD() then - -- don't load it now but make sure its dependencies are prepared - self:PrepareLoDTree(embedName) - else - -- FIXME: if it's already loaded - -- force-load it now so we can load the parent on demand - self:ForceLoadTree(depName) + if classes.Addon:Exists(embedName) then + local embed = classes.Addon:Get(embedName) + + if not embed:IsLoaded() then + if embed:CanLoD() then + Tree:Get(embed):PrepareForLoD() + else + Tree:Get(embed):ForceLoad() + end end end - ]] end + + root:Enable() end --- load the root too, since it may actually be a leaf +--- Force-loads this tree. +-- This will also load any LoD addons in the tree function tree:ForceLoad() + local root = self.root + + -- FIXME: if already loaded + -- load dependencies - local dependencies = {self.root:GetDependencies()} + local dependencies = {root:GetDependencies()} + for i, depName in pairs(dependencies) do - local depTree = Tree:Get(depName) - depTree:ForceLoad() + Tree:Get(depName):ForceLoad() end -- load embeds, if they are available separately - local embeds = {self.root:GetEmbeds()} + local embeds = {root:GetEmbeds()} + for i, embedName in pairs(embeds) do - local embedTree = Tree:Get(embedName) - embedTree:ForceLoad() + if classes.Addon:Exists(embedName) then + Tree:Get(embedName):ForceLoad() + end end root:ForceLoad() end - - ---[[ don't think i need this -function core:LoadLoDTree(root) - root = self:GetAddon(root) - assert(root) - - -- load dependencies - local dependencies = {root:GetDependencies(addon)} - for i, depName in pairs(dependencies) do - self:LoadLoDTree(depName) - end - - -- load embeds, if they are available separately - local embeds = {root:GetEmbeds(addon)} - for i, embedName in pairs(embeds) do - if Addon:Exists(embedName) then - self:LoadLoDTree(embedName) - end - end - - root:LoD() -end -]] -