flickerstreak@28: --[[ $Id: AceAddon-3.0.lua 63220 2008-02-29 11:29:58Z nevcairiel $ ]] flickerstreak@28: local MAJOR, MINOR = "AceAddon-3.0", 3 flickerstreak@28: local AceAddon, oldminor = LibStub:NewLibrary(MAJOR, MINOR) flickerstreak@28: flickerstreak@28: if not AceAddon then return end -- No Upgrade needed. flickerstreak@28: flickerstreak@28: AceAddon.frame = AceAddon.frame or CreateFrame("Frame", "AceAddon30Frame") -- Our very own frame flickerstreak@28: AceAddon.addons = AceAddon.addons or {} -- addons in general flickerstreak@28: AceAddon.statuses = AceAddon.statuses or {} -- statuses of addon. flickerstreak@28: AceAddon.initializequeue = AceAddon.initializequeue or {} -- addons that are new and not initialized flickerstreak@28: AceAddon.enablequeue = AceAddon.enablequeue or {} -- addons that are initialized and waiting to be enabled flickerstreak@28: AceAddon.embeds = AceAddon.embeds or setmetatable({}, {__index = function(tbl, key) tbl[key] = {} return tbl[key] end }) -- contains a list of libraries embedded in an addon flickerstreak@28: flickerstreak@28: local tinsert, tconcat = table.insert, table.concat flickerstreak@28: local fmt = string.format flickerstreak@28: local pairs, next, type = pairs, next, type flickerstreak@28: flickerstreak@28: --[[ flickerstreak@28: xpcall safecall implementation flickerstreak@28: ]] flickerstreak@28: local xpcall = xpcall flickerstreak@28: flickerstreak@28: local function errorhandler(err) flickerstreak@28: return geterrorhandler()(err) flickerstreak@28: end flickerstreak@28: flickerstreak@28: local function CreateDispatcher(argCount) flickerstreak@28: local code = [[ flickerstreak@28: local xpcall, eh = ... flickerstreak@28: local method, ARGS flickerstreak@28: local function call() return method(ARGS) end flickerstreak@28: flickerstreak@28: local function dispatch(func, ...) flickerstreak@28: method = func flickerstreak@28: if not method then return end flickerstreak@28: ARGS = ... flickerstreak@28: return xpcall(call, eh) flickerstreak@28: end flickerstreak@28: flickerstreak@28: return dispatch flickerstreak@28: ]] flickerstreak@28: flickerstreak@28: local ARGS = {} flickerstreak@28: for i = 1, argCount do ARGS[i] = "arg"..i end flickerstreak@28: code = code:gsub("ARGS", tconcat(ARGS, ", ")) flickerstreak@28: return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler) flickerstreak@28: end flickerstreak@28: flickerstreak@28: local Dispatchers = setmetatable({}, {__index=function(self, argCount) flickerstreak@28: local dispatcher = CreateDispatcher(argCount) flickerstreak@28: rawset(self, argCount, dispatcher) flickerstreak@28: return dispatcher flickerstreak@28: end}) flickerstreak@28: Dispatchers[0] = function(func) flickerstreak@28: return xpcall(func, errorhandler) flickerstreak@28: end flickerstreak@28: flickerstreak@28: local function safecall(func, ...) flickerstreak@28: -- we check to see if the func is passed is actually a function here and don't error when it isn't flickerstreak@28: -- this safecall is used for optional functions like OnInitialize OnEnable etc. When they are not flickerstreak@28: -- present execution should continue without hinderance flickerstreak@28: if type(func) == "function" then flickerstreak@28: return Dispatchers[select('#', ...)](func, ...) flickerstreak@28: end flickerstreak@28: end flickerstreak@28: flickerstreak@28: -- local functions that will be implemented further down flickerstreak@28: local Enable, Disable, EnableModule, DisableModule, Embed, NewModule, GetModule, GetName, SetDefaultModuleState, SetDefaultModuleLibraries, SetEnabledState, SetDefaultModulePrototype flickerstreak@28: flickerstreak@28: -- used in the addon metatable flickerstreak@28: local function addontostring( self ) return self.name end flickerstreak@28: flickerstreak@28: -- AceAddon:NewAddon( name, [lib, lib, lib, ...] ) flickerstreak@28: -- name (string) - unique addon object name flickerstreak@28: -- [lib] (string) - optional libs to embed in the addon object flickerstreak@28: -- flickerstreak@28: -- returns the addon object when succesful flickerstreak@28: function AceAddon:NewAddon(name, ...) flickerstreak@28: if type(name) ~= "string" then error(("Usage: NewAddon(name, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) end flickerstreak@28: flickerstreak@28: if self.addons[name] then error(("Usage: NewAddon(name, [lib, lib, lib, ...]): 'name' - Addon '%s' already exists."):format(name), 2) end flickerstreak@28: flickerstreak@28: local addon = setmetatable( {name = name}, { __tostring = addontostring } ) flickerstreak@28: self.addons[name] = addon flickerstreak@28: addon.modules = {} flickerstreak@28: addon.defaultModuleLibraries = {} flickerstreak@28: Embed( addon ) -- embed NewModule, GetModule methods flickerstreak@28: self:EmbedLibraries(addon, ...) flickerstreak@28: flickerstreak@28: -- add to queue of addons to be initialized upon ADDON_LOADED flickerstreak@28: tinsert(self.initializequeue, addon) flickerstreak@28: return addon flickerstreak@28: end flickerstreak@28: flickerstreak@28: -- AceAddon:GetAddon( name, [silent]) flickerstreak@28: -- name (string) - unique addon object name flickerstreak@28: -- silent (boolean) - if true, addon is optional, silently return nil if its not found flickerstreak@28: -- flickerstreak@28: -- throws an error if the addon object can not be found (except silent is set) flickerstreak@28: -- returns the addon object if found flickerstreak@28: function AceAddon:GetAddon(name, silent) flickerstreak@28: if not silent and not self.addons[name] then flickerstreak@28: error(("Usage: GetAddon(name): 'name' - Cannot find an AceAddon '%s'."):format(tostring(name)), 2) flickerstreak@28: end flickerstreak@28: return self.addons[name] flickerstreak@28: end flickerstreak@28: flickerstreak@28: -- AceAddon:EmbedLibraries( addon, [lib, lib, lib, ...] ) flickerstreak@28: -- addon (object) - addon to embed the libs in flickerstreak@28: -- [lib] (string) - optional libs to embed flickerstreak@28: function AceAddon:EmbedLibraries(addon, ...) flickerstreak@28: for i=1,select("#", ... ) do flickerstreak@28: local libname = select(i, ...) flickerstreak@28: self:EmbedLibrary(addon, libname, false, 4) flickerstreak@28: end flickerstreak@28: end flickerstreak@28: flickerstreak@28: -- AceAddon:EmbedLibrary( addon, libname, silent, offset ) flickerstreak@28: -- addon (object) - addon to embed the libs in flickerstreak@28: -- libname (string) - lib to embed flickerstreak@28: -- [silent] (boolean) - optional, marks an embed to fail silently if the library doesn't exist. flickerstreak@28: -- [offset] (number) - will push the error messages back to said offset defaults to 2 flickerstreak@28: function AceAddon:EmbedLibrary(addon, libname, silent, offset) flickerstreak@28: local lib = LibStub:GetLibrary(libname, true) flickerstreak@28: if not lib and not silent then flickerstreak@28: error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Cannot find a library instance of %q."):format(tostring(libname)), offset or 2) flickerstreak@28: elseif lib and type(lib.Embed) == "function" then flickerstreak@28: lib:Embed(addon) flickerstreak@28: tinsert(self.embeds[addon], libname) flickerstreak@28: return true flickerstreak@28: elseif lib then flickerstreak@28: error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Library '%s' is not Embed capable"):format(libname), offset or 2) flickerstreak@28: end flickerstreak@28: end flickerstreak@28: flickerstreak@28: -- addon:GetModule( name, [silent]) flickerstreak@28: -- name (string) - unique module object name flickerstreak@28: -- silent (boolean) - if true, module is optional, silently return nil if its not found flickerstreak@28: -- flickerstreak@28: -- throws an error if the addon object can not be found (except silent is set) flickerstreak@28: -- returns the module object if found flickerstreak@28: function GetModule(self, name, silent) flickerstreak@28: if not self.modules[name] and not silent then flickerstreak@28: error(("Usage: GetModule(name, silent): 'name' - Cannot find module '%s'."):format(tostring(name)), 2) flickerstreak@28: end flickerstreak@28: return self.modules[name] flickerstreak@28: end flickerstreak@28: flickerstreak@28: local function IsModuleTrue(self) return true end flickerstreak@28: flickerstreak@28: -- addon:NewModule( name, [prototype, [lib, lib, lib, ...] ) flickerstreak@28: -- name (string) - unique module object name for this addon flickerstreak@28: -- prototype (object) - object to derive this module from, methods and values from this table will be mixed into the module, if a string is passed a lib is assumed flickerstreak@28: -- [lib] (string) - optional libs to embed in the addon object flickerstreak@28: -- flickerstreak@28: -- returns the addon object when succesful flickerstreak@28: function NewModule(self, name, prototype, ...) flickerstreak@28: if type(name) ~= "string" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) end flickerstreak@28: if type(prototype) ~= "string" and type(prototype) ~= "table" and type(prototype) ~= "nil" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'prototype' - table (prototype), string (lib) or nil expected got '%s'."):format(type(prototype)), 2) end flickerstreak@28: flickerstreak@28: if self.modules[name] then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - Module '%s' already exists."):format(name), 2) end flickerstreak@28: flickerstreak@28: -- modules are basically addons. We treat them as such. They will be added to the initializequeue properly as well. flickerstreak@28: -- NewModule can only be called after the parent addon is present thus the modules will be initialized after their parent is. flickerstreak@28: local module = AceAddon:NewAddon(fmt("%s_%s", self.name or tostring(self), name)) flickerstreak@28: flickerstreak@28: module.IsModule = IsModuleTrue flickerstreak@28: module:SetEnabledState(self.defaultModuleState) flickerstreak@28: module.moduleName = name flickerstreak@28: flickerstreak@28: if type(prototype) == "string" then flickerstreak@28: AceAddon:EmbedLibraries(module, prototype, ...) flickerstreak@28: else flickerstreak@28: AceAddon:EmbedLibraries(module, ...) flickerstreak@28: end flickerstreak@28: AceAddon:EmbedLibraries(module, unpack(self.defaultModuleLibraries)) flickerstreak@28: flickerstreak@28: if not prototype or type(prototype) == "string" then flickerstreak@28: prototype = self.defaultModulePrototype or nil flickerstreak@28: end flickerstreak@28: flickerstreak@28: if type(prototype) == "table" then flickerstreak@28: local mt = getmetatable(module) flickerstreak@28: mt.__index = prototype flickerstreak@28: setmetatable(module, mt) -- More of a Base class type feel. flickerstreak@28: end flickerstreak@28: flickerstreak@28: safecall(self.OnModuleCreated, self, module) -- Was in Ace2 and I think it could be a cool thing to have handy. flickerstreak@28: self.modules[name] = module flickerstreak@28: flickerstreak@28: return module flickerstreak@28: end flickerstreak@28: flickerstreak@28: --addon:GetName() flickerstreak@28: -- Returns the real name of the addon or module, without any prefix flickerstreak@28: function GetName(self) flickerstreak@28: return self.moduleName or self.name flickerstreak@28: end flickerstreak@28: flickerstreak@28: --addon:Enable() flickerstreak@28: -- Enables the Addon if possible, return true or false depending on success flickerstreak@28: function Enable(self) flickerstreak@28: self:SetEnabledState(true) flickerstreak@28: return AceAddon:EnableAddon(self) flickerstreak@28: end flickerstreak@28: flickerstreak@28: --addon:Disable() flickerstreak@28: -- Disables the Addon if possible, return true or false depending on success flickerstreak@28: function Disable(self) flickerstreak@28: self:SetEnabledState(false) flickerstreak@28: return AceAddon:DisableAddon(self) flickerstreak@28: end flickerstreak@28: flickerstreak@28: -- addon:EnableModule( name ) flickerstreak@28: -- name (string) - unique module object name flickerstreak@28: -- flickerstreak@28: -- Enables the Module if possible, return true or false depending on success flickerstreak@28: function EnableModule(self, name) flickerstreak@28: local module = self:GetModule( name ) flickerstreak@28: return module:Enable() flickerstreak@28: end flickerstreak@28: flickerstreak@28: -- addon:DisableModule( name ) flickerstreak@28: -- name (string) - unique module object name flickerstreak@28: -- flickerstreak@28: -- Disables the Module if possible, return true or false depending on success flickerstreak@28: function DisableModule(self, name) flickerstreak@28: local module = self:GetModule( name ) flickerstreak@28: return module:Disable() flickerstreak@28: end flickerstreak@28: flickerstreak@28: -- addon:SetDefaultModuleLibraries( [lib, lib, lib, ...] ) flickerstreak@28: -- [lib] (string) - libs to embed in every module flickerstreak@28: function SetDefaultModuleLibraries(self, ...) flickerstreak@28: if next(self.modules) then flickerstreak@28: error("Usage: SetDefaultModuleLibraries(...): cannot change the module defaults after a module has been registered.", 2) flickerstreak@28: end flickerstreak@28: self.defaultModuleLibraries = {...} flickerstreak@28: end flickerstreak@28: flickerstreak@28: -- addon:SetDefaultModuleState( state ) flickerstreak@28: -- state (boolean) - default state for new modules (enabled=true, disabled=false) flickerstreak@28: function SetDefaultModuleState(self, state) flickerstreak@28: if next(self.modules) then flickerstreak@28: error("Usage: SetDefaultModuleState(state): cannot change the module defaults after a module has been registered.", 2) flickerstreak@28: end flickerstreak@28: self.defaultModuleState = state flickerstreak@28: end flickerstreak@28: flickerstreak@28: -- addon:SetDefaultModulePrototype( prototype ) flickerstreak@28: -- prototype (string or table) - the default prototype to use if none is specified on module creation flickerstreak@28: function SetDefaultModulePrototype(self, prototype) flickerstreak@28: if next(self.modules) then flickerstreak@28: error("Usage: SetDefaultModulePrototype(prototype): cannot change the module defaults after a module has been registered.", 2) flickerstreak@28: end flickerstreak@28: if type(prototype) ~= "table" then flickerstreak@28: error(("Usage: SetDefaultModulePrototype(prototype): 'prototype' - table expected got '%s'."):format(type(prototype)), 2) flickerstreak@28: end flickerstreak@28: self.defaultModulePrototype = prototype flickerstreak@28: end flickerstreak@28: flickerstreak@28: -- addon:SetEnabledState ( state ) flickerstreak@28: -- state ( boolean ) - set the state of an addon or module (enabled=true, disabled=false) flickerstreak@28: -- flickerstreak@28: -- should only be called before any Enabling actually happend, aka in OnInitialize flickerstreak@28: function SetEnabledState(self, state) flickerstreak@28: self.enabledState = state flickerstreak@28: end flickerstreak@28: flickerstreak@28: flickerstreak@28: local function IterateModules(self) return pairs(self.modules) end flickerstreak@28: local function IterateEmbeds(self) return pairs(AceAddon.embeds[self]) end flickerstreak@28: local function IsEnabled(self) return self.enabledState end flickerstreak@28: local mixins = { flickerstreak@28: NewModule = NewModule, flickerstreak@28: GetModule = GetModule, flickerstreak@28: Enable = Enable, flickerstreak@28: Disable = Disable, flickerstreak@28: EnableModule = EnableModule, flickerstreak@28: DisableModule = DisableModule, flickerstreak@28: IsEnabled = IsEnabled, flickerstreak@28: SetDefaultModuleLibraries = SetDefaultModuleLibraries, flickerstreak@28: SetDefaultModuleState = SetDefaultModuleState, flickerstreak@28: SetDefaultModulePrototype = SetDefaultModulePrototype, flickerstreak@28: SetEnabledState = SetEnabledState, flickerstreak@28: IterateModules = IterateModules, flickerstreak@28: IterateEmbeds = IterateEmbeds, flickerstreak@28: GetName = GetName, flickerstreak@28: } flickerstreak@28: local function IsModule(self) return false end flickerstreak@28: local pmixins = { flickerstreak@28: defaultModuleState = true, flickerstreak@28: enabledState = true, flickerstreak@28: IsModule = IsModule, flickerstreak@28: } flickerstreak@28: -- Embed( target ) flickerstreak@28: -- target (object) - target object to embed aceaddon in flickerstreak@28: -- flickerstreak@28: -- this is a local function specifically since it's meant to be only called internally flickerstreak@28: function Embed(target) flickerstreak@28: for k, v in pairs(mixins) do flickerstreak@28: target[k] = v flickerstreak@28: end flickerstreak@28: for k, v in pairs(pmixins) do flickerstreak@28: target[k] = target[k] or v flickerstreak@28: end flickerstreak@28: end flickerstreak@28: flickerstreak@28: flickerstreak@28: -- AceAddon:IntializeAddon( addon ) flickerstreak@28: -- addon (object) - addon to intialize flickerstreak@28: -- flickerstreak@28: -- calls OnInitialize on the addon object if available flickerstreak@28: -- calls OnEmbedInitialize on embedded libs in the addon object if available flickerstreak@28: function AceAddon:InitializeAddon(addon) flickerstreak@28: safecall(addon.OnInitialize, addon) flickerstreak@28: flickerstreak@28: local embeds = self.embeds[addon] flickerstreak@28: for i = 1, #embeds do flickerstreak@28: local lib = LibStub:GetLibrary(embeds[i], true) flickerstreak@28: if lib then safecall(lib.OnEmbedInitialize, lib, addon) end flickerstreak@28: end flickerstreak@28: flickerstreak@28: -- we don't call InitializeAddon on modules specifically, this is handled flickerstreak@28: -- from the event handler and only done _once_ flickerstreak@28: end flickerstreak@28: flickerstreak@28: -- AceAddon:EnableAddon( addon ) flickerstreak@28: -- addon (object) - addon to enable flickerstreak@28: -- flickerstreak@28: -- calls OnEnable on the addon object if available flickerstreak@28: -- calls OnEmbedEnable on embedded libs in the addon object if available flickerstreak@28: function AceAddon:EnableAddon(addon) flickerstreak@28: if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end flickerstreak@28: if self.statuses[addon.name] or not addon.enabledState then return false end flickerstreak@28: -- TODO: handle 'first'? Or let addons do it on their own? flickerstreak@28: safecall(addon.OnEnable, addon) flickerstreak@28: local embeds = self.embeds[addon] flickerstreak@28: for i = 1, #embeds do flickerstreak@28: local lib = LibStub:GetLibrary(embeds[i], true) flickerstreak@28: if lib then safecall(lib.OnEmbedEnable, lib, addon) end flickerstreak@28: end flickerstreak@28: self.statuses[addon.name] = addon.enabledState flickerstreak@28: flickerstreak@28: -- enable possible modules. flickerstreak@28: for name, module in pairs(addon.modules) do flickerstreak@28: self:EnableAddon(module) flickerstreak@28: end flickerstreak@28: flickerstreak@28: return true flickerstreak@28: end flickerstreak@28: flickerstreak@28: -- AceAddon:DisableAddon( addon ) flickerstreak@28: -- addon (object|string) - addon to disable flickerstreak@28: -- flickerstreak@28: -- calls OnDisable on the addon object if available flickerstreak@28: -- calls OnEmbedDisable on embedded libs in the addon object if available flickerstreak@28: function AceAddon:DisableAddon(addon) flickerstreak@28: if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end flickerstreak@28: if not self.statuses[addon.name] then return false end flickerstreak@28: safecall( addon.OnDisable, addon ) flickerstreak@28: local embeds = self.embeds[addon] flickerstreak@28: for i = 1, #embeds do flickerstreak@28: local lib = LibStub:GetLibrary(embeds[i], true) flickerstreak@28: if lib then safecall(lib.OnEmbedDisable, lib, addon) end flickerstreak@28: end flickerstreak@28: self.statuses[addon.name] = addon.enabledState flickerstreak@28: flickerstreak@28: -- disable possible modules. flickerstreak@28: for name, module in pairs(addon.modules) do flickerstreak@28: self:DisableAddon(module) flickerstreak@28: end flickerstreak@28: flickerstreak@28: return true flickerstreak@28: end flickerstreak@28: flickerstreak@28: --The next few funcs are just because no one should be reaching into the internal registries flickerstreak@28: --Thoughts? flickerstreak@28: function AceAddon:IterateAddons() return pairs(self.addons) end flickerstreak@28: function AceAddon:IterateEmbedsOnAddon(addon) return pairs(self.embeds[addon]) end flickerstreak@28: function AceAddon:IterateAddonStatus() return pairs(self.statuses) end flickerstreak@28: function AceAddon:IterateModulesOfAddon(addon) return pairs(addon.modules) end flickerstreak@28: flickerstreak@28: -- Event Handling flickerstreak@28: local function onEvent(this, event, arg1) flickerstreak@28: if event == "ADDON_LOADED" or event == "PLAYER_LOGIN" then flickerstreak@28: -- if a addon loads another addon, recursion could happen here, so we need to validate the table on every iteration flickerstreak@28: while(#AceAddon.initializequeue > 0) do flickerstreak@28: local addon = tremove(AceAddon.initializequeue, 1) flickerstreak@28: -- this might be an issue with recursion - TODO: validate flickerstreak@28: if event == "ADDON_LOADED" then addon.baseName = arg1 end flickerstreak@28: AceAddon:InitializeAddon(addon) flickerstreak@28: tinsert(AceAddon.enablequeue, addon) flickerstreak@28: end flickerstreak@28: flickerstreak@28: if IsLoggedIn() then flickerstreak@28: while(#AceAddon.enablequeue > 0) do flickerstreak@28: local addon = tremove(AceAddon.enablequeue, 1) flickerstreak@28: AceAddon:EnableAddon(addon) flickerstreak@28: end flickerstreak@28: end flickerstreak@28: end flickerstreak@28: end flickerstreak@28: flickerstreak@28: AceAddon.frame:RegisterEvent("ADDON_LOADED") flickerstreak@28: AceAddon.frame:RegisterEvent("PLAYER_LOGIN") flickerstreak@28: AceAddon.frame:SetScript("OnEvent", onEvent) flickerstreak@28: flickerstreak@28: -- upgrade embeded flickerstreak@28: for name, addon in pairs(AceAddon.addons) do flickerstreak@28: Embed(addon) flickerstreak@28: end