diff --git a/ProfessionMenu/Libs/AceAddon-3.0/AceAddon-3.0.lua b/ProfessionMenu/Libs/AceAddon-3.0/AceAddon-3.0.lua index 6c89654..00e4e48 100644 --- a/ProfessionMenu/Libs/AceAddon-3.0/AceAddon-3.0.lua +++ b/ProfessionMenu/Libs/AceAddon-3.0/AceAddon-3.0.lua @@ -6,31 +6,31 @@ -- * **OnEnable** which gets called during the PLAYER_LOGIN event, when most of the data provided by the game is already present. -- * **OnDisable**, which is only called when your addon is manually being disabled. -- @usage --- -- A small (but complete) addon, that doesn't do anything, +-- -- A small (but complete) addon, that doesn't do anything, -- -- but shows usage of the callbacks. -- local MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") --- +-- -- function MyAddon:OnInitialize() --- -- do init tasks here, like loading the Saved Variables, +-- -- do init tasks here, like loading the Saved Variables, -- -- or setting up slash commands. -- end --- +-- -- function MyAddon:OnEnable() -- -- Do more initialization here, that really enables the use of your addon. --- -- Register Events, Hook functions, Create Frames, Get information from +-- -- Register Events, Hook functions, Create Frames, Get information from -- -- the game that wasn't available in OnInitialize -- end -- -- function MyAddon:OnDisable() -- -- Unhook, Unregister Events, Hide frames that you created. --- -- You would probably only use an OnDisable if you want to +-- -- You would probably only use an OnDisable if you want to -- -- build a "standby" mode, or be able to toggle modules on/off. -- end -- @class file -- @name AceAddon-3.0.lua --- @release $Id: AceAddon-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $ +-- @release $Id$ -local MAJOR, MINOR = "AceAddon-3.0", 5 +local MAJOR, MINOR = "AceAddon-3.0", 13 local AceAddon, oldminor = LibStub:NewLibrary(MAJOR, MINOR) if not AceAddon then return end -- No Upgrade needed. @@ -49,10 +49,6 @@ local select, pairs, next, type, unpack = select, pairs, next, type, unpack local loadstring, assert, error = loadstring, assert, error local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget --- Global vars/functions that we don't upvalue since they might get hooked, or upgraded --- List them here for Mikk's FindGlobals script --- GLOBALS: LibStub, IsLoggedIn, geterrorhandler - --[[ xpcall safecall implementation ]] @@ -62,43 +58,12 @@ local function errorhandler(err) return geterrorhandler()(err) end -local function CreateDispatcher(argCount) - local code = [[ - local xpcall, eh = ... - local method, ARGS - local function call() return method(ARGS) end - - local function dispatch(func, ...) - method = func - if not method then return end - ARGS = ... - return xpcall(call, eh) - end - - return dispatch - ]] - - local ARGS = {} - for i = 1, argCount do ARGS[i] = "arg"..i end - code = code:gsub("ARGS", tconcat(ARGS, ", ")) - return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler) -end - -local Dispatchers = setmetatable({}, {__index=function(self, argCount) - local dispatcher = CreateDispatcher(argCount) - rawset(self, argCount, dispatcher) - return dispatcher -end}) -Dispatchers[0] = function(func) - return xpcall(func, errorhandler) -end - local function safecall(func, ...) -- we check to see if the func is passed is actually a function here and don't error when it isn't -- this safecall is used for optional functions like OnInitialize OnEnable etc. When they are not -- present execution should continue without hinderance if type(func) == "function" then - return Dispatchers[select('#', ...)](func, ...) + return xpcall(func, errorhandler, ...) end end @@ -106,17 +71,27 @@ end local Enable, Disable, EnableModule, DisableModule, Embed, NewModule, GetModule, GetName, SetDefaultModuleState, SetDefaultModuleLibraries, SetEnabledState, SetDefaultModulePrototype -- used in the addon metatable -local function addontostring( self ) return self.name end +local function addontostring( self ) return self.name end + +-- Check if the addon is queued for initialization +local function queuedForInitialization(addon) + for i = 1, #AceAddon.initializequeue do + if AceAddon.initializequeue[i] == addon then + return true + end + end + return false +end --- Create a new AceAddon-3.0 addon. --- Any libraries you specified will be embeded, and the addon will be scheduled for +-- Any libraries you specified will be embeded, and the addon will be scheduled for -- its OnInitialize and OnEnable callbacks. -- The final addon object, with all libraries embeded, will be returned. -- @paramsig [object ,]name[, lib, ...] -- @param object Table to use as a base for the addon (optional) -- @param name Name of the addon object to create -- @param lib List of libraries to embed into the addon --- @usage +-- @usage -- -- Create a simple addon object -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceEvent-3.0") -- @@ -136,10 +111,10 @@ function AceAddon:NewAddon(objectorname, ...) if type(name)~="string" then error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) end - if self.addons[name] then + if self.addons[name] then error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - Addon '%s' already exists."):format(name), 2) end - + object = object or {} object.name = name @@ -149,14 +124,15 @@ function AceAddon:NewAddon(objectorname, ...) for k, v in pairs(oldmeta) do addonmeta[k] = v end end addonmeta.__tostring = addontostring - + setmetatable( object, addonmeta ) self.addons[name] = object object.modules = {} + object.orderedModules = {} object.defaultModuleLibraries = {} Embed( object ) -- embed NewModule, GetModule methods self:EmbedLibraries(object, select(i,...)) - + -- add to queue of addons to be initialized upon ADDON_LOADED tinsert(self.initializequeue, object) return object @@ -167,7 +143,7 @@ end -- Throws an error if the addon object cannot be found (except if silent is set). -- @param name unique name of the addon object -- @param silent if true, the addon is optional, silently return nil if its not found --- @usage +-- @usage -- -- Get the Addon -- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") function AceAddon:GetAddon(name, silent) @@ -222,7 +198,7 @@ end -- @paramsig name[, silent] -- @param name unique name of the module -- @param silent if true, the module is optional, silently return nil if its not found (optional) --- @usage +-- @usage -- -- Get the Addon -- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") -- -- Get the Module @@ -245,23 +221,23 @@ local function IsModuleTrue(self) return true end -- @param name unique name of the module -- @param prototype object to derive this module from, methods and values from this table will be mixed into the module (optional) -- @param lib List of libraries to embed into the addon --- @usage +-- @usage -- -- Create a module with some embeded libraries -- MyModule = MyAddon:NewModule("MyModule", "AceEvent-3.0", "AceHook-3.0") --- +-- -- -- Create a module with a prototype -- local prototype = { OnEnable = function(self) print("OnEnable called!") end } -- MyModule = MyAddon:NewModule("MyModule", prototype, "AceEvent-3.0", "AceHook-3.0") function NewModule(self, name, prototype, ...) if type(name) ~= "string" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) end 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 - + if self.modules[name] then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - Module '%s' already exists."):format(name), 2) end - + -- modules are basically addons. We treat them as such. They will be added to the initializequeue properly as well. -- NewModule can only be called after the parent addon is present thus the modules will be initialized after their parent is. local module = AceAddon:NewAddon(fmt("%s_%s", self.name or tostring(self), name)) - + module.IsModule = IsModuleTrue module:SetEnabledState(self.defaultModuleState) module.moduleName = name @@ -276,23 +252,24 @@ function NewModule(self, name, prototype, ...) if not prototype or type(prototype) == "string" then prototype = self.defaultModulePrototype or nil end - + if type(prototype) == "table" then local mt = getmetatable(module) mt.__index = prototype setmetatable(module, mt) -- More of a Base class type feel. end - + safecall(self.OnModuleCreated, self, module) -- Was in Ace2 and I think it could be a cool thing to have handy. self.modules[name] = module - + tinsert(self.orderedModules, module) + return module end --- Returns the real name of the addon or module, without any prefix. -- @name //addon//:GetName --- @paramsig --- @usage +-- @paramsig +-- @usage -- print(MyAddon:GetName()) -- -- prints "MyAddon" function GetName(self) @@ -304,15 +281,20 @@ end -- and enabling all modules of the addon (unless explicitly disabled).\\ -- :Enable() also sets the internal `enableState` variable to true -- @name //addon//:Enable --- @paramsig --- @usage +-- @paramsig +-- @usage -- -- Enable MyModule -- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") -- MyModule = MyAddon:GetModule("MyModule") -- MyModule:Enable() function Enable(self) self:SetEnabledState(true) - return AceAddon:EnableAddon(self) + + -- nevcairiel 2013-04-27: don't enable an addon/module if its queued for init still + -- it'll be enabled after the init process + if not queuedForInitialization(self) then + return AceAddon:EnableAddon(self) + end end --- Disables the Addon, if possible, return true or false depending on success. @@ -320,8 +302,8 @@ end -- and disabling all modules of the addon.\\ -- :Disable() also sets the internal `enableState` variable to false -- @name //addon//:Disable --- @paramsig --- @usage +-- @paramsig +-- @usage -- -- Disable MyAddon -- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") -- MyAddon:Disable() @@ -334,7 +316,7 @@ end -- Short-hand function that retrieves the module via `:GetModule` and calls `:Enable` on the module object. -- @name //addon//:EnableModule -- @paramsig name --- @usage +-- @usage -- -- Enable MyModule using :GetModule -- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") -- MyModule = MyAddon:GetModule("MyModule") @@ -352,7 +334,7 @@ end -- Short-hand function that retrieves the module via `:GetModule` and calls `:Disable` on the module object. -- @name //addon//:DisableModule -- @paramsig name --- @usage +-- @usage -- -- Disable MyModule using :GetModule -- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") -- MyModule = MyAddon:GetModule("MyModule") @@ -371,7 +353,7 @@ end -- @name //addon//:SetDefaultModuleLibraries -- @paramsig lib[, lib, ...] -- @param lib List of libraries to embed into the addon --- @usage +-- @usage -- -- Create the addon object -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") -- -- Configure default libraries for modules (all modules need AceEvent-3.0) @@ -390,7 +372,7 @@ end -- @name //addon//:SetDefaultModuleState -- @paramsig state -- @param state Default state for new modules, true for enabled, false for disabled --- @usage +-- @usage -- -- Create the addon object -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") -- -- Set the default state to "disabled" @@ -410,7 +392,7 @@ end -- @name //addon//:SetDefaultModulePrototype -- @paramsig prototype -- @param prototype Default prototype for the new modules (table) --- @usage +-- @usage -- -- Define a prototype -- local prototype = { OnEnable = function(self) print("OnEnable called!") end } -- -- Set the default prototype @@ -442,8 +424,8 @@ end --- Return an iterator of all modules associated to the addon. -- @name //addon//:IterateModules --- @paramsig --- @usage +-- @paramsig +-- @usage -- -- Enable all modules -- for name, module in MyAddon:IterateModules() do -- module:Enable() @@ -452,13 +434,13 @@ local function IterateModules(self) return pairs(self.modules) end -- Returns an iterator of all embeds in the addon -- @name //addon//:IterateEmbeds --- @paramsig +-- @paramsig local function IterateEmbeds(self) return pairs(AceAddon.embeds[self]) end --- Query the enabledState of an addon. -- @name //addon//:IsEnabled --- @paramsig --- @usage +-- @paramsig +-- @usage -- if MyAddon:IsEnabled() then -- MyAddon:Disable() -- end @@ -489,32 +471,34 @@ local pmixins = { -- target (object) - target object to embed aceaddon in -- -- this is a local function specifically since it's meant to be only called internally -function Embed(target) +function Embed(target, skipPMixins) for k, v in pairs(mixins) do target[k] = v end - for k, v in pairs(pmixins) do - target[k] = target[k] or v + if not skipPMixins then + for k, v in pairs(pmixins) do + target[k] = target[k] or v + end end end -- - Initialize the addon after creation. -- This function is only used internally during the ADDON_LOADED event --- It will call the **OnInitialize** function on the addon object (if present), +-- It will call the **OnInitialize** function on the addon object (if present), -- and the **OnEmbedInitialize** function on all embeded libraries. --- +-- -- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. -- @param addon addon object to intialize function AceAddon:InitializeAddon(addon) safecall(addon.OnInitialize, addon) - + local embeds = self.embeds[addon] for i = 1, #embeds do local lib = LibStub:GetLibrary(embeds[i], true) if lib then safecall(lib.OnEmbedInitialize, lib, addon) end end - + -- we don't call InitializeAddon on modules specifically, this is handled -- from the event handler and only done _once_ end @@ -522,7 +506,7 @@ end -- - Enable the addon after creation. -- Note: This function is only used internally during the PLAYER_LOGIN event, or during ADDON_LOADED, -- if IsLoggedIn() already returns true at that point, e.g. for LoD Addons. --- It will call the **OnEnable** function on the addon object (if present), +-- It will call the **OnEnable** function on the addon object (if present), -- and the **OnEmbedEnable** function on all embeded libraries.\\ -- This function does not toggle the enable state of the addon itself, and will return early if the addon is disabled. -- @@ -532,12 +516,12 @@ end function AceAddon:EnableAddon(addon) if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end if self.statuses[addon.name] or not addon.enabledState then return false end - + -- set the statuses first, before calling the OnEnable. this allows for Disabling of the addon in OnEnable. self.statuses[addon.name] = true - + safecall(addon.OnEnable, addon) - + -- make sure we're still enabled before continueing if self.statuses[addon.name] then local embeds = self.embeds[addon] @@ -545,10 +529,11 @@ function AceAddon:EnableAddon(addon) local lib = LibStub:GetLibrary(embeds[i], true) if lib then safecall(lib.OnEmbedEnable, lib, addon) end end - + -- enable possible modules. - for name, module in pairs(addon.modules) do - self:EnableAddon(module) + local modules = addon.orderedModules + for i = 1, #modules do + self:EnableAddon(modules[i]) end end return self.statuses[addon.name] -- return true if we're disabled @@ -556,40 +541,41 @@ end -- - Disable the addon -- Note: This function is only used internally. --- It will call the **OnDisable** function on the addon object (if present), +-- It will call the **OnDisable** function on the addon object (if present), -- and the **OnEmbedDisable** function on all embeded libraries.\\ -- This function does not toggle the enable state of the addon itself, and will return early if the addon is still enabled. -- --- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. +-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. -- Use :Disable on the addon itself instead. -- @param addon addon object to enable function AceAddon:DisableAddon(addon) if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end if not self.statuses[addon.name] then return false end - + -- set statuses first before calling OnDisable, this allows for aborting the disable in OnDisable. self.statuses[addon.name] = false - + safecall( addon.OnDisable, addon ) - + -- make sure we're still disabling... - if not self.statuses[addon.name] then + if not self.statuses[addon.name] then local embeds = self.embeds[addon] for i = 1, #embeds do local lib = LibStub:GetLibrary(embeds[i], true) if lib then safecall(lib.OnEmbedDisable, lib, addon) end end -- disable possible modules. - for name, module in pairs(addon.modules) do - self:DisableAddon(module) + local modules = addon.orderedModules + for i = 1, #modules do + self:DisableAddon(modules[i]) end end - + return not self.statuses[addon.name] -- return true if we're disabled end --- Get an iterator over all registered addons. --- @usage +-- @usage -- -- Print a list of all installed AceAddon's -- for name, addon in AceAddon:IterateAddons() do -- print("Addon: " .. name) @@ -597,7 +583,7 @@ end function AceAddon:IterateAddons() return pairs(self.addons) end --- Get an iterator over the internal status registry. --- @usage +-- @usage -- -- Print a list of all enabled addons -- for name, status in AceAddon:IterateAddonStatus() do -- if status then @@ -611,9 +597,20 @@ function AceAddon:IterateAddonStatus() return pairs(self.statuses) end function AceAddon:IterateEmbedsOnAddon(addon) return pairs(self.embeds[addon]) end function AceAddon:IterateModulesOfAddon(addon) return pairs(addon.modules) end +-- Blizzard AddOns which can load very early in the loading process and mess with Ace3 addon loading +local BlizzardEarlyLoadAddons = { + Blizzard_DebugTools = true, + Blizzard_TimeManager = true, + Blizzard_BattlefieldMap = true, + Blizzard_MapCanvas = true, + Blizzard_SharedMapDataProviders = true, + Blizzard_CombatLog = true, +} + -- Event Handling local function onEvent(this, event, arg1) - if event == "ADDON_LOADED" or event == "PLAYER_LOGIN" then + -- 2020-08-28 nevcairiel - ignore the load event of Blizzard addons which occur early in the loading process + if (event == "ADDON_LOADED" and (arg1 == nil or not BlizzardEarlyLoadAddons[arg1])) or event == "PLAYER_LOGIN" then -- if a addon loads another addon, recursion could happen here, so we need to validate the table on every iteration while(#AceAddon.initializequeue > 0) do local addon = tremove(AceAddon.initializequeue, 1) @@ -622,7 +619,7 @@ local function onEvent(this, event, arg1) AceAddon:InitializeAddon(addon) tinsert(AceAddon.enablequeue, addon) end - + if IsLoggedIn() then while(#AceAddon.enablequeue > 0) do local addon = tremove(AceAddon.enablequeue, 1) @@ -638,5 +635,15 @@ AceAddon.frame:SetScript("OnEvent", onEvent) -- upgrade embeded for name, addon in pairs(AceAddon.addons) do - Embed(addon) + Embed(addon, true) +end + +-- 2010-10-27 nevcairiel - add new "orderedModules" table +if oldminor and oldminor < 10 then + for name, addon in pairs(AceAddon.addons) do + addon.orderedModules = {} + for module_name, module in pairs(addon.modules) do + tinsert(addon.orderedModules, module) + end + end end diff --git a/ProfessionMenu/Libs/AceComm-3.0/AceComm-3.0.lua b/ProfessionMenu/Libs/AceComm-3.0/AceComm-3.0.lua index ad5268f..faab36c 100644 --- a/ProfessionMenu/Libs/AceComm-3.0/AceComm-3.0.lua +++ b/ProfessionMenu/Libs/AceComm-3.0/AceComm-3.0.lua @@ -2,14 +2,14 @@ -- It'll automatically split the messages into multiple parts and rebuild them on the receiving end.\\ -- **ChatThrottleLib** is of course being used to avoid being disconnected by the server. -- --- **AceComm-3.0** can be embeded into your addon, either explicitly by calling AceComm:Embed(MyAddon) or by +-- **AceComm-3.0** can be embeded into your addon, either explicitly by calling AceComm:Embed(MyAddon) or by -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object -- and can be accessed directly, without having to explicitly call AceComm itself.\\ -- It is recommended to embed AceComm, otherwise you'll have to specify a custom `self` on all calls you -- make into AceComm. -- @class file -- @name AceComm-3.0 --- @release $Id: AceComm-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $ +-- @release $Id$ --[[ AceComm-3.0 @@ -17,24 +17,23 @@ TODO: Time out old data rotting around from dead senders? Not a HUGE deal since ]] -local MAJOR, MINOR = "AceComm-3.0", 6 +local CallbackHandler = LibStub("CallbackHandler-1.0") +local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib") +local MAJOR, MINOR = "AceComm-3.0", 14 local AceComm,oldminor = LibStub:NewLibrary(MAJOR, MINOR) if not AceComm then return end -local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0") -local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib") - -- Lua APIs local type, next, pairs, tostring = type, next, pairs, tostring local strsub, strfind = string.sub, string.find +local match = string.match local tinsert, tconcat = table.insert, table.concat local error, assert = error, assert --- Global vars/functions that we don't upvalue since they might get hooked, or upgraded --- List them here for Mikk's FindGlobals script --- GLOBALS: LibStub, DEFAULT_CHAT_FRAME, geterrorhandler +-- WoW APIs +local Ambiguate = Ambiguate AceComm.embeds = AceComm.embeds or {} @@ -42,21 +41,32 @@ AceComm.embeds = AceComm.embeds or {} local MSG_MULTI_FIRST = "\001" local MSG_MULTI_NEXT = "\002" local MSG_MULTI_LAST = "\003" +local MSG_ESCAPE = "\004" -AceComm.multipart_origprefixes = AceComm.multipart_origprefixes or {} -- e.g. "Prefix\001"="Prefix", "Prefix\002"="Prefix" -AceComm.multipart_reassemblers = AceComm.multipart_reassemblers or {} -- e.g. "Prefix\001"="OnReceiveMultipartFirst" +-- remove old structures (pre WoW 4.0) +AceComm.multipart_origprefixes = nil +AceComm.multipart_reassemblers = nil -- the multipart message spool: indexed by a combination of sender+distribution+ -AceComm.multipart_spool = AceComm.multipart_spool or {} +AceComm.multipart_spool = AceComm.multipart_spool or {} --- Register for Addon Traffic on a specified prefix --- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent) +-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent), max 16 characters -- @param method Callback to call on message reception: Function reference, or method name (string) to call on self. Defaults to "OnCommReceived" function AceComm:RegisterComm(prefix, method) if method == nil then method = "OnCommReceived" end + if #prefix > 16 then -- TODO: 15? + error("AceComm:RegisterComm(prefix,method): prefix length is limited to 16 characters") + end + if C_ChatInfo then + C_ChatInfo.RegisterAddonMessagePrefix(prefix) + else + RegisterAddonMessagePrefix(prefix) + end + return AceComm._RegisterComm(self, prefix, method) -- created by CallbackHandler end @@ -75,57 +85,55 @@ function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callb if not( type(prefix)=="string" and type(text)=="string" and type(distribution)=="string" and - (target==nil or type(target)=="string") and - (prio=="BULK" or prio=="NORMAL" or prio=="ALERT") + (target==nil or type(target)=="string" or type(target)=="number") and + (prio=="BULK" or prio=="NORMAL" or prio=="ALERT") ) then error('Usage: SendCommMessage(addon, "prefix", "text", "distribution"[, "target"[, "prio"[, callbackFn, callbackarg]]])', 2) end - - if strfind(prefix, "[\001-\009]") then - if strfind(prefix, "[\001-\003]") then - error("SendCommMessage: Characters \\001--\\003 in prefix are reserved for AceComm metadata", 2) - elseif not warnedPrefix then - -- I have some ideas about future extensions that require more control characters /mikk, 20090808 - geterrorhandler()("SendCommMessage: Heads-up developers: Characters \\004--\\009 in prefix are reserved for AceComm future extension") - warnedPrefix = true - end - end - local textlen = #text - local maxtextlen = 254 - #prefix -- 254 is the max length of prefix + text that can be sent in one message - local queueName = prefix..distribution..(target or "") + local maxtextlen = 255 -- Yes, the max is 255 even if the dev post said 256. I tested. Char 256+ get silently truncated. /Mikk, 20110327 + local queueName = prefix local ctlCallback = nil if callbackFn then - ctlCallback = function(sent) - return callbackFn(callbackArg, sent, textlen) + ctlCallback = function(sent, sendResult) + return callbackFn(callbackArg, sent, textlen, sendResult) end end - if textlen <= maxtextlen then + local forceMultipart + if match(text, "^[\001-\009]") then -- 4.1+: see if the first character is a control character + -- we need to escape the first character with a \004 + if textlen+1 > maxtextlen then -- would we go over the size limit? + forceMultipart = true -- just make it multipart, no escape problems then + else + text = "\004" .. text + end + end + + if not forceMultipart and textlen <= maxtextlen then -- fits all in one message CTL:SendAddonMessage(prio, prefix, text, distribution, target, queueName, ctlCallback, textlen) else - maxtextlen = maxtextlen - 1 -- 1 extra byte for part indicator in prefix + maxtextlen = maxtextlen - 1 -- 1 extra byte for part indicator in prefix(4.0)/start of message(4.1) -- first part local chunk = strsub(text, 1, maxtextlen) - CTL:SendAddonMessage(prio, prefix..MSG_MULTI_FIRST, chunk, distribution, target, queueName, ctlCallback, maxtextlen) + CTL:SendAddonMessage(prio, prefix, MSG_MULTI_FIRST..chunk, distribution, target, queueName, ctlCallback, maxtextlen) -- continuation local pos = 1+maxtextlen - local prefix2 = prefix..MSG_MULTI_NEXT while pos+maxtextlen <= textlen do chunk = strsub(text, pos, pos+maxtextlen-1) - CTL:SendAddonMessage(prio, prefix2, chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1) + CTL:SendAddonMessage(prio, prefix, MSG_MULTI_NEXT..chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1) pos = pos + maxtextlen end -- final part chunk = strsub(text, pos) - CTL:SendAddonMessage(prio, prefix..MSG_MULTI_LAST, chunk, distribution, target, queueName, ctlCallback, textlen) + CTL:SendAddonMessage(prio, prefix, MSG_MULTI_LAST..chunk, distribution, target, queueName, ctlCallback, textlen) end end @@ -138,17 +146,17 @@ do local compost = setmetatable({}, {__mode = "k"}) local function new() local t = next(compost) - if t then + if t then compost[t]=nil for i=#t,3,-1 do -- faster than pairs loop. don't even nil out 1/2 since they'll be overwritten t[i]=nil end return t end - + return {} end - + local function lostdatawarning(prefix,sender,where) DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: lost network data regarding '"..tostring(prefix).."' from '"..tostring(sender).."' (in "..where..")") end @@ -156,14 +164,14 @@ do function AceComm:OnReceiveMultipartFirst(prefix, message, distribution, sender) local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender local spool = AceComm.multipart_spool - + --[[ - if spool[key] then + if spool[key] then lostdatawarning(prefix,sender,"First") -- continue and overwrite end --]] - + spool[key] = message -- plain string for now end @@ -171,7 +179,7 @@ do local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender local spool = AceComm.multipart_spool local olddata = spool[key] - + if not olddata then --lostdatawarning(prefix,sender,"Next") return @@ -192,14 +200,14 @@ do local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender local spool = AceComm.multipart_spool local olddata = spool[key] - + if not olddata then --lostdatawarning(prefix,sender,"End") return end spool[key] = nil - + if type(olddata) == "table" then -- if we've received a "next", the spooled data will be a table for rapid & garbage-free tconcat tinsert(olddata, message) @@ -222,47 +230,31 @@ end ---------------------------------------- if not AceComm.callbacks then - -- ensure that 'prefix to watch' table is consistent with registered - -- callbacks - AceComm.__prefixes = {} - AceComm.callbacks = CallbackHandler:New(AceComm, "_RegisterComm", "UnregisterComm", "UnregisterAllComm") end -function AceComm.callbacks:OnUsed(target, prefix) - AceComm.multipart_origprefixes[prefix..MSG_MULTI_FIRST] = prefix - AceComm.multipart_reassemblers[prefix..MSG_MULTI_FIRST] = "OnReceiveMultipartFirst" - - AceComm.multipart_origprefixes[prefix..MSG_MULTI_NEXT] = prefix - AceComm.multipart_reassemblers[prefix..MSG_MULTI_NEXT] = "OnReceiveMultipartNext" - - AceComm.multipart_origprefixes[prefix..MSG_MULTI_LAST] = prefix - AceComm.multipart_reassemblers[prefix..MSG_MULTI_LAST] = "OnReceiveMultipartLast" -end +AceComm.callbacks.OnUsed = nil +AceComm.callbacks.OnUnused = nil -function AceComm.callbacks:OnUnused(target, prefix) - AceComm.multipart_origprefixes[prefix..MSG_MULTI_FIRST] = nil - AceComm.multipart_reassemblers[prefix..MSG_MULTI_FIRST] = nil - - AceComm.multipart_origprefixes[prefix..MSG_MULTI_NEXT] = nil - AceComm.multipart_reassemblers[prefix..MSG_MULTI_NEXT] = nil - - AceComm.multipart_origprefixes[prefix..MSG_MULTI_LAST] = nil - AceComm.multipart_reassemblers[prefix..MSG_MULTI_LAST] = nil -end - -local function OnEvent(this, event, ...) +local function OnEvent(self, event, prefix, message, distribution, sender) if event == "CHAT_MSG_ADDON" then - local prefix,message,distribution,sender = ... - local reassemblername = AceComm.multipart_reassemblers[prefix] - if reassemblername then - -- multipart: reassemble - local aceCommReassemblerFunc = AceComm[reassemblername] - local origprefix = AceComm.multipart_origprefixes[prefix] - aceCommReassemblerFunc(AceComm, origprefix, message, distribution, sender) + sender = Ambiguate(sender, "none") + local control, rest = match(message, "^([\001-\009])(.*)") + if control then + if control==MSG_MULTI_FIRST then + AceComm:OnReceiveMultipartFirst(prefix, rest, distribution, sender) + elseif control==MSG_MULTI_NEXT then + AceComm:OnReceiveMultipartNext(prefix, rest, distribution, sender) + elseif control==MSG_MULTI_LAST then + AceComm:OnReceiveMultipartLast(prefix, rest, distribution, sender) + elseif control==MSG_ESCAPE then + AceComm.callbacks:Fire(prefix, rest, distribution, sender) + else + -- unknown control character, ignore SILENTLY (dont warn unnecessarily about future extensions!) + end else -- single part: fire it off immediately and let CallbackHandler decide if it's registered or not AceComm.callbacks:Fire(prefix, message, distribution, sender) diff --git a/ProfessionMenu/Libs/AceComm-3.0/ChatThrottleLib.lua b/ProfessionMenu/Libs/AceComm-3.0/ChatThrottleLib.lua index b0afc92..6e89a6a 100644 --- a/ProfessionMenu/Libs/AceComm-3.0/ChatThrottleLib.lua +++ b/ProfessionMenu/Libs/AceComm-3.0/ChatThrottleLib.lua @@ -3,7 +3,7 @@ -- -- Manages AddOn chat output to keep player from getting kicked off. -- --- ChatThrottleLib:SendChatMessage/:SendAddonMessage functions that accept +-- ChatThrottleLib:SendChatMessage/:SendAddonMessage functions that accept -- a Priority ("BULK", "NORMAL", "ALERT") as well as prefix for SendChatMessage. -- -- Priorities get an equal share of available bandwidth when fully loaded. @@ -20,8 +20,10 @@ -- -- Can run as a standalone addon also, but, really, just embed it! :-) -- +-- LICENSE: ChatThrottleLib is released into the Public Domain +-- -local CTL_VERSION = 21 +local CTL_VERSION = 31 local _G = _G @@ -71,8 +73,8 @@ local math_min = math.min local math_max = math.max local next = next local strlen = string.len -local GetFrameRate = GetFrameRate - +local GetFramerate = GetFramerate +local unpack,type,pairs,wipe = unpack,type,pairs,table.wipe ----------------------------------------------------------------------- @@ -111,28 +113,41 @@ function Ring:Remove(obj) end end +-- Note that this is local because there's no upgrade logic for existing ring +-- metatables, and this isn't present on rings created in versions older than +-- v25. +local function Ring_Link(self, other) -- Move and append all contents of another ring to this ring + if not self.pos then + -- This ring is empty, so just transfer ownership. + self.pos = other.pos + other.pos = nil + elseif other.pos then + -- Our tail should point to their head, and their tail to our head. + self.pos.prev.next, other.pos.prev.next = other.pos, self.pos + -- Our head should point to their tail, and their head to our tail. + self.pos.prev, other.pos.prev = other.pos.prev, self.pos.prev + other.pos = nil + end +end + ----------------------------------------------------------------------- --- Recycling bin for pipes --- A pipe is a plain integer-indexed queue, which also happens to be a ring member +-- Recycling bin for pipes +-- A pipe is a plain integer-indexed queue of messages +-- Pipes normally live in Rings of pipes (3 rings total, one per priority) ChatThrottleLib.PipeBin = nil -- pre-v19, drastically different local PipeBin = setmetatable({}, {__mode="k"}) local function DelPipe(pipe) - for i = #pipe, 1, -1 do - pipe[i] = nil - end - pipe.prev = nil - pipe.next = nil - PipeBin[pipe] = true end local function NewPipe() local pipe = next(PipeBin) if pipe then + wipe(pipe) PipeBin[pipe] = nil return pipe end @@ -169,7 +184,7 @@ end -- Initialize queues, set up frame for OnUpdate, etc -function ChatThrottleLib:Init() +function ChatThrottleLib:Init() -- Set up queues if not self.Prio then @@ -179,6 +194,13 @@ function ChatThrottleLib:Init() self.Prio["BULK"] = { ByName = {}, Ring = Ring:New(), avail = 0 } end + if not self.BlockedQueuesDelay then + -- v25: Add blocked queues to rings to handle new client throttles. + for _, Prio in pairs(self.Prio) do + Prio.Blocked = Ring:New() + end + end + -- v4: total send counters per priority for _, Prio in pairs(self.Prio) do Prio.nTotalSent = Prio.nTotalSent or 0 @@ -201,6 +223,7 @@ function ChatThrottleLib:Init() self.Frame:SetScript("OnEvent", self.OnEvent) -- v11: Monitor P_E_W so we can throttle hard for a few seconds self.Frame:RegisterEvent("PLAYER_ENTERING_WORLD") self.OnUpdateDelay = 0 + self.BlockedQueuesDelay = 0 self.LastAvailUpdate = GetTime() self.HardThrottlingBeginTime = GetTime() -- v11: Throttle hard for a few seconds after startup @@ -209,14 +232,43 @@ function ChatThrottleLib:Init() -- Use secure hooks as of v16. Old regular hook support yanked out in v21. self.securelyHooked = true --SendChatMessage - hooksecurefunc("SendChatMessage", function(...) - return ChatThrottleLib.Hook_SendChatMessage(...) - end) + if _G.C_ChatInfo and _G.C_ChatInfo.SendChatMessage then + hooksecurefunc(_G.C_ChatInfo, "SendChatMessage", function(...) + return ChatThrottleLib.Hook_SendChatMessage(...) + end) + else + hooksecurefunc("SendChatMessage", function(...) + return ChatThrottleLib.Hook_SendChatMessage(...) + end) + end --SendAddonMessage - hooksecurefunc("SendAddonMessage", function(...) + hooksecurefunc(_G.C_ChatInfo, "SendAddonMessage", function(...) return ChatThrottleLib.Hook_SendAddonMessage(...) end) end + + -- v26: Hook SendAddonMessageLogged for traffic logging + if not self.securelyHookedLogged then + self.securelyHookedLogged = true + hooksecurefunc(_G.C_ChatInfo, "SendAddonMessageLogged", function(...) + return ChatThrottleLib.Hook_SendAddonMessageLogged(...) + end) + end + + -- v29: Hook BNSendGameData for traffic logging + if not self.securelyHookedBNGameData then + self.securelyHookedBNGameData = true + if _G.C_BattleNet and _G.C_BattleNet.SendGameData then + hooksecurefunc(_G.C_BattleNet, "SendGameData", function(...) + return ChatThrottleLib.Hook_BNSendGameData(...) + end) + else + hooksecurefunc("BNSendGameData", function(...) + return ChatThrottleLib.Hook_BNSendGameData(...) + end) + end + end + self.nBypass = 0 end @@ -245,6 +297,12 @@ function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destinati self.avail = self.avail - size self.nBypass = self.nBypass + size -- just a statistic end +function ChatThrottleLib.Hook_SendAddonMessageLogged(prefix, text, chattype, destination, ...) + ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destination, ...) +end +function ChatThrottleLib.Hook_BNSendGameData(destination, prefix, text) + ChatThrottleLib.Hook_SendAddonMessage(prefix, text, "WHISPER", destination) +end @@ -281,27 +339,93 @@ end ----------------------------------------------------------------------- -- Despooling logic +-- Reminder: +-- - We have 3 Priorities, each containing a "Ring" construct ... +-- - ... made up of N "Pipe"s (1 for each destination/pipename) +-- - and each pipe contains messages + +local SendAddonMessageResult = Enum.SendAddonMessageResult or { + Success = 0, + AddonMessageThrottle = 3, + NotInGroup = 5, + ChannelThrottle = 8, + GeneralError = 9, +} + +local function MapToSendResult(ok, ...) + local result + + if not ok then + -- The send function itself errored; don't look at anything else. + result = SendAddonMessageResult.GeneralError + else + -- Grab the last return value from the send function and remap + -- it from a boolean to an enum code. If there are no results, + -- assume success (true). + + result = select(-1, true, ...) + + if result == true then + result = SendAddonMessageResult.Success + elseif result == false then + result = SendAddonMessageResult.GeneralError + end + end + + return result +end + +local function IsThrottledSendResult(result) + return result == SendAddonMessageResult.AddonMessageThrottle +end + +-- A copy of this function exists in FrameXML, but for clarity it's here too. +local function CallErrorHandler(...) + return geterrorhandler()(...) +end + +local function PerformSend(sendFunction, ...) + bMyTraffic = true + local sendResult = MapToSendResult(xpcall(sendFunction, CallErrorHandler, ...)) + bMyTraffic = false + return sendResult +end function ChatThrottleLib:Despool(Prio) local ring = Prio.Ring while ring.pos and Prio.avail > ring.pos[1].nSize do - local msg = table_remove(Prio.Ring.pos, 1) - if not Prio.Ring.pos[1] then - local pipe = Prio.Ring.pos + local pipe = ring.pos + local msg = pipe[1] + local sendResult = PerformSend(msg.f, unpack(msg, 1, msg.n)) + + if IsThrottledSendResult(sendResult) then + -- Message was throttled; move the pipe into the blocked ring. Prio.Ring:Remove(pipe) - Prio.ByName[pipe.name] = nil - DelPipe(pipe) + Prio.Blocked:Add(pipe) else - Prio.Ring.pos = Prio.Ring.pos.next - end - Prio.avail = Prio.avail - msg.nSize - bMyTraffic = true - msg.f(unpack(msg, 1, msg.n)) - bMyTraffic = false - Prio.nTotalSent = Prio.nTotalSent + msg.nSize - DelMsg(msg) - if msg.callbackFn then - msg.callbackFn (msg.callbackArg) + -- Dequeue message after submission. + table_remove(pipe, 1) + DelMsg(msg) + + if not pipe[1] then -- did we remove last msg in this pipe? + Prio.Ring:Remove(pipe) + Prio.ByName[pipe.name] = nil + DelPipe(pipe) + else + ring.pos = ring.pos.next + end + + -- Update bandwidth counters on successful sends. + local didSend = (sendResult == SendAddonMessageResult.Success) + if didSend then + Prio.avail = Prio.avail - msg.nSize + Prio.nTotalSent = Prio.nTotalSent + msg.nSize + end + + -- Notify caller of message submission. + if msg.callbackFn then + securecallfunction(msg.callbackFn, msg.callbackArg, didSend, sendResult) + end end end end @@ -321,6 +445,7 @@ function ChatThrottleLib.OnUpdate(this,delay) local self = ChatThrottleLib self.OnUpdateDelay = self.OnUpdateDelay + delay + self.BlockedQueuesDelay = self.BlockedQueuesDelay + delay if self.OnUpdateDelay < 0.08 then return end @@ -332,40 +457,60 @@ function ChatThrottleLib.OnUpdate(this,delay) return -- argh. some bastard is spewing stuff past the lib. just bail early to save cpu. end - -- See how many of our priorities have queued messages (we only have 3, don't worry about the loop) - local n = 0 - for prioname,Prio in pairs(self.Prio) do - if Prio.Ring.pos or Prio.avail < 0 then - n = n + 1 + -- Integrate blocked queues back into their rings periodically. + if self.BlockedQueuesDelay >= 0.35 then + for _, Prio in pairs(self.Prio) do + Ring_Link(Prio.Ring, Prio.Blocked) end + + self.BlockedQueuesDelay = 0 end - -- Anything queued still? - if n<1 then - -- Nope. Move spillover bandwidth to global availability gauge and clear self.bQueueing - for prioname, Prio in pairs(self.Prio) do + -- See how many of our priorities have queued messages. This is split + -- into two counters because priorities that consist only of blocked + -- queues must keep our OnUpdate alive, but shouldn't count toward + -- bandwidth distribution. + local nSendablePrios = 0 + local nBlockedPrios = 0 + + for prioname, Prio in pairs(self.Prio) do + if Prio.Ring.pos then + nSendablePrios = nSendablePrios + 1 + elseif Prio.Blocked.pos then + nBlockedPrios = nBlockedPrios + 1 + end + + -- Collect unused bandwidth from priorities with nothing to send. + if not Prio.Ring.pos then self.avail = self.avail + Prio.avail Prio.avail = 0 end - self.bQueueing = false - self.Frame:Hide() + end + + -- Bandwidth reclamation may take us back over the burst cap. + self.avail = math_min(self.avail, self.BURST) + + -- If we can't currently send on any priorities, stop processing early. + if nSendablePrios == 0 then + -- If we're completely out of data to send, disable queue processing. + if nBlockedPrios == 0 then + self.bQueueing = false + self.Frame:Hide() + end + return end -- There's stuff queued. Hand out available bandwidth to priorities as needed and despool their queues - local avail = self.avail/n + local avail = self.avail / nSendablePrios self.avail = 0 for prioname, Prio in pairs(self.Prio) do - if Prio.Ring.pos or Prio.avail < 0 then + if Prio.Ring.pos then Prio.avail = Prio.avail + avail - if Prio.Ring.pos and Prio.avail > Prio.Ring.pos[1].nSize then - self:Despool(Prio) - -- Note: We might not get here if the user-supplied callback function errors out! Take care! - end + self:Despool(Prio) end end - end @@ -374,7 +519,6 @@ end ----------------------------------------------------------------------- -- Spooling logic - function ChatThrottleLib:Enqueue(prioname, pipename, msg) local Prio = self.Prio[prioname] local pipe = Prio.ByName[pipename] @@ -391,8 +535,6 @@ function ChatThrottleLib:Enqueue(prioname, pipename, msg) self.bQueueing = true end - - function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, language, destination, queueName, callbackFn, callbackArg) if not self or not prio or not prefix or not text or not self.Prio[prio] then error('Usage: ChatThrottleLib:SendChatMessage("{BULK||NORMAL||ALERT}", "prefix", "text"[, "chattype"[, "language"[, "destination"]]]', 2) @@ -411,20 +553,27 @@ function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, languag -- Check if there's room in the global available bandwidth gauge to send directly if not self.bQueueing and nSize < self:UpdateAvail() then - self.avail = self.avail - nSize - bMyTraffic = true - _G.SendChatMessage(text, chattype, language, destination) - bMyTraffic = false - self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize - if callbackFn then - callbackFn (callbackArg) + local sendResult = PerformSend(_G.C_ChatInfo.SendChatMessage or _G.SendChatMessage, text, chattype, language, destination) + + if not IsThrottledSendResult(sendResult) then + local didSend = (sendResult == SendAddonMessageResult.Success) + + if didSend then + self.avail = self.avail - nSize + self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize + end + + if callbackFn then + securecallfunction(callbackFn, callbackArg, didSend, sendResult) + end + + return end - return end -- Message needs to be queued local msg = NewMsg() - msg.f = _G.SendChatMessage + msg.f = _G.C_ChatInfo.SendChatMessage or _G.SendChatMessage msg[1] = text msg[2] = chattype or "SAY" msg[3] = language @@ -434,42 +583,36 @@ function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, languag msg.callbackFn = callbackFn msg.callbackArg = callbackArg - self:Enqueue(prio, queueName or (prefix..(chattype or "SAY")..(destination or "")), msg) + self:Enqueue(prio, queueName or prefix, msg) end -function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) - if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then - error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2) - end - if callbackFn and type(callbackFn)~="function" then - error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2) - end - - local nSize = prefix:len() + 1 + text:len(); - - if nSize>255 then - error("ChatThrottleLib:SendAddonMessage(): prefix + message length cannot exceed 254 bytes", 2) - end - - nSize = nSize + self.MSG_OVERHEAD; +local function SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) + local nSize = #text + self.MSG_OVERHEAD -- Check if there's room in the global available bandwidth gauge to send directly if not self.bQueueing and nSize < self:UpdateAvail() then - self.avail = self.avail - nSize - bMyTraffic = true - _G.SendAddonMessage(prefix, text, chattype, target) - bMyTraffic = false - self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize - if callbackFn then - callbackFn (callbackArg) + local sendResult = PerformSend(sendFunction, prefix, text, chattype, target) + + if not IsThrottledSendResult(sendResult) then + local didSend = (sendResult == SendAddonMessageResult.Success) + + if didSend then + self.avail = self.avail - nSize + self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize + end + + if callbackFn then + securecallfunction(callbackFn, callbackArg, didSend, sendResult) + end + + return end - return end -- Message needs to be queued local msg = NewMsg() - msg.f = _G.SendAddonMessage + msg.f = sendFunction msg[1] = prefix msg[2] = text msg[3] = chattype @@ -479,10 +622,65 @@ function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, msg.callbackFn = callbackFn msg.callbackArg = callbackArg - self:Enqueue(prio, queueName or (prefix..chattype..(target or "")), msg) + self:Enqueue(prio, queueName or prefix, msg) end +function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) + if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then + error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2) + elseif callbackFn and type(callbackFn)~="function" then + error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2) + elseif #text>255 then + error("ChatThrottleLib:SendAddonMessage(): message length cannot exceed 255 bytes", 2) + end + + local sendFunction = _G.C_ChatInfo.SendAddonMessage + SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) +end + + +function ChatThrottleLib:SendAddonMessageLogged(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) + if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then + error('Usage: ChatThrottleLib:SendAddonMessageLogged("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2) + elseif callbackFn and type(callbackFn)~="function" then + error('ChatThrottleLib:SendAddonMessageLogged(): callbackFn: expected function, got '..type(callbackFn), 2) + elseif #text>255 then + error("ChatThrottleLib:SendAddonMessageLogged(): message length cannot exceed 255 bytes", 2) + end + + local sendFunction = _G.C_ChatInfo.SendAddonMessageLogged + SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) +end + +local function BNSendGameDataReordered(prefix, text, _, gameAccountID) + local bnSendFunc = _G.C_BattleNet and _G.C_BattleNet.SendGameData or _G.BNSendGameData + return bnSendFunc(gameAccountID, prefix, text) +end + +function ChatThrottleLib:BNSendGameData(prio, prefix, text, chattype, gameAccountID, queueName, callbackFn, callbackArg) + -- Note that this API is intentionally limited to 255 bytes of data + -- for reasons of traffic fairness, which is less than the 4078 bytes + -- BNSendGameData natively supports. Additionally, a chat type is required + -- but must always be set to 'WHISPER' to match what is exposed by the + -- receipt event. + -- + -- If splitting messages, callers must also be aware that message + -- delivery over BNSendGameData is unordered. + + if not self or not prio or not prefix or not text or not gameAccountID or not chattype or not self.Prio[prio] then + error('Usage: ChatThrottleLib:BNSendGameData("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype", gameAccountID)', 2) + elseif callbackFn and type(callbackFn)~="function" then + error('ChatThrottleLib:BNSendGameData(): callbackFn: expected function, got '..type(callbackFn), 2) + elseif #text>255 then + error("ChatThrottleLib:BNSendGameData(): message length cannot exceed 255 bytes", 2) + elseif chattype ~= "WHISPER" then + error("ChatThrottleLib:BNSendGameData(): chat type must be 'WHISPER'", 2) + end + + local sendFunction = BNSendGameDataReordered + SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, gameAccountID, queueName, callbackFn, callbackArg) +end ----------------------------------------------------------------------- diff --git a/ProfessionMenu/Libs/AceConsole-3.0/AceConsole-3.0.lua b/ProfessionMenu/Libs/AceConsole-3.0/AceConsole-3.0.lua index c001123..8e5ec81 100644 --- a/ProfessionMenu/Libs/AceConsole-3.0/AceConsole-3.0.lua +++ b/ProfessionMenu/Libs/AceConsole-3.0/AceConsole-3.0.lua @@ -2,14 +2,14 @@ -- You can register slash commands to your custom functions and use the `GetArgs` function to parse them -- to your addons individual needs. -- --- **AceConsole-3.0** can be embeded into your addon, either explicitly by calling AceConsole:Embed(MyAddon) or by +-- **AceConsole-3.0** can be embeded into your addon, either explicitly by calling AceConsole:Embed(MyAddon) or by -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object -- and can be accessed directly, without having to explicitly call AceConsole itself.\\ -- It is recommended to embed AceConsole, otherwise you'll have to specify a custom `self` on all calls you -- make into AceConsole. -- @class file -- @name AceConsole-3.0 --- @release $Id: AceConsole-3.0.lua 878 2009-11-02 18:51:58Z nevcairiel $ +-- @release $Id$ local MAJOR,MINOR = "AceConsole-3.0", 7 local AceConsole, oldminor = LibStub:NewLibrary(MAJOR, MINOR) @@ -29,10 +29,6 @@ local max = math.max -- WoW APIs local _G = _G --- Global vars/functions that we don't upvalue since they might get hooked, or upgraded --- List them here for Mikk's FindGlobals script --- GLOBALS: DEFAULT_CHAT_FRAME, SlashCmdList, hash_SlashCmdList - local tmp={} local function Print(self,frame,...) local n=0 @@ -84,11 +80,11 @@ end -- @param persist if false, the command will be soft disabled/enabled when aceconsole is used as a mixin (default: true) function AceConsole:RegisterChatCommand( command, func, persist ) if type(command)~="string" then error([[Usage: AceConsole:RegisterChatCommand( "command", func[, persist ]): 'command' - expected a string]], 2) end - + if persist==nil then persist=true end -- I'd rather have my addon's "/addon enable" around if the author screws up. Having some extra slash regged when it shouldnt be isn't as destructive. True is a better default. /Mikk - + local name = "ACECONSOLE_"..command:upper() - + if type( func ) == "string" then SlashCmdList[name] = function(input, editBox) self[func](self, input, editBox) @@ -132,11 +128,11 @@ local function nils(n, ...) return ... end end - ---- Retreive one or more space-separated arguments from a string. + +--- Retreive one or more space-separated arguments from a string. -- Treats quoted strings and itemlinks as non-spaced. --- @param string The raw argument string +-- @param str The raw argument string -- @param numargs How many arguments to get (default 1) -- @param startpos Where in the string to start scanning (default 1) -- @return Returns arg1, arg2, ..., nextposition\\ @@ -144,7 +140,7 @@ end function AceConsole:GetArgs(str, numargs, startpos) numargs = numargs or 1 startpos = max(startpos or 1, 1) - + local pos=startpos -- find start of new arg @@ -169,24 +165,24 @@ function AceConsole:GetArgs(str, numargs, startpos) else delim_or_pipe="([| ])" end - + startpos = pos - + while true do -- find delimiter or hyperlink - local ch,_ + local _ pos,_,ch = strfind(str, delim_or_pipe, pos) - + if not pos then break end - + if ch=="|" then -- some kind of escape - + if strsub(str,pos,pos+1)=="|H" then -- It's a |H....|hhyper link!|h pos=strfind(str, "|h", pos+2) -- first |h if not pos then break end - + pos=strfind(str, "|h", pos+2) -- second |h if not pos then break end elseif strsub(str,pos, pos+1) == "|T" then @@ -194,16 +190,16 @@ function AceConsole:GetArgs(str, numargs, startpos) pos=strfind(str, "|t", pos+2) if not pos then break end end - + pos=pos+2 -- skip past this escape (last |h if it was a hyperlink) - + else -- found delimiter, done with this arg return strsub(str, startpos, pos-1), AceConsole:GetArgs(str, numargs-1, pos+1) end - + end - + -- search aborted, we hit end of string. return it all as one argument. (yes, even if it's an unterminated quote or hyperlink) return strsub(str, startpos), nils(numargs-1, 1e9) end @@ -214,10 +210,10 @@ end local mixins = { "Print", "Printf", - "RegisterChatCommand", + "RegisterChatCommand", "UnregisterChatCommand", "GetArgs", -} +} -- Embeds AceConsole into the target object making the functions from the mixins list available on target:.. -- @param target target object to embed AceBucket in diff --git a/ProfessionMenu/Libs/AceDB-3.0/AceDB-3.0.lua b/ProfessionMenu/Libs/AceDB-3.0/AceDB-3.0.lua index 7a29450..231196c 100644 --- a/ProfessionMenu/Libs/AceDB-3.0/AceDB-3.0.lua +++ b/ProfessionMenu/Libs/AceDB-3.0/AceDB-3.0.lua @@ -10,6 +10,7 @@ -- * **race** Race-specific data. All of the players characters of the same race share this database. -- * **faction** Faction-specific data. All of the players characters of the same faction share this database. -- * **factionrealm** Faction and realm specific data. All of the players characters on the same realm and of the same faction share this database. +-- * **locale** Locale specific data, based on the locale of the players game client. -- * **global** Global Data. All characters on the same account share this database. -- * **profile** Profile-specific data. All characters using the same profile share this database. The user can control which profile should be used. -- @@ -39,23 +40,19 @@ -- end -- @class file -- @name AceDB-3.0.lua --- @release $Id: AceDB-3.0.lua 940 2010-06-19 08:01:47Z nevcairiel $ -local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 21 -local AceDB, oldminor = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR) +-- @release $Id$ +local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 33 +local AceDB = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR) if not AceDB then return end -- No upgrade needed -- Lua APIs local type, pairs, next, error = type, pairs, next, error -local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget +local setmetatable, rawset, rawget = setmetatable, rawset, rawget -- WoW APIs local _G = _G --- Global vars/functions that we don't upvalue since they might get hooked, or upgraded --- List them here for Mikk's FindGlobals script --- GLOBALS: LibStub - AceDB.db_registry = AceDB.db_registry or {} AceDB.frame = AceDB.frame or CreateFrame("Frame") @@ -97,11 +94,11 @@ local function copyDefaults(dest, src) -- This is a metatable used for table defaults local mt = { -- This handles the lookup and creation of new subtables - __index = function(t,k) - if k == nil then return nil end + __index = function(t,k2) + if k2 == nil then return nil end local tbl = {} copyDefaults(tbl, v) - rawset(t, k, tbl) + rawset(t, k2, tbl) return tbl end, } @@ -114,7 +111,7 @@ local function copyDefaults(dest, src) end else -- Values are not tables, so this is just a simple return - local mt = {__index = function(t,k) return k~=nil and v or nil end} + local mt = {__index = function(t,k2) return k2~=nil and v or nil end} setmetatable(dest, mt) end elseif type(v) == "table" then @@ -260,6 +257,12 @@ local _, classKey = UnitClass("player") local _, raceKey = UnitRace("player") local factionKey = UnitFactionGroup("player") local factionrealmKey = factionKey .. " - " .. realmKey +local localeKey = GetLocale():lower() + +local regionTable = { "US", "KR", "EU", "TW", "CN" } +local regionKey = regionTable[GetCurrentRegion()] or GetCurrentRegionName() or "TR" +local factionrealmregionKey = factionrealmKey .. " - " .. regionKey + -- Actual database initialization function local function initdb(sv, defaults, defaultProfile, olddb, parent) -- Generate the database keys for each section @@ -295,7 +298,9 @@ local function initdb(sv, defaults, defaultProfile, olddb, parent) ["race"] = raceKey, ["faction"] = factionKey, ["factionrealm"] = factionrealmKey, + ["factionrealmregion"] = factionrealmregionKey, ["profile"] = profileKey, + ["locale"] = localeKey, ["global"] = true, ["profiles"] = true, } @@ -352,10 +357,10 @@ local function logoutHandler(frame, event) for db in pairs(AceDB.db_registry) do db.callbacks:Fire("OnDatabaseShutdown", db) db:RegisterDefaults(nil) - + -- cleanup sections that are empty without defaults local sv = rawget(db, "sv") - for section in pairs(db.keys) do + for section in pairs(rawget(db, "keys")) do if rawget(sv, section) then -- global is special, all other sections have sub-entrys -- also don't delete empty profiles on main dbs, only on namespaces @@ -372,6 +377,26 @@ local function logoutHandler(frame, event) end end end + + -- second pass after everything else is cleaned up to remove empty namespaces + -- can't be run in-loop above since there is no guaranteed order + for db in pairs(AceDB.db_registry) do + local sv = rawget(db, "sv") + local namespaces = rawget(sv, "namespaces") + if namespaces then + for name in pairs(namespaces) do + -- cleanout empty profiles table, if still present + if namespaces[name].profiles and not next(namespaces[name].profiles) then + namespaces[name].profiles = nil + end + + -- remove entire namespace, if needed + if not next(namespaces[name]) then + namespaces[name] = nil + end + end + end + end end end @@ -388,7 +413,7 @@ AceDB.frame:SetScript("OnEvent", logoutHandler) -- @param defaults A table of defaults for this database function DBObjectLib:RegisterDefaults(defaults) if defaults and type(defaults) ~= "table" then - error("Usage: AceDBObject:RegisterDefaults(defaults): 'defaults' - table or nil expected.", 2) + error(("Usage: AceDBObject:RegisterDefaults(defaults): 'defaults' - table or nil expected, got %q."):format(type(defaults)), 2) end validateDefaults(defaults, self.keys) @@ -420,7 +445,7 @@ end -- @param name The name of the profile to set as the current profile function DBObjectLib:SetProfile(name) if type(name) ~= "string" then - error("Usage: AceDBObject:SetProfile(name): 'name' - string expected.", 2) + error(("Usage: AceDBObject:SetProfile(name): 'name' - string expected, got %q."):format(type(name)), 2) end -- changing to the same profile, dont do anything @@ -462,7 +487,7 @@ end -- @param tbl A table to store the profile names in (optional) function DBObjectLib:GetProfiles(tbl) if tbl and type(tbl) ~= "table" then - error("Usage: AceDBObject:GetProfiles(tbl): 'tbl' - table or nil expected.", 2) + error(("Usage: AceDBObject:GetProfiles(tbl): 'tbl' - table or nil expected, got %q."):format(type(tbl)), 2) end -- Clear the container table @@ -500,15 +525,15 @@ end -- @param silent If true, do not raise an error when the profile does not exist function DBObjectLib:DeleteProfile(name, silent) if type(name) ~= "string" then - error("Usage: AceDBObject:DeleteProfile(name): 'name' - string expected.", 2) + error(("Usage: AceDBObject:DeleteProfile(name): 'name' - string expected, got %q."):format(type(name)), 2) end if self.keys.profile == name then - error("Cannot delete the active profile in an AceDBObject.", 2) + error(("Cannot delete the active profile (%q) in an AceDBObject."):format(name), 2) end if not rawget(self.profiles, name) and not silent then - error("Cannot delete profile '" .. name .. "'. It does not exist.", 2) + error(("Cannot delete profile %q as it does not exist."):format(name), 2) end self.profiles[name] = nil @@ -520,6 +545,26 @@ function DBObjectLib:DeleteProfile(name, silent) end end + -- remove from unloaded namespaces + if self.sv.namespaces then + for nsname, data in pairs(self.sv.namespaces) do + if self.children and self.children[nsname] then + -- already a mapped namespace + elseif data.profiles then + data.profiles[name] = nil + end + end + end + + -- switch all characters that use this profile back to the default + if self.sv.profileKeys then + for key, profile in pairs(self.sv.profileKeys) do + if profile == name then + self.sv.profileKeys[key] = nil + end + end + end + -- Callback: OnProfileDeleted, database, profileKey self.callbacks:Fire("OnProfileDeleted", self, name) end @@ -530,15 +575,15 @@ end -- @param silent If true, do not raise an error when the profile does not exist function DBObjectLib:CopyProfile(name, silent) if type(name) ~= "string" then - error("Usage: AceDBObject:CopyProfile(name): 'name' - string expected.", 2) + error(("Usage: AceDBObject:CopyProfile(name): 'name' - string expected, got %q."):format(type(name)), 2) end if name == self.keys.profile then - error("Cannot have the same source and destination profiles.", 2) + error(("Cannot have the same source and destination profiles (%q)."):format(name), 2) end if not rawget(self.profiles, name) and not silent then - error("Cannot copy profile '" .. name .. "'. It does not exist.", 2) + error(("Cannot copy profile %q as it does not exist."):format(name), 2) end -- Reset the profile before copying @@ -556,6 +601,20 @@ function DBObjectLib:CopyProfile(name, silent) end end + -- copy unloaded namespaces + if self.sv.namespaces then + for nsname, data in pairs(self.sv.namespaces) do + if self.children and self.children[nsname] then + -- already a mapped namespace + elseif data.profiles then + -- reset the current profile + data.profiles[self.keys.profile] = {} + -- copy data + copyTable(data.profiles[name], data.profiles[self.keys.profile]) + end + end + end + -- Callback: OnProfileCopied, database, sourceProfileKey self.callbacks:Fire("OnProfileCopied", self, name) end @@ -582,6 +641,18 @@ function DBObjectLib:ResetProfile(noChildren, noCallbacks) end end + -- reset unloaded namespaces + if self.sv.namespaces and not noChildren then + for nsname, data in pairs(self.sv.namespaces) do + if self.children and self.children[nsname] then + -- already a mapped namespace + elseif data.profiles then + -- reset the current profile + data.profiles[self.keys.profile] = nil + end + end + end + -- Callback: OnProfileReset, database if not noCallbacks then self.callbacks:Fire("OnProfileReset", self) @@ -592,8 +663,8 @@ end -- profile. -- @param defaultProfile The profile name to use as the default function DBObjectLib:ResetDB(defaultProfile) - if defaultProfile and type(defaultProfile) ~= "string" then - error("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or nil expected.", 2) + if defaultProfile and type(defaultProfile) ~= "string" and defaultProfile ~= true then + error(("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or true expected, got %q."):format(type(defaultProfile)), 2) end local sv = self.sv @@ -601,8 +672,6 @@ function DBObjectLib:ResetDB(defaultProfile) sv[k] = nil end - local parent = self.parent - initdb(sv, self.defaults, defaultProfile, self) -- fix the child namespaces @@ -629,13 +698,13 @@ end -- @param defaults A table of values to use as defaults function DBObjectLib:RegisterNamespace(name, defaults) if type(name) ~= "string" then - error("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - string expected.", 2) + error(("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - string expected, got %q."):format(type(name)), 2) end if defaults and type(defaults) ~= "table" then - error("Usage: AceDBObject:RegisterNamespace(name, defaults): 'defaults' - table or nil expected.", 2) + error(("Usage: AceDBObject:RegisterNamespace(name, defaults): 'defaults' - table or nil expected, got %q."):format(type(defaults)), 2) end if self.children and self.children[name] then - error ("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - a namespace with that name already exists.", 2) + error(("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - a namespace called %q already exists."):format(name), 2) end local sv = self.sv @@ -659,10 +728,10 @@ end -- @return the namespace object if found function DBObjectLib:GetNamespace(name, silent) if type(name) ~= "string" then - error("Usage: AceDBObject:GetNamespace(name): 'name' - string expected.", 2) + error(("Usage: AceDBObject:GetNamespace(name): 'name' - string expected, got %q."):format(type(name)), 2) end if not silent and not (self.children and self.children[name]) then - error ("Usage: AceDBObject:GetNamespace(name): 'name' - namespace does not exist.", 2) + error(("Usage: AceDBObject:GetNamespace(name): 'name' - namespace %q does not exist."):format(name), 2) end if not self.children then self.children = {} end return self.children[name] @@ -701,15 +770,15 @@ function AceDB:New(tbl, defaults, defaultProfile) end if type(tbl) ~= "table" then - error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'tbl' - table expected.", 2) + error(("Usage: AceDB:New(tbl, defaults, defaultProfile): 'tbl' - table expected, got %q."):format(type(tbl)), 2) end if defaults and type(defaults) ~= "table" then - error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaults' - table expected.", 2) + error(("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaults' - table expected, got %q."):format(type(defaults)), 2) end if defaultProfile and type(defaultProfile) ~= "string" and defaultProfile ~= true then - error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaultProfile' - string or true expected.", 2) + error(("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaultProfile' - string or true expected, got %q."):format(type(defaultProfile)), 2) end return initdb(tbl, defaults, defaultProfile) diff --git a/ProfessionMenu/Libs/AceEvent-3.0/AceEvent-3.0.lua b/ProfessionMenu/Libs/AceEvent-3.0/AceEvent-3.0.lua index e9b18b1..9f96bf3 100644 --- a/ProfessionMenu/Libs/AceEvent-3.0/AceEvent-3.0.lua +++ b/ProfessionMenu/Libs/AceEvent-3.0/AceEvent-3.0.lua @@ -2,15 +2,17 @@ -- All dispatching is done using **CallbackHandler-1.0**. AceEvent is a simple wrapper around -- CallbackHandler, and dispatches all game events or addon message to the registrees. -- --- **AceEvent-3.0** can be embeded into your addon, either explicitly by calling AceEvent:Embed(MyAddon) or by +-- **AceEvent-3.0** can be embeded into your addon, either explicitly by calling AceEvent:Embed(MyAddon) or by -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object -- and can be accessed directly, without having to explicitly call AceEvent itself.\\ -- It is recommended to embed AceEvent, otherwise you'll have to specify a custom `self` on all calls you -- make into AceEvent. -- @class file -- @name AceEvent-3.0 --- @release $Id: AceEvent-3.0.lua 877 2009-11-02 15:56:50Z nevcairiel $ -local MAJOR, MINOR = "AceEvent-3.0", 3 +-- @release $Id$ +local CallbackHandler = LibStub("CallbackHandler-1.0") + +local MAJOR, MINOR = "AceEvent-3.0", 4 local AceEvent = LibStub:NewLibrary(MAJOR, MINOR) if not AceEvent then return end @@ -18,29 +20,27 @@ if not AceEvent then return end -- Lua APIs local pairs = pairs -local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0") - AceEvent.frame = AceEvent.frame or CreateFrame("Frame", "AceEvent30Frame") -- our event frame AceEvent.embeds = AceEvent.embeds or {} -- what objects embed this lib -- APIs and registry for blizzard events, using CallbackHandler lib if not AceEvent.events then - AceEvent.events = CallbackHandler:New(AceEvent, + AceEvent.events = CallbackHandler:New(AceEvent, "RegisterEvent", "UnregisterEvent", "UnregisterAllEvents") end -function AceEvent.events:OnUsed(target, eventname) +function AceEvent.events:OnUsed(target, eventname) AceEvent.frame:RegisterEvent(eventname) end -function AceEvent.events:OnUnused(target, eventname) +function AceEvent.events:OnUnused(target, eventname) AceEvent.frame:UnregisterEvent(eventname) end -- APIs and registry for IPC messages, using CallbackHandler lib if not AceEvent.messages then - AceEvent.messages = CallbackHandler:New(AceEvent, + AceEvent.messages = CallbackHandler:New(AceEvent, "RegisterMessage", "UnregisterMessage", "UnregisterAllMessages" ) AceEvent.SendMessage = AceEvent.messages.Fire @@ -55,7 +55,7 @@ local mixins = { } --- Register for a Blizzard Event. --- The callback will always be called with the event as the first argument, and if supplied, the `arg` as second argument. +-- The callback will be called with the optional `arg` as the first argument (if supplied), and the event name as the second (or first, if no arg was supplied) -- Any arguments to the event will be passed on after that. -- @name AceEvent:RegisterEvent -- @class function @@ -71,7 +71,7 @@ local mixins = { -- @param event The event to unregister --- Register for a custom AceEvent-internal message. --- The callback will always be called with the event as the first argument, and if supplied, the `arg` as second argument. +-- The callback will be called with the optional `arg` as the first argument (if supplied), and the event name as the second (or first, if no arg was supplied) -- Any arguments to the event will be passed on after that. -- @name AceEvent:RegisterMessage -- @class function diff --git a/ProfessionMenu/Libs/AceSerializer-3.0/AceSerializer-3.0.lua b/ProfessionMenu/Libs/AceSerializer-3.0/AceSerializer-3.0.lua index b072a2b..ae0f7f9 100644 --- a/ProfessionMenu/Libs/AceSerializer-3.0/AceSerializer-3.0.lua +++ b/ProfessionMenu/Libs/AceSerializer-3.0/AceSerializer-3.0.lua @@ -1,17 +1,17 @@ --- **AceSerializer-3.0** can serialize any variable (except functions or userdata) into a string format, --- that can be send over the addon comm channel. AceSerializer was designed to keep all data intact, especially +-- that can be send over the addon comm channel. AceSerializer was designed to keep all data intact, especially -- very large numbers or floating point numbers, and table structures. The only caveat currently is, that multiple -- references to the same table will be send individually. -- --- **AceSerializer-3.0** can be embeded into your addon, either explicitly by calling AceSerializer:Embed(MyAddon) or by +-- **AceSerializer-3.0** can be embeded into your addon, either explicitly by calling AceSerializer:Embed(MyAddon) or by -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object -- and can be accessed directly, without having to explicitly call AceSerializer itself.\\ -- It is recommended to embed AceSerializer, otherwise you'll have to specify a custom `self` on all calls you -- make into AceSerializer. -- @class file -- @name AceSerializer-3.0 --- @release $Id: AceSerializer-3.0.lua 910 2010-02-11 21:54:24Z mikk $ -local MAJOR,MINOR = "AceSerializer-3.0", 3 +-- @release $Id$ +local MAJOR,MINOR = "AceSerializer-3.0", 5 local AceSerializer, oldminor = LibStub:NewLibrary(MAJOR, MINOR) if not AceSerializer then return end @@ -24,9 +24,11 @@ local pairs, select, frexp = pairs, select, math.frexp local tconcat = table.concat -- quick copies of string representations of wonky numbers -local serNaN = tostring(0/0) -local serInf = tostring(1/0) -local serNegInf = tostring(-1/0) +local inf = math.huge + +local serNaN -- can't do this in 4.3, see ace3 ticket 268 +local serInf, serInfMac = "1.#INF", "inf" +local serNegInf, serNegInfMac = "-1.#INF", "-inf" -- Serialization functions @@ -38,7 +40,7 @@ local function SerializeStringHelper(ch) -- Used by SerializeValue for strings return "\126\122" elseif n<=32 then -- nonprint + space return "\126"..strchar(n+64) - elseif n==94 then -- value separator + elseif n==94 then -- value separator return "\126\125" elseif n==126 then -- our own escape character return "\126\124" @@ -52,19 +54,23 @@ end local function SerializeValue(v, res, nres) -- We use "^" as a value separator, followed by one byte for type indicator local t=type(v) - + if t=="string" then -- ^S = string (escaped to remove nonprints, "^"s, etc) res[nres+1] = "^S" res[nres+2] = gsub(v,"[%c \94\126\127]", SerializeStringHelper) nres=nres+2 - + elseif t=="number" then -- ^N = number (just tostring()ed) or ^F (float components) local str = tostring(v) - if tonumber(str)==v or str==serNaN or str==serInf or str==serNegInf then + if tonumber(str)==v --[[not in 4.3 or str==serNaN]] then -- translates just fine, transmit as-is res[nres+1] = "^N" res[nres+2] = str nres=nres+2 + elseif v == inf or v == -inf then + res[nres+1] = "^N" + res[nres+2] = v == inf and serInf or serNegInf + nres=nres+2 else local m,e = frexp(v) res[nres+1] = "^F" @@ -73,17 +79,17 @@ local function SerializeValue(v, res, nres) res[nres+4] = tostring(e-53) -- adjust exponent to counteract mantissa manipulation nres=nres+4 end - + elseif t=="table" then -- ^T...^t = table (list of key,value pairs) nres=nres+1 res[nres] = "^T" - for k,v in pairs(v) do - nres = SerializeValue(k, res, nres) - nres = SerializeValue(v, res, nres) + for key,value in pairs(v) do + nres = SerializeValue(key, res, nres) + nres = SerializeValue(value, res, nres) end nres=nres+1 res[nres] = "^t" - + elseif t=="boolean" then -- ^B = true, ^b = false nres=nres+1 if v then @@ -91,15 +97,15 @@ local function SerializeValue(v, res, nres) else res[nres] = "^b" -- false end - + elseif t=="nil" then -- ^Z = nil (zero, "N" was taken :P) nres=nres+1 res[nres] = "^Z" - + else error(MAJOR..": Cannot serialize a value of type '"..t.."'") -- can't produce error on right level, this is wildly recursive end - + return nres end @@ -115,14 +121,14 @@ local serializeTbl = { "^1" } -- "^1" = Hi, I'm data serialized by AceSerializer -- @return The data in its serialized form (string) function AceSerializer:Serialize(...) local nres = 1 - + for i=1,select("#", ...) do local v = select(i, ...) nres = SerializeValue(v, serializeTbl, nres) end - + serializeTbl[nres+1] = "^^" -- "^^" = End of serialized data - + return tconcat(serializeTbl, "", 1, nres+1) end @@ -143,12 +149,12 @@ local function DeserializeStringHelper(escape) end local function DeserializeNumberHelper(number) - if number == serNaN then + --[[ not in 4.3 if number == serNaN then return 0/0 - elseif number == serNegInf then - return -1/0 - elseif number == serInf then - return 1/0 + else]]if number == serNegInf or number == serNegInfMac then + return -inf + elseif number == serInf or number == serInfMac then + return inf else return tonumber(number) end @@ -169,9 +175,9 @@ local function DeserializeValue(iter,single,ctl,data) ctl,data = iter() end - if not ctl then + if not ctl then error("Supplied data misses AceSerializer terminator ('^^')") - end + end if ctl=="^^" then -- ignore extraneous data @@ -179,7 +185,7 @@ local function DeserializeValue(iter,single,ctl,data) end local res - + if ctl=="^S" then res = gsub(data, "~.", DeserializeStringHelper) elseif ctl=="^N" then @@ -212,7 +218,7 @@ local function DeserializeValue(iter,single,ctl,data) ctl,data = iter() if ctl=="^t" then break end -- ignore ^t's data k = DeserializeValue(iter,true,ctl,data) - if k==nil then + if k==nil then error("Invalid AceSerializer table format (no table end marker)") end ctl,data = iter() @@ -225,7 +231,7 @@ local function DeserializeValue(iter,single,ctl,data) else error("Invalid AceSerializer control code '"..ctl.."'") end - + if not single then return res,DeserializeValue(iter) else @@ -278,4 +284,4 @@ end -- Update embeds for target, v in pairs(AceSerializer.embeds) do AceSerializer:Embed(target) -end \ No newline at end of file +end diff --git a/ProfessionMenu/Libs/AceTimer-3.0/AceTimer-3.0.lua b/ProfessionMenu/Libs/AceTimer-3.0/AceTimer-3.0.lua index c376f16..e84d420 100644 --- a/ProfessionMenu/Libs/AceTimer-3.0/AceTimer-3.0.lua +++ b/ProfessionMenu/Libs/AceTimer-3.0/AceTimer-3.0.lua @@ -1,12 +1,12 @@ --- **AceTimer-3.0** provides a central facility for registering timers. -- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient --- data structure that allows easy dispatching and fast rescheduling. Timers can be registered, rescheduled +-- data structure that allows easy dispatching and fast rescheduling. Timers can be registered -- or canceled at any time, even from within a running timer, without conflict or large overhead.\\ --- AceTimer is currently limited to firing timers at a frequency of 0.1s. This constant may change --- in the future, but for now it seemed like a good compromise in efficiency and accuracy. +-- AceTimer is currently limited to firing timers at a frequency of 0.01s as this is what the WoW timer API +-- restricts us to. -- -- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you --- need to cancel or reschedule the timer you just registered. +-- need to cancel the timer you just registered. -- -- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object @@ -15,282 +15,110 @@ -- make into AceTimer. -- @class file -- @name AceTimer-3.0 --- @release $Id: AceTimer-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $ +-- @release $Id$ ---[[ - Basic assumptions: - * In a typical system, we do more re-scheduling per second than there are timer pulses per second - * Regardless of timer implementation, we cannot guarantee timely delivery due to FPS restriction (may be as low as 10) - - This implementation: - CON: The smallest timer interval is constrained by HZ (currently 1/10s). - PRO: It will still correctly fire any timer slower than HZ over a length of time, e.g. 0.11s interval -> 90 times over 10 seconds - PRO: In lag bursts, the system simly skips missed timer intervals to decrease load - CON: Algorithms depending on a timer firing "N times per minute" will fail - PRO: (Re-)scheduling is O(1) with a VERY small constant. It's a simple linked list insertion in a hash bucket. - CAUTION: The BUCKETS constant constrains how many timers can be efficiently handled. With too many hash collisions, performance will decrease. - - Major assumptions upheld: - - ALLOWS scheduling multiple timers with the same funcref/method - - ALLOWS scheduling more timers during OnUpdate processing - - ALLOWS unscheduling ANY timer (including the current running one) at any time, including during OnUpdate processing -]] - -local MAJOR, MINOR = "AceTimer-3.0", 5 +local MAJOR, MINOR = "AceTimer-3.0", 17 -- Bump minor on changes local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR) if not AceTimer then return end -- No upgrade needed - -AceTimer.hash = AceTimer.hash or {} -- Array of [0..BUCKET-1] = linked list of timers (using .next member) - -- Linked list gets around ACE-88 and ACE-90. -AceTimer.selfs = AceTimer.selfs or {} -- Array of [self]={[handle]=timerobj, [handle2]=timerobj2, ...} -AceTimer.frame = AceTimer.frame or CreateFrame("Frame", "AceTimer30Frame") +AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list +local activeTimers = AceTimer.activeTimers -- Upvalue our private data -- Lua APIs -local assert, error, loadstring = assert, error, loadstring -local setmetatable, rawset, rawget = setmetatable, rawset, rawget -local select, pairs, type, next, tostring = select, pairs, type, next, tostring -local floor, max, min = math.floor, math.max, math.min -local tconcat = table.concat - +local type, unpack, next, error, select = type, unpack, next, error, select -- WoW APIs -local GetTime = GetTime +local GetTime, C_TimerAfter = GetTime, C_Timer.After --- Global vars/functions that we don't upvalue since they might get hooked, or upgraded --- List them here for Mikk's FindGlobals script --- GLOBALS: DEFAULT_CHAT_FRAME, geterrorhandler - --- Simple ONE-SHOT timer cache. Much more efficient than a full compost for our purposes. -local timerCache = nil - ---[[ - Timers will not be fired more often than HZ-1 times per second. - Keep at intended speed PLUS ONE or we get bitten by floating point rounding errors (n.5 + 0.1 can be n.599999) - If this is ever LOWERED, all existing timers need to be enforced to have a delay >= 1/HZ on lib upgrade. - If this number is ever changed, all entries need to be rehashed on lib upgrade. - ]] -local HZ = 11 - ---[[ - Prime for good distribution - If this number is ever changed, all entries need to be rehashed on lib upgrade. -]] -local BUCKETS = 131 - -local hash = AceTimer.hash -for i=1,BUCKETS do - hash[i] = hash[i] or false -- make it an integer-indexed array; it's faster than hashes -end - ---[[ - xpcall safecall implementation -]] -local xpcall = xpcall - -local function errorhandler(err) - return geterrorhandler()(err) -end - -local function CreateDispatcher(argCount) - local code = [[ - local xpcall, eh = ... -- our arguments are received as unnamed values in "..." since we don't have a proper function declaration - local method, ARGS - local function call() return method(ARGS) end - - local function dispatch(func, ...) - method = func - if not method then return end - ARGS = ... - return xpcall(call, eh) - end - - return dispatch - ]] - - local ARGS = {} - for i = 1, argCount do ARGS[i] = "arg"..i end - code = code:gsub("ARGS", tconcat(ARGS, ", ")) - return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler) -end - -local Dispatchers = setmetatable({}, { - __index=function(self, argCount) - local dispatcher = CreateDispatcher(argCount) - rawset(self, argCount, dispatcher) - return dispatcher +local function new(self, loop, func, delay, ...) + if delay < 0.01 then + delay = 0.01 -- Restrict to the lowest time that the C_Timer API allows us end -}) -Dispatchers[0] = function(func) - return xpcall(func, errorhandler) -end -local function safecall(func, ...) - return Dispatchers[select('#', ...)](func, ...) -end + local timer = { + object = self, + func = func, + looping = loop, + argsCount = select("#", ...), + delay = delay, + ends = GetTime() + delay, + ... + } -local lastint = floor(GetTime() * HZ) + activeTimers[timer] = timer --- -------------------------------------------------------------------- --- OnUpdate handler --- --- traverse buckets, always chasing "now", and fire timers that have expired + -- Create new timer closure to wrap the "timer" object + timer.callback = function() + if not timer.cancelled then + if type(timer.func) == "string" then + -- We manually set the unpack count to prevent issues with an arg set that contains nil and ends with nil + -- e.g. local t = {1, 2, nil, 3, nil} print(#t) will result in 2, instead of 5. This fixes said issue. + timer.object[timer.func](timer.object, unpack(timer, 1, timer.argsCount)) + else + timer.func(unpack(timer, 1, timer.argsCount)) + end -local function OnUpdate() - local now = GetTime() - local nowint = floor(now * HZ) - - -- Have we passed into a new hash bucket? - if nowint == lastint then return end - - local soon = now + 1 -- +1 is safe as long as 1 < HZ < BUCKETS/2 - - -- Pass through each bucket at most once - -- Happens on e.g. instance loads, but COULD happen on high local load situations also - for curint = (max(lastint, nowint - BUCKETS) + 1), nowint do -- loop until we catch up with "now", usually only 1 iteration - local curbucket = (curint % BUCKETS)+1 - -- Yank the list of timers out of the bucket and empty it. This allows reinsertion in the currently-processed bucket from callbacks. - local nexttimer = hash[curbucket] - hash[curbucket] = false -- false rather than nil to prevent the array from becoming a hash - - while nexttimer do - local timer = nexttimer - nexttimer = timer.next - local when = timer.when - - if when < soon then - -- Call the timer func, either as a method on given object, or a straight function ref - local callback = timer.callback - if type(callback) == "string" then - safecall(timer.object[callback], timer.object, timer.arg) - elseif callback then - safecall(callback, timer.arg) - else - -- probably nilled out by CancelTimer - timer.delay = nil -- don't reschedule it - end - - local delay = timer.delay -- NOW make a local copy, can't do it earlier in case the timer cancelled itself in the callback - - if not delay then - -- single-shot timer (or cancelled) - AceTimer.selfs[timer.object][tostring(timer)] = nil - timerCache = timer - else - -- repeating timer - local newtime = when + delay - if newtime < now then -- Keep lag from making us firing a timer unnecessarily. (Note that this still won't catch too-short-delay timers though.) - newtime = now + delay - end - timer.when = newtime - - -- add next timer execution to the correct bucket - local bucket = (floor(newtime * HZ) % BUCKETS) + 1 - timer.next = hash[bucket] - hash[bucket] = timer - end - else -- if when>=soon - -- reinsert (yeah, somewhat expensive, but shouldn't be happening too often either due to hash distribution) - timer.next = hash[curbucket] - hash[curbucket] = timer - end -- if whenhandle->timer registry - local handle = tostring(timer) - - local selftimers = AceTimer.selfs[self] - if not selftimers then - selftimers = {} - AceTimer.selfs[self] = selftimers - end - selftimers[handle] = timer - selftimers.__ops = (selftimers.__ops or 0) + 1 - - return handle + C_TimerAfter(delay, timer.callback) + return timer end --- Schedule a new one-shot timer. -- The timer will fire once in `delay` seconds, unless canceled before. --- @param callback Callback function for the timer pulse (funcref or method name). +-- @param func Callback function for the timer pulse (funcref or method name). -- @param delay Delay for the timer, in seconds. --- @param arg An optional argument to be passed to the callback function. +-- @param ... An optional, unlimited amount of arguments to pass to the callback function. -- @usage --- MyAddon = LibStub("AceAddon-3.0"):NewAddon("TimerTest", "AceTimer-3.0") +-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0") -- --- function MyAddon:OnEnable() +-- function MyAddOn:OnEnable() -- self:ScheduleTimer("TimerFeedback", 5) -- end -- --- function MyAddon:TimerFeedback() +-- function MyAddOn:TimerFeedback() -- print("5 seconds passed") -- end -function AceTimer:ScheduleTimer(callback, delay, arg) - return Reg(self, callback, delay, arg) +function AceTimer:ScheduleTimer(func, delay, ...) + if not func or not delay then + error(MAJOR..": ScheduleTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2) + end + if type(func) == "string" then + if type(self) ~= "table" then + error(MAJOR..": ScheduleTimer(callback, delay, args...): 'self' - must be a table.", 2) + elseif not self[func] then + error(MAJOR..": ScheduleTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2) + end + end + return new(self, nil, func, delay, ...) end --- Schedule a repeating timer. -- The timer will fire every `delay` seconds, until canceled. --- @param callback Callback function for the timer pulse (funcref or method name). +-- @param func Callback function for the timer pulse (funcref or method name). -- @param delay Delay for the timer, in seconds. --- @param arg An optional argument to be passed to the callback function. +-- @param ... An optional, unlimited amount of arguments to pass to the callback function. -- @usage --- MyAddon = LibStub("AceAddon-3.0"):NewAddon("TimerTest", "AceTimer-3.0") +-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0") -- --- function MyAddon:OnEnable() +-- function MyAddOn:OnEnable() -- self.timerCount = 0 -- self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5) -- end -- --- function MyAddon:TimerFeedback() +-- function MyAddOn:TimerFeedback() -- self.timerCount = self.timerCount + 1 -- print(("%d seconds passed"):format(5 * self.timerCount)) -- -- run 30 seconds in total @@ -298,129 +126,124 @@ end -- self:CancelTimer(self.testTimer) -- end -- end -function AceTimer:ScheduleRepeatingTimer(callback, delay, arg) - return Reg(self, callback, delay, arg, true) +function AceTimer:ScheduleRepeatingTimer(func, delay, ...) + if not func or not delay then + error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2) + end + if type(func) == "string" then + if type(self) ~= "table" then + error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'self' - must be a table.", 2) + elseif not self[func] then + error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2) + end + end + return new(self, true, func, delay, ...) end ---- Cancels a timer with the given handle, registered by the same addon object as used for `:ScheduleTimer` --- Both one-shot and repeating timers can be canceled with this function, as long as the `handle` is valid +--- Cancels a timer with the given id, registered by the same addon object as used for `:ScheduleTimer` +-- Both one-shot and repeating timers can be canceled with this function, as long as the `id` is valid -- and the timer has not fired yet or was canceled before. --- @param handle The handle of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` --- @param silent If true, no error is raised if the timer handle is invalid (expired or already canceled) --- @return True if the timer was successfully cancelled. -function AceTimer:CancelTimer(handle, silent) - if not handle then return end -- nil handle -> bail out without erroring - if type(handle) ~= "string" then - error(MAJOR..": CancelTimer(handle): 'handle' - expected a string", 2) -- for now, anyway - end - local selftimers = AceTimer.selfs[self] - local timer = selftimers and selftimers[handle] - if silent then - if timer then - timer.callback = nil -- don't run it again - timer.delay = nil -- if this is the currently-executing one: don't even reschedule - -- The timer object is removed in the OnUpdate loop - end - return not not timer -- might return "true" even if we double-cancel. we'll live. +-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` +function AceTimer:CancelTimer(id) + local timer = activeTimers[id] + + if not timer then + return false else - if not timer then - geterrorhandler()(MAJOR..": CancelTimer(handle[, silent]): '"..tostring(handle).."' - no such timer registered") - return false - end - if not timer.callback then - geterrorhandler()(MAJOR..": CancelTimer(handle[, silent]): '"..tostring(handle).."' - timer already cancelled or expired") - return false - end - timer.callback = nil -- don't run it again - timer.delay = nil -- if this is the currently-executing one: don't even reschedule + timer.cancelled = true + activeTimers[id] = nil return true end end --- Cancels all timers registered to the current addon object ('self') function AceTimer:CancelAllTimers() - if not(type(self) == "string" or type(self) == "table") then - error(MAJOR..": CancelAllTimers(): 'self' - must be a string or a table",2) - end - if self == AceTimer then - error(MAJOR..": CancelAllTimers(): supply a meaningful 'self'", 2) - end - - local selftimers = AceTimer.selfs[self] - if selftimers then - for handle,v in pairs(selftimers) do - if type(v) == "table" then -- avoid __ops, etc - AceTimer.CancelTimer(self, handle, true) - end + for k,v in next, activeTimers do + if v.object == self then + AceTimer.CancelTimer(self, k) end end end ---- Returns the time left for a timer with the given handle, registered by the current addon object ('self'). --- This function will raise a warning when the handle is invalid, but not stop execution. --- @param handle The handle of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` --- @return The time left on the timer, or false if the handle is invalid. -function AceTimer:TimeLeft(handle) - if not handle then return end - if type(handle) ~= "string" then - error(MAJOR..": TimeLeft(handle): 'handle' - expected a string", 2) -- for now, anyway - end - local selftimers = AceTimer.selfs[self] - local timer = selftimers and selftimers[handle] +--- Returns the time left for a timer with the given id, registered by the current addon object ('self'). +-- This function will return 0 when the id is invalid. +-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` +-- @return The time left on the timer. +function AceTimer:TimeLeft(id) + local timer = activeTimers[id] if not timer then - geterrorhandler()(MAJOR..": TimeLeft(handle): '"..tostring(handle).."' - no such timer registered") - return false + return 0 + else + return timer.ends - GetTime() end - return timer.when - GetTime() end -- --------------------------------------------------------------------- --- PLAYER_REGEN_ENABLED: Run through our .selfs[] array step by step --- and clean it out - otherwise the table indices can grow indefinitely --- if an addon starts and stops a lot of timers. AceBucket does this! --- --- See ACE-94 and tests/AceTimer-3.0-ACE-94.lua +-- Upgrading -local lastCleaned = nil - -local function OnEvent(this, event) - if event~="PLAYER_REGEN_ENABLED" then - return +-- Upgrade from old hash-bucket based timers to C_Timer.After timers. +if oldminor and oldminor < 10 then + -- disable old timer logic + AceTimer.frame:SetScript("OnUpdate", nil) + AceTimer.frame:SetScript("OnEvent", nil) + AceTimer.frame:UnregisterAllEvents() + -- convert timers + for object,timers in next, AceTimer.selfs do + for handle,timer in next, timers do + if type(timer) == "table" and timer.callback then + local newTimer + if timer.delay then + newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.callback, timer.delay, timer.arg) + else + newTimer = AceTimer.ScheduleTimer(timer.object, timer.callback, timer.when - GetTime(), timer.arg) + end + -- Use the old handle for old timers + activeTimers[newTimer] = nil + activeTimers[handle] = newTimer + newTimer.handle = handle + end + end + end + AceTimer.selfs = nil + AceTimer.hash = nil + AceTimer.debug = nil +elseif oldminor and oldminor < 17 then + -- Upgrade from old animation based timers to C_Timer.After timers. + AceTimer.inactiveTimers = nil + AceTimer.frame = nil + local oldTimers = AceTimer.activeTimers + -- Clear old timer table and update upvalue + AceTimer.activeTimers = {} + activeTimers = AceTimer.activeTimers + for handle, timer in next, oldTimers do + local newTimer + -- Stop the old timer animation + local duration, elapsed = timer:GetDuration(), timer:GetElapsed() + timer:GetParent():Stop() + if timer.looping then + newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.func, duration, unpack(timer.args, 1, timer.argsCount)) + else + newTimer = AceTimer.ScheduleTimer(timer.object, timer.func, duration - elapsed, unpack(timer.args, 1, timer.argsCount)) + end + -- Use the old handle for old timers + activeTimers[newTimer] = nil + activeTimers[handle] = newTimer + newTimer.handle = handle end - -- Get the next 'self' to process - local selfs = AceTimer.selfs - local self = next(selfs, lastCleaned) - if not self then - self = next(selfs) + -- Migrate transitional handles + if oldminor < 13 and AceTimer.hashCompatTable then + for handle, id in next, AceTimer.hashCompatTable do + local t = activeTimers[id] + if t then + activeTimers[id] = nil + activeTimers[handle] = t + t.handle = handle + end + end + AceTimer.hashCompatTable = nil end - lastCleaned = self - if not self then -- should only happen if .selfs[] is empty - return - end - - -- Time to clean it out? - local list = selfs[self] - if (list.__ops or 0) < 250 then -- 250 slosh indices = ~10KB wasted (max!). For one 'self'. - return - end - - -- Create a new table and copy all members over - local newlist = {} - local n=0 - for k,v in pairs(list) do - newlist[k] = v - n=n+1 - end - newlist.__ops = 0 -- Reset operation count - - -- And since we now have a count of the number of live timers, check that it's reasonable. Emit a warning if not. - if n>BUCKETS then - DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: The addon/module '"..tostring(self).."' has "..n.." live timers. Surely that's not intended?") - end - - selfs[self] = newlist end -- --------------------------------------------------------------------- @@ -436,38 +259,20 @@ local mixins = { function AceTimer:Embed(target) AceTimer.embeds[target] = true - for _,v in pairs(mixins) do + for _,v in next, mixins do target[v] = AceTimer[v] end return target end --- AceTimer:OnEmbedDisable( target ) +-- AceTimer:OnEmbedDisable(target) -- target (object) - target object that AceTimer is embedded in. -- -- cancel all timers registered for the object -function AceTimer:OnEmbedDisable( target ) +function AceTimer:OnEmbedDisable(target) target:CancelAllTimers() end - -for addon in pairs(AceTimer.embeds) do +for addon in next, AceTimer.embeds do AceTimer:Embed(addon) end - --- --------------------------------------------------------------------- --- Debug tools (expose copies of internals to test suites) -AceTimer.debug = AceTimer.debug or {} -AceTimer.debug.HZ = HZ -AceTimer.debug.BUCKETS = BUCKETS - --- --------------------------------------------------------------------- --- Finishing touchups - -AceTimer.frame:SetScript("OnUpdate", OnUpdate) -AceTimer.frame:SetScript("OnEvent", OnEvent) -AceTimer.frame:RegisterEvent("PLAYER_REGEN_ENABLED") - --- In theory, we should hide&show the frame based on there being timers or not. --- However, this job is fairly expensive, and the chance that there will --- actually be zero timers running is diminuitive to say the lest. diff --git a/ProfessionMenu/Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua b/ProfessionMenu/Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua index 9016d1e..05fb9d2 100644 --- a/ProfessionMenu/Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua +++ b/ProfessionMenu/Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua @@ -1,61 +1,26 @@ ---[[ $Id: CallbackHandler-1.0.lua 3 2008-09-29 16:54:20Z nevcairiel $ ]] -local MAJOR, MINOR = "CallbackHandler-1.0", 3 +--[[ $Id: CallbackHandler-1.0.lua 25 2022-12-12 15:02:36Z nevcairiel $ ]] +local MAJOR, MINOR = "CallbackHandler-1.0", 8 local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR) if not CallbackHandler then return end -- No upgrade needed local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end} -local type = type -local pcall = pcall -local pairs = pairs -local assert = assert -local concat = table.concat -local loadstring = loadstring -local next = next -local select = select -local type = type -local xpcall = xpcall +-- Lua APIs +local securecallfunction, error = securecallfunction, error +local setmetatable, rawget = setmetatable, rawget +local next, select, pairs, type, tostring = next, select, pairs, type, tostring -local function errorhandler(err) - return geterrorhandler()(err) + +local function Dispatch(handlers, ...) + local index, method = next(handlers) + if not method then return end + repeat + securecallfunction(method, ...) + index, method = next(handlers, index) + until not method end -local function CreateDispatcher(argCount) - local code = [[ - local next, xpcall, eh = ... - - local method, ARGS - local function call() method(ARGS) end - - local function dispatch(handlers, ...) - local index - index, method = next(handlers) - if not method then return end - local OLD_ARGS = ARGS - ARGS = ... - repeat - xpcall(call, eh) - index, method = next(handlers, index) - until not method - ARGS = OLD_ARGS - end - - return dispatch - ]] - - local ARGS, OLD_ARGS = {}, {} - for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end - code = code:gsub("OLD_ARGS", concat(OLD_ARGS, ", ")):gsub("ARGS", concat(ARGS, ", ")) - return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler) -end - -local Dispatchers = setmetatable({}, {__index=function(self, argCount) - local dispatcher = CreateDispatcher(argCount) - rawset(self, argCount, dispatcher) - return dispatcher -end}) - -------------------------------------------------------------------------- -- CallbackHandler:New -- @@ -64,9 +29,7 @@ end}) -- UnregisterName - name of the callback unregistration API, default "UnregisterCallback" -- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API. -function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName, OnUsed, OnUnused) - -- TODO: Remove this after beta has gone out - assert(not OnUsed and not OnUnused, "ACE-80: OnUsed/OnUnused are deprecated. Callbacks are now done to registry.OnUsed and registry.OnUnused") +function CallbackHandler.New(_self, target, RegisterName, UnregisterName, UnregisterAllName) RegisterName = RegisterName or "RegisterCallback" UnregisterName = UnregisterName or "UnregisterCallback" @@ -88,19 +51,19 @@ function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAll local oldrecurse = registry.recurse registry.recurse = oldrecurse + 1 - Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...) + Dispatch(events[eventname], eventname, ...) registry.recurse = oldrecurse if registry.insertQueue and oldrecurse==0 then -- Something in one of our callbacks wanted to register more callbacks; they got queued - for eventname,callbacks in pairs(registry.insertQueue) do - local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. - for self,func in pairs(callbacks) do - events[eventname][self] = func + for event,callbacks in pairs(registry.insertQueue) do + local first = not rawget(events, event) or not next(events[event]) -- test for empty before. not test for one member after. that one member may have been overwritten. + for object,func in pairs(callbacks) do + events[event][object] = func -- fire OnUsed callback? if first and registry.OnUsed then - registry.OnUsed(registry, target, eventname) + registry.OnUsed(registry, target, event) first = nil end end @@ -146,9 +109,9 @@ function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAll regfunc = function(...) self[method](self,...) end end else - -- function ref with self=object or self="addonId" - if type(self)~="table" and type(self)~="string" then - error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string expected.", 2) + -- function ref with self=object or self="addonId" or self=thread + if type(self)~="table" and type(self)~="string" and type(self)~="thread" then + error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread expected.", 2) end if select("#",...)>=1 then -- this is not the same as testing for arg==nil! diff --git a/ProfessionMenu/Libs/LibStub/LibStub.lua b/ProfessionMenu/Libs/LibStub/LibStub.lua index 0a41ac0..d50c267 100644 --- a/ProfessionMenu/Libs/LibStub/LibStub.lua +++ b/ProfessionMenu/Libs/LibStub/LibStub.lua @@ -7,24 +7,24 @@ if not LibStub or LibStub.minor < LIBSTUB_MINOR then LibStub = LibStub or {libs = {}, minors = {} } _G[LIBSTUB_MAJOR] = LibStub LibStub.minor = LIBSTUB_MINOR - + function LibStub:NewLibrary(major, minor) assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)") - minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.") - + minor = assert(tonumber(string.match(minor, "%d+")), "Minor version must either be a number or contain a number.") + local oldminor = self.minors[major] if oldminor and oldminor >= minor then return nil end self.minors[major], self.libs[major] = minor, self.libs[major] or {} return self.libs[major], oldminor end - + function LibStub:GetLibrary(major, silent) if not self.libs[major] and not silent then error(("Cannot find a library instance of %q."):format(tostring(major)), 2) end return self.libs[major], self.minors[major] end - + function LibStub:IterateLibraries() return pairs(self.libs) end setmetatable(LibStub, { __call = LibStub.GetLibrary }) end diff --git a/ProfessionMenu/Libs/LibStub/LibStub.toc b/ProfessionMenu/Libs/LibStub/LibStub.toc deleted file mode 100644 index 17cf732..0000000 --- a/ProfessionMenu/Libs/LibStub/LibStub.toc +++ /dev/null @@ -1,13 +0,0 @@ -## Interface: 20400 -## Title: Lib: LibStub -## Notes: Universal Library Stub -## Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel -## X-Website: http://jira.wowace.com/browse/LS -## X-Category: Library -## X-License: Public Domain -## X-Curse-Packaged-Version: 1.0 -## X-Curse-Project-Name: LibStub -## X-Curse-Project-ID: libstub -## X-Curse-Repository-ID: wow/libstub/mainline - -LibStub.lua