Mercurial > wow > dependencyloader
changeset 0:9852fcd5e59e
initial import
author | mckenziemc |
---|---|
date | Tue, 30 Nov 2010 16:13:04 -0800 |
parents | |
children | 8ff8ff3f6395 |
files | DependencyLoader/DependencyLoader.toc DependencyLoader/bootstrap.lua DependencyLoader/start.xml DependencyLoader_Core/Addon.lua DependencyLoader_Core/Core.lua DependencyLoader_Core/DependencyLoader_Core.toc DependencyLoader_Core/class.lua DependencyLoader_Core/frontend.lua DependencyLoader_Core/start.lua DependencyLoader_Core/start.xml DependencyLoader_Errata/DependencyLoader_Errata.toc DependencyLoader_Errata/main.lua DependencyLoader_Errata/start.xml |
diffstat | 13 files changed, 654 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DependencyLoader/DependencyLoader.toc Tue Nov 30 16:13:04 2010 -0800 @@ -0,0 +1,11 @@ +## Interface: 40000 + +## Notes: Bootstrap for DependencyLoader_Core +## Author: mckenziemc + +## LoadManagers: !!!LoadFirst, AddonLoader +## X-LoadFirst: true +## X-LoadOn-Always: true + + +start.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DependencyLoader/bootstrap.lua Tue Nov 30 16:13:04 2010 -0800 @@ -0,0 +1,36 @@ +-- bootstrap.lua +-- Bootstrap code for DependencyLoader, allowing +-- the user to successfully load it without +-- explicitly enabling its own dependencies. + + +local addonName, addonTable = ... + +-- TODO: move and use dependency parsing function here? +local dependencies = {"LibStub", "LibPrint-1.0", "Ace3"} + +local canLoad = true +for _, addon in pairs(dependencies) do + local reason = select(6, GetAddOnInfo(addon)) + + if reason ~= nil and reason ~= "DISABLED" then + canLoad = false + break + end +end + +if not canLoad then + print("Can't load DependencyLoader") + return +end + + +print("Loading DependencyLoader") + +for _, addon in pairs(dependencies) do + EnableAddOn(addon) +end + +EnableAddOn("DependencyLoader_Core") +LoadAddOn("DependencyLoader_Core") +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DependencyLoader/start.xml Tue Nov 30 16:13:04 2010 -0800 @@ -0,0 +1,1 @@ +<Ui xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd"> <Script file="bootstrap.lua"/> </Ui> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DependencyLoader_Core/Addon.lua Tue Nov 30 16:13:04 2010 -0800 @@ -0,0 +1,152 @@ +-- Addon +-- Represents individual addon modules + + +local addonName, addonTable = ... + +print("running Addon.lua") + +-- NOTE: I assume that the API addon functions are +-- slightly quicker with an index than with a number. + +-- TODO: modify the dependency stuff to use the Errata module if available + +local Addon, addon = addonTable:NewClass("Addon") + +function Addon:New(id) + assert(type(id) == "number" or type(id) == "string") + + local instance = {} + + setmetatable(instance, self.instanceMetatable) + + if type(id) == "number" then + -- TODO: make sure it's in range + instance.index = id + instance.name = GetAddOnInfo(id) + else + -- FIXME: allow blizzard addons? + local index + + for i=1,GetNumAddOns() do + if GetAddOnInfo(i) == id then + index = i + break + end + end + + if index then + instance.name = GetAddOnInfo(id) + instance.index = index + else + error("Addon not found") + end + end + + return instance +end + + +-- Checks if an addon exists with the specified name. +-- @param addon Name of the addon. +-- @return True if the addon is present, false otherwise. +function Addon:Exists(addon) + if type(addon) == "number" then + if addon >= 1 and addon <= GetNumAddOns() then + return true + else + return false + end + elseif type(addon) == "string" then + local status = select(6, GetAddOnInfo(addon)) + + if status == "MISSING" then + return false + else + return true + end + else + error() + end +end + + +function addon:GetName() + return self.name +end + + +function addon:GetIndex() + return self.index +end + + +function addon:IsEnabled() + -- FIXME: written while tired; review later + local status = select(6, GetAddOnInfo(self.index)) + + if status == "DISABLED" then + return false + else + return true + end +end + +-- FIXME: an addon may be present but unloadable if loading out of date addons is disabled. +-- NOTE: CanForceLoad and CanLoD don't check the status of dependencies + +function addon:CanForceLoad() + return true -- TODO: check if there's any reason addons can't be forceloaded +end + +function addon:CanLoD() + -- FIXME: what will the client say about addons using LoadManagers if the LM was force-loaded? + if IsAddOnLoadOnDemand(self.name) then + return true + else + return false + end +end + +-- NOTE: only call for LoD, not force-loading +function addon:Load() + assert(self:CanLoD()) + + EnableAddOn(self.name) + LoadAddOn(self.name) +end + +function addon:ForceLoad() + 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 +end + + +function addon:GetDependencies() + return GetAddOnDependencies(self.index) +end + + +function addon:GetEmbeds() + local embeds = {} + + local embedString = GetAddOnMetadata(self.name, "X-Embeds") + + if embedString then + for match in string.gmatch(embedString, "[^,%s]+") do + table.insert(embeds, match) + end + end + + return unpack(embeds) +end + +function addon:IsLoaded() + if IsAddOnLoaded(self.index) then + return true + else + return false + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DependencyLoader_Core/Core.lua Tue Nov 30 16:13:04 2010 -0800 @@ -0,0 +1,298 @@ + + +local addonName, addonTable = ... + + +print("running Core.lua") + +-- TODO: prevent infinite loops in the recursive functions + + +local Core, core = addonTable:NewClass("Core") + +local Addon = addonTable.classes.Addon + +function Core:New() + local instance = {} + setmetatable(instance, self.instanceMetatable) + + instance.addons = {} + instance.nameToIndex = {} + + for i=1,GetNumAddOns() do + local newAddon = Addon:New(i) + + instance.addons[i] = newAddon + instance.nameToIndex[newAddon:GetName()] = i + end + + -- TODO: enable dependencies for any addons that have already loaded + + return instance +end + + +-- @param addon Name or index of the addon to retrieve. +-- @return The addon object, or nil if not found +function core:GetAddon(addon) + if not Addon:Exists(addon) then + return nil + end + + local index + + if type(addon) == "string" then + index = self.nameToIndex[addon] + elseif type(addon) == "number" then + index = addon + end + + return self.addons[index] +end + + +-- Checks if the tree rooted at the specified addon can be force-loaded. +-- @param root The name or index of the root addon. +function core:CanForceLoadTree(root) + -- convert root to an Addon object + root = self:GetAddon(root) + assert(root) + + if root:IsLoaded() then + return true + end + + -- check if the root itself can be force-loaded + if not root:CanForceLoad() then + return false + end + + -- now check dependencies recursively + -- FIXME: prevent infinite recursion + + local dependencies = {root:GetDependencies()} + for i, depName in pairs(dependencies) do + local dep = self:GetAddon(depName) + + if dep == nil then + return false -- missing dependency + end + + -- if it's already loaded then skip to next one + if not dep:IsLoaded() then + if not self:CanForceLoadTree(depName) then + return false + end + end + end + + return true +end + +-- NOTE: any tree that can be loaded on demand is also eligible for force-loading +-- Checks if the tree rooted at the specified addon can be loaded on demand. +-- @param root The name or index of the root addon. +function core:CanLoDTree(root) + root = self:GetAddon(root) + assert(root) + + -- since this will be used recursively, return true if + -- this is already loaded. + if root:IsLoaded() then + return true + end + + -- true if all dependencies are loaded or LoD + + if not root:CanLoD() then + return false + end + + -- now check dependencies recursively + local dependencies = {root:GetDependencies()} + for i, depName in pairs(dependencies) do + local dep = self:GetAddon(depName) + + if not dep then + return false -- missing + end + + if not dep:IsLoaded() then + if not self:CanLoDTree(depName) then + return false + end + end + end + + return true +end + + +-- Checks if the tree rooted at the specified addon +-- can be loaded if all dependencies are enabled +-- the UI is reloaded. +-- Basically makes sure all dependencies are installed. +function core:CanReloadTree(root) + -- convert root to an Addon object + root = self:GetAddon(root) + assert(root) + + if root:IsLoaded() then + return true + -- FIXME: deps may have been disabled + end + + -- TODO: make sure the root isn't incompatible + + -- now check dependencies recursively + -- FIXME: prevent infinite recursion + + local dependencies = {root:GetDependencies()} + for i, depName in pairs(dependencies) do + local dep = self:GetAddon(depName) + + if dep == nil then + return false -- missing dependency + end + + -- TODO: make sure it's compatible + end + + return true +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 core:LoadTree(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 + + +-- load the root too, since it may actually be a leaf +function core:ForceLoadTree(root) + root = self:GetAddon(root) + assert(root) + + -- load dependencies + local dependencies = {root:GetDependencies(addon)} + for i, depName in pairs(dependencies) do + self:ForceLoadTree(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:ForceLoadTree(embedName) + end + end + + root:ForceLoad() +end + + +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 + + +-- 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 +function core:PrepareLoDTree(root) + root = self:GetAddon(root) + assert(root) + + -- assume root is LoD + + -- check dependencies + local dependencies = {root:GetDependencies(addon)} + for i, depName in pairs(dependencies) do + local dep = self:GetAddon(depName) + + -- assume external code made sure it exists + + 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) + end + end + + -- prepare embeds, if they are available separately + local embeds = {root:GetEmbeds(addon)} -- FIXME: addon? + for i, embedName in pairs(embeds) do + local embed = self:GetAddon(embedName) + + 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) + end + end + end +end + + +function Core:PrepareReloadTree(addon) + root = self:GetAddon(root) + assert(root) + + root:Enable() + + -- check dependencies + local dependencies = {root:GetDependencies()} + for i, depName in pairs(dependencies) do + self:PrepareReloadTree(depName) + end + + -- prepare embeds, if they are available separately + local embeds = {root:GetEmbeds(addon)} + for i, embedName in pairs(embeds) do + local embed = self:GetAddon(embedName) + + if embed then + self:PrepareReloadTree(embedName) + end + end +end + + +function Core:ForceLoadAvailable() + return true + -- FIXME: use field and a frame registered for PLAYER_LOGIN +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DependencyLoader_Core/DependencyLoader_Core.toc Tue Nov 30 16:13:04 2010 -0800 @@ -0,0 +1,14 @@ +## Interface: 40000 + +# TODO: actually explain what DependencyLoader does. +## Notes: Core module of DependencyLoader +## Author: mckenziemc + +# TODO: figure out some way to allow AddonLoader +# to work with DependencyLoader +## Dependencies: DependencyLoader, LibStub, LibPrint-1.0, Ace3 + +## LoadOnDemand: 1 + + +start.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DependencyLoader_Core/class.lua Tue Nov 30 16:13:04 2010 -0800 @@ -0,0 +1,23 @@ +-- class.lua +-- Implements a generic class builder. + + +local addonName, addonTable = ... + + +print( string.format([[running %s\class.lua]], addonName) ) + +addonTable.classes = {} + +function addonTable:NewClass(name) + local class, prototype, metatable = {}, {}, {} + + class.prototype = prototype + + metatable.__index = prototype + class.instanceMetatable = metatable + + self.classes[name] = class + + return class, prototype +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DependencyLoader_Core/frontend.lua Tue Nov 30 16:13:04 2010 -0800 @@ -0,0 +1,83 @@ +-- main.lua +-- + +local addonName, addonTable = ... + + +local DependencyLoader = LibStub("AceAddon-3.0"):NewAddon(addonName) +_G[addonName] = DependencyLoader + +local libPrint = LibStub("LibPrint-1.0") +DependencyLoader.printStream = libPrint:NewStream("DependencyLoader", "DpLdr", print) +DependencyLoader.debugStream = libPrint:NewStream("DependencyLoader", "DpLdr", "mcm") + +-- temp +DependencyLoader.classes = addonTable.classes + +function DependencyLoader:Print(...) + self.printStream:Print(...) +end + + +function DependencyLoader:Debug(...) + self.debugStream:Print(...) +end + + +function DependencyLoader:OnInitialize() + self:Debug("Initializing and enabling", addonName) + self:Enable() +end + + +function DependencyLoader:OnEnable() + self.core = addonTable.classes.Core:New() + + self:Print("Enabled", addonName) + + self:FixCurrentAddons() +end + +function DependencyLoader:OnDisable() + self:Print("Disabled", addonName) +end + + +-- TODO: move this into core? + +-- Enables any dependencies needed by the addons +-- that have already been enabled +function DependencyLoader:FixCurrentAddons() + local core = self.core + + for i=1, GetNumAddOns() do + local addon = self.core:GetAddon(i) + + if addon:IsEnabled() then + if addon:IsLoaded() then + -- TODO: it might still help to enable its embeds + else + self:Debug("Checking", addon:GetName()) + + if addonTable.classes.Core:ForceLoadAvailable() then + if core:CanForceLoadTree(addon:GetName()) then + core:ForceLoadTree(addon:GetName()) + else + print("Can't force load", addon:GetName()) + end + else + if core:CanLoDTree(addon:GetName()) then + core:PrepareLoDTree(addon:GetName()) + else + print("Couldn't load", addon:GetName(), "on demand.") + end + end + end + end + end +end + + + + + \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DependencyLoader_Core/start.lua Tue Nov 30 16:13:04 2010 -0800 @@ -0,0 +1,21 @@ +-- start.lua +-- Initializes components of DependencyLoader + + +local addonName, addonTable = ... + + +print( string.format([[running %s\start.lua]], addonName) ) + +-- NOTE: We don't have to check if this module's dependencies +-- are available: it lists them normally in its .toc file and +-- the bootstrap module will take care of enabling them first. + +-- prepare output functions +local lp = LibStub("LibPrint-1.0") +local printStream = lp:NewStream("DependencyLoader", "DepLoader", "DepLdr", print) +local debugStream = lp:NewStream("DependencyLoader", "DepLoader", "DepLdr", "mcm") + +addonTable.print = function(...) printStream:Print(...) end +addonTable.debug = function(...) debugStream:Print(...) end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DependencyLoader_Core/start.xml Tue Nov 30 16:13:04 2010 -0800 @@ -0,0 +1,1 @@ +<ui xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd"> <script file="start.lua"/> <script file="class.lua"/> <script file="Addon.lua"/> <script file="Core.lua"/> <script file="frontend.lua"/> </ui> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DependencyLoader_Errata/DependencyLoader_Errata.toc Tue Nov 30 16:13:04 2010 -0800 @@ -0,0 +1,10 @@ +## Interface: 40000 + +## Author: mckenziemc + +## Dependencies: DependencyLoader + +## LoadOnDemand: 1 + + +start.xml