chore(libs): sync Ace3 to coa-ace3 (WoWUIDev master @ 52e5f2c)
Bring every embedded Ace3 / CallbackHandler / LibStub copy in line with the canonical Exiles/coa-ace3 bundle so LibStub resolution is predictable across all Exiles forks regardless of which addons are enabled. Libraries updated in this fork: AceAddon-3.0 13 (5 → 13) AceComm-3.0 14 (6 → 14) AceConsole-3.0 7 AceDB-3.0 33 (21 → 33) AceEvent-3.0 4 (3 → 4) AceSerializer-3.0 5 (3 → 5) AceTimer-3.0 17 (5 → 17) CallbackHandler-1.0 8 (3 → 8) LibStub 2
This commit is contained in:
@@ -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.
|
-- * **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.
|
-- * **OnDisable**, which is only called when your addon is manually being disabled.
|
||||||
-- @usage
|
-- @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.
|
-- -- but shows usage of the callbacks.
|
||||||
-- local MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon")
|
-- local MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon")
|
||||||
--
|
--
|
||||||
-- function MyAddon:OnInitialize()
|
-- 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.
|
-- -- or setting up slash commands.
|
||||||
-- end
|
-- end
|
||||||
--
|
--
|
||||||
-- function MyAddon:OnEnable()
|
-- function MyAddon:OnEnable()
|
||||||
-- -- Do more initialization here, that really enables the use of your addon.
|
-- -- 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
|
-- -- the game that wasn't available in OnInitialize
|
||||||
-- end
|
-- end
|
||||||
--
|
--
|
||||||
-- function MyAddon:OnDisable()
|
-- function MyAddon:OnDisable()
|
||||||
-- -- Unhook, Unregister Events, Hide frames that you created.
|
-- -- 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.
|
-- -- build a "standby" mode, or be able to toggle modules on/off.
|
||||||
-- end
|
-- end
|
||||||
-- @class file
|
-- @class file
|
||||||
-- @name AceAddon-3.0.lua
|
-- @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)
|
local AceAddon, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||||
|
|
||||||
if not AceAddon then return end -- No Upgrade needed.
|
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 loadstring, assert, error = loadstring, assert, error
|
||||||
local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget
|
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
|
xpcall safecall implementation
|
||||||
]]
|
]]
|
||||||
@@ -62,43 +58,12 @@ local function errorhandler(err)
|
|||||||
return geterrorhandler()(err)
|
return geterrorhandler()(err)
|
||||||
end
|
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, ...)
|
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
|
-- 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
|
-- this safecall is used for optional functions like OnInitialize OnEnable etc. When they are not
|
||||||
-- present execution should continue without hinderance
|
-- present execution should continue without hinderance
|
||||||
if type(func) == "function" then
|
if type(func) == "function" then
|
||||||
return Dispatchers[select('#', ...)](func, ...)
|
return xpcall(func, errorhandler, ...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -106,17 +71,27 @@ end
|
|||||||
local Enable, Disable, EnableModule, DisableModule, Embed, NewModule, GetModule, GetName, SetDefaultModuleState, SetDefaultModuleLibraries, SetEnabledState, SetDefaultModulePrototype
|
local Enable, Disable, EnableModule, DisableModule, Embed, NewModule, GetModule, GetName, SetDefaultModuleState, SetDefaultModuleLibraries, SetEnabledState, SetDefaultModulePrototype
|
||||||
|
|
||||||
-- used in the addon metatable
|
-- 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.
|
--- 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.
|
-- its OnInitialize and OnEnable callbacks.
|
||||||
-- The final addon object, with all libraries embeded, will be returned.
|
-- The final addon object, with all libraries embeded, will be returned.
|
||||||
-- @paramsig [object ,]name[, lib, ...]
|
-- @paramsig [object ,]name[, lib, ...]
|
||||||
-- @param object Table to use as a base for the addon (optional)
|
-- @param object Table to use as a base for the addon (optional)
|
||||||
-- @param name Name of the addon object to create
|
-- @param name Name of the addon object to create
|
||||||
-- @param lib List of libraries to embed into the addon
|
-- @param lib List of libraries to embed into the addon
|
||||||
-- @usage
|
-- @usage
|
||||||
-- -- Create a simple addon object
|
-- -- Create a simple addon object
|
||||||
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceEvent-3.0")
|
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceEvent-3.0")
|
||||||
--
|
--
|
||||||
@@ -136,10 +111,10 @@ function AceAddon:NewAddon(objectorname, ...)
|
|||||||
if type(name)~="string" then
|
if type(name)~="string" then
|
||||||
error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2)
|
error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2)
|
||||||
end
|
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)
|
error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - Addon '%s' already exists."):format(name), 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
object = object or {}
|
object = object or {}
|
||||||
object.name = name
|
object.name = name
|
||||||
|
|
||||||
@@ -149,14 +124,15 @@ function AceAddon:NewAddon(objectorname, ...)
|
|||||||
for k, v in pairs(oldmeta) do addonmeta[k] = v end
|
for k, v in pairs(oldmeta) do addonmeta[k] = v end
|
||||||
end
|
end
|
||||||
addonmeta.__tostring = addontostring
|
addonmeta.__tostring = addontostring
|
||||||
|
|
||||||
setmetatable( object, addonmeta )
|
setmetatable( object, addonmeta )
|
||||||
self.addons[name] = object
|
self.addons[name] = object
|
||||||
object.modules = {}
|
object.modules = {}
|
||||||
|
object.orderedModules = {}
|
||||||
object.defaultModuleLibraries = {}
|
object.defaultModuleLibraries = {}
|
||||||
Embed( object ) -- embed NewModule, GetModule methods
|
Embed( object ) -- embed NewModule, GetModule methods
|
||||||
self:EmbedLibraries(object, select(i,...))
|
self:EmbedLibraries(object, select(i,...))
|
||||||
|
|
||||||
-- add to queue of addons to be initialized upon ADDON_LOADED
|
-- add to queue of addons to be initialized upon ADDON_LOADED
|
||||||
tinsert(self.initializequeue, object)
|
tinsert(self.initializequeue, object)
|
||||||
return object
|
return object
|
||||||
@@ -167,7 +143,7 @@ end
|
|||||||
-- Throws an error if the addon object cannot be found (except if silent is set).
|
-- Throws an error if the addon object cannot be found (except if silent is set).
|
||||||
-- @param name unique name of the addon object
|
-- @param name unique name of the addon object
|
||||||
-- @param silent if true, the addon is optional, silently return nil if its not found
|
-- @param silent if true, the addon is optional, silently return nil if its not found
|
||||||
-- @usage
|
-- @usage
|
||||||
-- -- Get the Addon
|
-- -- Get the Addon
|
||||||
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
||||||
function AceAddon:GetAddon(name, silent)
|
function AceAddon:GetAddon(name, silent)
|
||||||
@@ -222,7 +198,7 @@ end
|
|||||||
-- @paramsig name[, silent]
|
-- @paramsig name[, silent]
|
||||||
-- @param name unique name of the module
|
-- @param name unique name of the module
|
||||||
-- @param silent if true, the module is optional, silently return nil if its not found (optional)
|
-- @param silent if true, the module is optional, silently return nil if its not found (optional)
|
||||||
-- @usage
|
-- @usage
|
||||||
-- -- Get the Addon
|
-- -- Get the Addon
|
||||||
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
||||||
-- -- Get the Module
|
-- -- Get the Module
|
||||||
@@ -245,23 +221,23 @@ local function IsModuleTrue(self) return true end
|
|||||||
-- @param name unique name of the module
|
-- @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 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
|
-- @param lib List of libraries to embed into the addon
|
||||||
-- @usage
|
-- @usage
|
||||||
-- -- Create a module with some embeded libraries
|
-- -- Create a module with some embeded libraries
|
||||||
-- MyModule = MyAddon:NewModule("MyModule", "AceEvent-3.0", "AceHook-3.0")
|
-- MyModule = MyAddon:NewModule("MyModule", "AceEvent-3.0", "AceHook-3.0")
|
||||||
--
|
--
|
||||||
-- -- Create a module with a prototype
|
-- -- Create a module with a prototype
|
||||||
-- local prototype = { OnEnable = function(self) print("OnEnable called!") end }
|
-- local prototype = { OnEnable = function(self) print("OnEnable called!") end }
|
||||||
-- MyModule = MyAddon:NewModule("MyModule", prototype, "AceEvent-3.0", "AceHook-3.0")
|
-- MyModule = MyAddon:NewModule("MyModule", prototype, "AceEvent-3.0", "AceHook-3.0")
|
||||||
function NewModule(self, name, prototype, ...)
|
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(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 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
|
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.
|
-- 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.
|
-- 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))
|
local module = AceAddon:NewAddon(fmt("%s_%s", self.name or tostring(self), name))
|
||||||
|
|
||||||
module.IsModule = IsModuleTrue
|
module.IsModule = IsModuleTrue
|
||||||
module:SetEnabledState(self.defaultModuleState)
|
module:SetEnabledState(self.defaultModuleState)
|
||||||
module.moduleName = name
|
module.moduleName = name
|
||||||
@@ -276,23 +252,24 @@ function NewModule(self, name, prototype, ...)
|
|||||||
if not prototype or type(prototype) == "string" then
|
if not prototype or type(prototype) == "string" then
|
||||||
prototype = self.defaultModulePrototype or nil
|
prototype = self.defaultModulePrototype or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(prototype) == "table" then
|
if type(prototype) == "table" then
|
||||||
local mt = getmetatable(module)
|
local mt = getmetatable(module)
|
||||||
mt.__index = prototype
|
mt.__index = prototype
|
||||||
setmetatable(module, mt) -- More of a Base class type feel.
|
setmetatable(module, mt) -- More of a Base class type feel.
|
||||||
end
|
end
|
||||||
|
|
||||||
safecall(self.OnModuleCreated, self, module) -- Was in Ace2 and I think it could be a cool thing to have handy.
|
safecall(self.OnModuleCreated, self, module) -- Was in Ace2 and I think it could be a cool thing to have handy.
|
||||||
self.modules[name] = module
|
self.modules[name] = module
|
||||||
|
tinsert(self.orderedModules, module)
|
||||||
|
|
||||||
return module
|
return module
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns the real name of the addon or module, without any prefix.
|
--- Returns the real name of the addon or module, without any prefix.
|
||||||
-- @name //addon//:GetName
|
-- @name //addon//:GetName
|
||||||
-- @paramsig
|
-- @paramsig
|
||||||
-- @usage
|
-- @usage
|
||||||
-- print(MyAddon:GetName())
|
-- print(MyAddon:GetName())
|
||||||
-- -- prints "MyAddon"
|
-- -- prints "MyAddon"
|
||||||
function GetName(self)
|
function GetName(self)
|
||||||
@@ -304,15 +281,20 @@ end
|
|||||||
-- and enabling all modules of the addon (unless explicitly disabled).\\
|
-- and enabling all modules of the addon (unless explicitly disabled).\\
|
||||||
-- :Enable() also sets the internal `enableState` variable to true
|
-- :Enable() also sets the internal `enableState` variable to true
|
||||||
-- @name //addon//:Enable
|
-- @name //addon//:Enable
|
||||||
-- @paramsig
|
-- @paramsig
|
||||||
-- @usage
|
-- @usage
|
||||||
-- -- Enable MyModule
|
-- -- Enable MyModule
|
||||||
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
||||||
-- MyModule = MyAddon:GetModule("MyModule")
|
-- MyModule = MyAddon:GetModule("MyModule")
|
||||||
-- MyModule:Enable()
|
-- MyModule:Enable()
|
||||||
function Enable(self)
|
function Enable(self)
|
||||||
self:SetEnabledState(true)
|
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
|
end
|
||||||
|
|
||||||
--- Disables the Addon, if possible, return true or false depending on success.
|
--- Disables the Addon, if possible, return true or false depending on success.
|
||||||
@@ -320,8 +302,8 @@ end
|
|||||||
-- and disabling all modules of the addon.\\
|
-- and disabling all modules of the addon.\\
|
||||||
-- :Disable() also sets the internal `enableState` variable to false
|
-- :Disable() also sets the internal `enableState` variable to false
|
||||||
-- @name //addon//:Disable
|
-- @name //addon//:Disable
|
||||||
-- @paramsig
|
-- @paramsig
|
||||||
-- @usage
|
-- @usage
|
||||||
-- -- Disable MyAddon
|
-- -- Disable MyAddon
|
||||||
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
||||||
-- MyAddon:Disable()
|
-- MyAddon:Disable()
|
||||||
@@ -334,7 +316,7 @@ end
|
|||||||
-- Short-hand function that retrieves the module via `:GetModule` and calls `:Enable` on the module object.
|
-- Short-hand function that retrieves the module via `:GetModule` and calls `:Enable` on the module object.
|
||||||
-- @name //addon//:EnableModule
|
-- @name //addon//:EnableModule
|
||||||
-- @paramsig name
|
-- @paramsig name
|
||||||
-- @usage
|
-- @usage
|
||||||
-- -- Enable MyModule using :GetModule
|
-- -- Enable MyModule using :GetModule
|
||||||
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
||||||
-- MyModule = MyAddon:GetModule("MyModule")
|
-- 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.
|
-- Short-hand function that retrieves the module via `:GetModule` and calls `:Disable` on the module object.
|
||||||
-- @name //addon//:DisableModule
|
-- @name //addon//:DisableModule
|
||||||
-- @paramsig name
|
-- @paramsig name
|
||||||
-- @usage
|
-- @usage
|
||||||
-- -- Disable MyModule using :GetModule
|
-- -- Disable MyModule using :GetModule
|
||||||
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
||||||
-- MyModule = MyAddon:GetModule("MyModule")
|
-- MyModule = MyAddon:GetModule("MyModule")
|
||||||
@@ -371,7 +353,7 @@ end
|
|||||||
-- @name //addon//:SetDefaultModuleLibraries
|
-- @name //addon//:SetDefaultModuleLibraries
|
||||||
-- @paramsig lib[, lib, ...]
|
-- @paramsig lib[, lib, ...]
|
||||||
-- @param lib List of libraries to embed into the addon
|
-- @param lib List of libraries to embed into the addon
|
||||||
-- @usage
|
-- @usage
|
||||||
-- -- Create the addon object
|
-- -- Create the addon object
|
||||||
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon")
|
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon")
|
||||||
-- -- Configure default libraries for modules (all modules need AceEvent-3.0)
|
-- -- Configure default libraries for modules (all modules need AceEvent-3.0)
|
||||||
@@ -390,7 +372,7 @@ end
|
|||||||
-- @name //addon//:SetDefaultModuleState
|
-- @name //addon//:SetDefaultModuleState
|
||||||
-- @paramsig state
|
-- @paramsig state
|
||||||
-- @param state Default state for new modules, true for enabled, false for disabled
|
-- @param state Default state for new modules, true for enabled, false for disabled
|
||||||
-- @usage
|
-- @usage
|
||||||
-- -- Create the addon object
|
-- -- Create the addon object
|
||||||
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon")
|
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon")
|
||||||
-- -- Set the default state to "disabled"
|
-- -- Set the default state to "disabled"
|
||||||
@@ -410,7 +392,7 @@ end
|
|||||||
-- @name //addon//:SetDefaultModulePrototype
|
-- @name //addon//:SetDefaultModulePrototype
|
||||||
-- @paramsig prototype
|
-- @paramsig prototype
|
||||||
-- @param prototype Default prototype for the new modules (table)
|
-- @param prototype Default prototype for the new modules (table)
|
||||||
-- @usage
|
-- @usage
|
||||||
-- -- Define a prototype
|
-- -- Define a prototype
|
||||||
-- local prototype = { OnEnable = function(self) print("OnEnable called!") end }
|
-- local prototype = { OnEnable = function(self) print("OnEnable called!") end }
|
||||||
-- -- Set the default prototype
|
-- -- Set the default prototype
|
||||||
@@ -442,8 +424,8 @@ end
|
|||||||
|
|
||||||
--- Return an iterator of all modules associated to the addon.
|
--- Return an iterator of all modules associated to the addon.
|
||||||
-- @name //addon//:IterateModules
|
-- @name //addon//:IterateModules
|
||||||
-- @paramsig
|
-- @paramsig
|
||||||
-- @usage
|
-- @usage
|
||||||
-- -- Enable all modules
|
-- -- Enable all modules
|
||||||
-- for name, module in MyAddon:IterateModules() do
|
-- for name, module in MyAddon:IterateModules() do
|
||||||
-- module:Enable()
|
-- module:Enable()
|
||||||
@@ -452,13 +434,13 @@ local function IterateModules(self) return pairs(self.modules) end
|
|||||||
|
|
||||||
-- Returns an iterator of all embeds in the addon
|
-- Returns an iterator of all embeds in the addon
|
||||||
-- @name //addon//:IterateEmbeds
|
-- @name //addon//:IterateEmbeds
|
||||||
-- @paramsig
|
-- @paramsig
|
||||||
local function IterateEmbeds(self) return pairs(AceAddon.embeds[self]) end
|
local function IterateEmbeds(self) return pairs(AceAddon.embeds[self]) end
|
||||||
|
|
||||||
--- Query the enabledState of an addon.
|
--- Query the enabledState of an addon.
|
||||||
-- @name //addon//:IsEnabled
|
-- @name //addon//:IsEnabled
|
||||||
-- @paramsig
|
-- @paramsig
|
||||||
-- @usage
|
-- @usage
|
||||||
-- if MyAddon:IsEnabled() then
|
-- if MyAddon:IsEnabled() then
|
||||||
-- MyAddon:Disable()
|
-- MyAddon:Disable()
|
||||||
-- end
|
-- end
|
||||||
@@ -489,32 +471,34 @@ local pmixins = {
|
|||||||
-- target (object) - target object to embed aceaddon in
|
-- target (object) - target object to embed aceaddon in
|
||||||
--
|
--
|
||||||
-- this is a local function specifically since it's meant to be only called internally
|
-- 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
|
for k, v in pairs(mixins) do
|
||||||
target[k] = v
|
target[k] = v
|
||||||
end
|
end
|
||||||
for k, v in pairs(pmixins) do
|
if not skipPMixins then
|
||||||
target[k] = target[k] or v
|
for k, v in pairs(pmixins) do
|
||||||
|
target[k] = target[k] or v
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- - Initialize the addon after creation.
|
-- - Initialize the addon after creation.
|
||||||
-- This function is only used internally during the ADDON_LOADED event
|
-- 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.
|
-- 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.
|
-- **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
|
-- @param addon addon object to intialize
|
||||||
function AceAddon:InitializeAddon(addon)
|
function AceAddon:InitializeAddon(addon)
|
||||||
safecall(addon.OnInitialize, addon)
|
safecall(addon.OnInitialize, addon)
|
||||||
|
|
||||||
local embeds = self.embeds[addon]
|
local embeds = self.embeds[addon]
|
||||||
for i = 1, #embeds do
|
for i = 1, #embeds do
|
||||||
local lib = LibStub:GetLibrary(embeds[i], true)
|
local lib = LibStub:GetLibrary(embeds[i], true)
|
||||||
if lib then safecall(lib.OnEmbedInitialize, lib, addon) end
|
if lib then safecall(lib.OnEmbedInitialize, lib, addon) end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- we don't call InitializeAddon on modules specifically, this is handled
|
-- we don't call InitializeAddon on modules specifically, this is handled
|
||||||
-- from the event handler and only done _once_
|
-- from the event handler and only done _once_
|
||||||
end
|
end
|
||||||
@@ -522,7 +506,7 @@ end
|
|||||||
-- - Enable the addon after creation.
|
-- - Enable the addon after creation.
|
||||||
-- Note: This function is only used internally during the PLAYER_LOGIN event, or during ADDON_LOADED,
|
-- 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.
|
-- 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.\\
|
-- 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.
|
-- 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)
|
function AceAddon:EnableAddon(addon)
|
||||||
if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end
|
if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end
|
||||||
if self.statuses[addon.name] or not addon.enabledState then return false 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.
|
-- set the statuses first, before calling the OnEnable. this allows for Disabling of the addon in OnEnable.
|
||||||
self.statuses[addon.name] = true
|
self.statuses[addon.name] = true
|
||||||
|
|
||||||
safecall(addon.OnEnable, addon)
|
safecall(addon.OnEnable, addon)
|
||||||
|
|
||||||
-- make sure we're still enabled before continueing
|
-- make sure we're still enabled before continueing
|
||||||
if self.statuses[addon.name] then
|
if self.statuses[addon.name] then
|
||||||
local embeds = self.embeds[addon]
|
local embeds = self.embeds[addon]
|
||||||
@@ -545,10 +529,11 @@ function AceAddon:EnableAddon(addon)
|
|||||||
local lib = LibStub:GetLibrary(embeds[i], true)
|
local lib = LibStub:GetLibrary(embeds[i], true)
|
||||||
if lib then safecall(lib.OnEmbedEnable, lib, addon) end
|
if lib then safecall(lib.OnEmbedEnable, lib, addon) end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- enable possible modules.
|
-- enable possible modules.
|
||||||
for name, module in pairs(addon.modules) do
|
local modules = addon.orderedModules
|
||||||
self:EnableAddon(module)
|
for i = 1, #modules do
|
||||||
|
self:EnableAddon(modules[i])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return self.statuses[addon.name] -- return true if we're disabled
|
return self.statuses[addon.name] -- return true if we're disabled
|
||||||
@@ -556,40 +541,41 @@ end
|
|||||||
|
|
||||||
-- - Disable the addon
|
-- - Disable the addon
|
||||||
-- Note: This function is only used internally.
|
-- 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.\\
|
-- 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.
|
-- 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.
|
-- Use :Disable on the addon itself instead.
|
||||||
-- @param addon addon object to enable
|
-- @param addon addon object to enable
|
||||||
function AceAddon:DisableAddon(addon)
|
function AceAddon:DisableAddon(addon)
|
||||||
if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end
|
if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end
|
||||||
if not self.statuses[addon.name] then return false 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.
|
-- set statuses first before calling OnDisable, this allows for aborting the disable in OnDisable.
|
||||||
self.statuses[addon.name] = false
|
self.statuses[addon.name] = false
|
||||||
|
|
||||||
safecall( addon.OnDisable, addon )
|
safecall( addon.OnDisable, addon )
|
||||||
|
|
||||||
-- make sure we're still disabling...
|
-- 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]
|
local embeds = self.embeds[addon]
|
||||||
for i = 1, #embeds do
|
for i = 1, #embeds do
|
||||||
local lib = LibStub:GetLibrary(embeds[i], true)
|
local lib = LibStub:GetLibrary(embeds[i], true)
|
||||||
if lib then safecall(lib.OnEmbedDisable, lib, addon) end
|
if lib then safecall(lib.OnEmbedDisable, lib, addon) end
|
||||||
end
|
end
|
||||||
-- disable possible modules.
|
-- disable possible modules.
|
||||||
for name, module in pairs(addon.modules) do
|
local modules = addon.orderedModules
|
||||||
self:DisableAddon(module)
|
for i = 1, #modules do
|
||||||
|
self:DisableAddon(modules[i])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return not self.statuses[addon.name] -- return true if we're disabled
|
return not self.statuses[addon.name] -- return true if we're disabled
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get an iterator over all registered addons.
|
--- Get an iterator over all registered addons.
|
||||||
-- @usage
|
-- @usage
|
||||||
-- -- Print a list of all installed AceAddon's
|
-- -- Print a list of all installed AceAddon's
|
||||||
-- for name, addon in AceAddon:IterateAddons() do
|
-- for name, addon in AceAddon:IterateAddons() do
|
||||||
-- print("Addon: " .. name)
|
-- print("Addon: " .. name)
|
||||||
@@ -597,7 +583,7 @@ end
|
|||||||
function AceAddon:IterateAddons() return pairs(self.addons) end
|
function AceAddon:IterateAddons() return pairs(self.addons) end
|
||||||
|
|
||||||
--- Get an iterator over the internal status registry.
|
--- Get an iterator over the internal status registry.
|
||||||
-- @usage
|
-- @usage
|
||||||
-- -- Print a list of all enabled addons
|
-- -- Print a list of all enabled addons
|
||||||
-- for name, status in AceAddon:IterateAddonStatus() do
|
-- for name, status in AceAddon:IterateAddonStatus() do
|
||||||
-- if status then
|
-- 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:IterateEmbedsOnAddon(addon) return pairs(self.embeds[addon]) end
|
||||||
function AceAddon:IterateModulesOfAddon(addon) return pairs(addon.modules) 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
|
-- Event Handling
|
||||||
local function onEvent(this, event, arg1)
|
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
|
-- 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
|
while(#AceAddon.initializequeue > 0) do
|
||||||
local addon = tremove(AceAddon.initializequeue, 1)
|
local addon = tremove(AceAddon.initializequeue, 1)
|
||||||
@@ -622,7 +619,7 @@ local function onEvent(this, event, arg1)
|
|||||||
AceAddon:InitializeAddon(addon)
|
AceAddon:InitializeAddon(addon)
|
||||||
tinsert(AceAddon.enablequeue, addon)
|
tinsert(AceAddon.enablequeue, addon)
|
||||||
end
|
end
|
||||||
|
|
||||||
if IsLoggedIn() then
|
if IsLoggedIn() then
|
||||||
while(#AceAddon.enablequeue > 0) do
|
while(#AceAddon.enablequeue > 0) do
|
||||||
local addon = tremove(AceAddon.enablequeue, 1)
|
local addon = tremove(AceAddon.enablequeue, 1)
|
||||||
@@ -638,5 +635,15 @@ AceAddon.frame:SetScript("OnEvent", onEvent)
|
|||||||
|
|
||||||
-- upgrade embeded
|
-- upgrade embeded
|
||||||
for name, addon in pairs(AceAddon.addons) do
|
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
|
end
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
-- It'll automatically split the messages into multiple parts and rebuild them on the receiving end.\\
|
-- 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.
|
-- **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
|
-- 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.\\
|
-- 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
|
-- It is recommended to embed AceComm, otherwise you'll have to specify a custom `self` on all calls you
|
||||||
-- make into AceComm.
|
-- make into AceComm.
|
||||||
-- @class file
|
-- @class file
|
||||||
-- @name AceComm-3.0
|
-- @name AceComm-3.0
|
||||||
-- @release $Id: AceComm-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $
|
-- @release $Id$
|
||||||
|
|
||||||
--[[ AceComm-3.0
|
--[[ 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)
|
local AceComm,oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||||
|
|
||||||
if not AceComm then return end
|
if not AceComm then return end
|
||||||
|
|
||||||
local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0")
|
|
||||||
local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib")
|
|
||||||
|
|
||||||
-- Lua APIs
|
-- Lua APIs
|
||||||
local type, next, pairs, tostring = type, next, pairs, tostring
|
local type, next, pairs, tostring = type, next, pairs, tostring
|
||||||
local strsub, strfind = string.sub, string.find
|
local strsub, strfind = string.sub, string.find
|
||||||
|
local match = string.match
|
||||||
local tinsert, tconcat = table.insert, table.concat
|
local tinsert, tconcat = table.insert, table.concat
|
||||||
local error, assert = error, assert
|
local error, assert = error, assert
|
||||||
|
|
||||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
-- WoW APIs
|
||||||
-- List them here for Mikk's FindGlobals script
|
local Ambiguate = Ambiguate
|
||||||
-- GLOBALS: LibStub, DEFAULT_CHAT_FRAME, geterrorhandler
|
|
||||||
|
|
||||||
AceComm.embeds = AceComm.embeds or {}
|
AceComm.embeds = AceComm.embeds or {}
|
||||||
|
|
||||||
@@ -42,21 +41,32 @@ AceComm.embeds = AceComm.embeds or {}
|
|||||||
local MSG_MULTI_FIRST = "\001"
|
local MSG_MULTI_FIRST = "\001"
|
||||||
local MSG_MULTI_NEXT = "\002"
|
local MSG_MULTI_NEXT = "\002"
|
||||||
local MSG_MULTI_LAST = "\003"
|
local MSG_MULTI_LAST = "\003"
|
||||||
|
local MSG_ESCAPE = "\004"
|
||||||
|
|
||||||
AceComm.multipart_origprefixes = AceComm.multipart_origprefixes or {} -- e.g. "Prefix\001"="Prefix", "Prefix\002"="Prefix"
|
-- remove old structures (pre WoW 4.0)
|
||||||
AceComm.multipart_reassemblers = AceComm.multipart_reassemblers or {} -- e.g. "Prefix\001"="OnReceiveMultipartFirst"
|
AceComm.multipart_origprefixes = nil
|
||||||
|
AceComm.multipart_reassemblers = nil
|
||||||
|
|
||||||
-- the multipart message spool: indexed by a combination of sender+distribution+
|
-- 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
|
--- 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"
|
-- @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)
|
function AceComm:RegisterComm(prefix, method)
|
||||||
if method == nil then
|
if method == nil then
|
||||||
method = "OnCommReceived"
|
method = "OnCommReceived"
|
||||||
end
|
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
|
return AceComm._RegisterComm(self, prefix, method) -- created by CallbackHandler
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -75,57 +85,55 @@ function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callb
|
|||||||
if not( type(prefix)=="string" and
|
if not( type(prefix)=="string" and
|
||||||
type(text)=="string" and
|
type(text)=="string" and
|
||||||
type(distribution)=="string" and
|
type(distribution)=="string" and
|
||||||
(target==nil or type(target)=="string") and
|
(target==nil or type(target)=="string" or type(target)=="number") and
|
||||||
(prio=="BULK" or prio=="NORMAL" or prio=="ALERT")
|
(prio=="BULK" or prio=="NORMAL" or prio=="ALERT")
|
||||||
) then
|
) then
|
||||||
error('Usage: SendCommMessage(addon, "prefix", "text", "distribution"[, "target"[, "prio"[, callbackFn, callbackarg]]])', 2)
|
error('Usage: SendCommMessage(addon, "prefix", "text", "distribution"[, "target"[, "prio"[, callbackFn, callbackarg]]])', 2)
|
||||||
end
|
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 textlen = #text
|
||||||
local maxtextlen = 254 - #prefix -- 254 is the max length of prefix + text that can be sent in one message
|
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..distribution..(target or "")
|
local queueName = prefix
|
||||||
|
|
||||||
local ctlCallback = nil
|
local ctlCallback = nil
|
||||||
if callbackFn then
|
if callbackFn then
|
||||||
ctlCallback = function(sent)
|
ctlCallback = function(sent, sendResult)
|
||||||
return callbackFn(callbackArg, sent, textlen)
|
return callbackFn(callbackArg, sent, textlen, sendResult)
|
||||||
end
|
end
|
||||||
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
|
-- fits all in one message
|
||||||
CTL:SendAddonMessage(prio, prefix, text, distribution, target, queueName, ctlCallback, textlen)
|
CTL:SendAddonMessage(prio, prefix, text, distribution, target, queueName, ctlCallback, textlen)
|
||||||
else
|
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
|
-- first part
|
||||||
local chunk = strsub(text, 1, maxtextlen)
|
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
|
-- continuation
|
||||||
local pos = 1+maxtextlen
|
local pos = 1+maxtextlen
|
||||||
local prefix2 = prefix..MSG_MULTI_NEXT
|
|
||||||
|
|
||||||
while pos+maxtextlen <= textlen do
|
while pos+maxtextlen <= textlen do
|
||||||
chunk = strsub(text, pos, pos+maxtextlen-1)
|
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
|
pos = pos + maxtextlen
|
||||||
end
|
end
|
||||||
|
|
||||||
-- final part
|
-- final part
|
||||||
chunk = strsub(text, pos)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -138,17 +146,17 @@ do
|
|||||||
local compost = setmetatable({}, {__mode = "k"})
|
local compost = setmetatable({}, {__mode = "k"})
|
||||||
local function new()
|
local function new()
|
||||||
local t = next(compost)
|
local t = next(compost)
|
||||||
if t then
|
if t then
|
||||||
compost[t]=nil
|
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
|
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
|
t[i]=nil
|
||||||
end
|
end
|
||||||
return t
|
return t
|
||||||
end
|
end
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
local function lostdatawarning(prefix,sender,where)
|
local function lostdatawarning(prefix,sender,where)
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: lost network data regarding '"..tostring(prefix).."' from '"..tostring(sender).."' (in "..where..")")
|
DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: lost network data regarding '"..tostring(prefix).."' from '"..tostring(sender).."' (in "..where..")")
|
||||||
end
|
end
|
||||||
@@ -156,14 +164,14 @@ do
|
|||||||
function AceComm:OnReceiveMultipartFirst(prefix, message, distribution, sender)
|
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 key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
|
||||||
local spool = AceComm.multipart_spool
|
local spool = AceComm.multipart_spool
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
if spool[key] then
|
if spool[key] then
|
||||||
lostdatawarning(prefix,sender,"First")
|
lostdatawarning(prefix,sender,"First")
|
||||||
-- continue and overwrite
|
-- continue and overwrite
|
||||||
end
|
end
|
||||||
--]]
|
--]]
|
||||||
|
|
||||||
spool[key] = message -- plain string for now
|
spool[key] = message -- plain string for now
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -171,7 +179,7 @@ do
|
|||||||
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
|
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
|
||||||
local spool = AceComm.multipart_spool
|
local spool = AceComm.multipart_spool
|
||||||
local olddata = spool[key]
|
local olddata = spool[key]
|
||||||
|
|
||||||
if not olddata then
|
if not olddata then
|
||||||
--lostdatawarning(prefix,sender,"Next")
|
--lostdatawarning(prefix,sender,"Next")
|
||||||
return
|
return
|
||||||
@@ -192,14 +200,14 @@ do
|
|||||||
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
|
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
|
||||||
local spool = AceComm.multipart_spool
|
local spool = AceComm.multipart_spool
|
||||||
local olddata = spool[key]
|
local olddata = spool[key]
|
||||||
|
|
||||||
if not olddata then
|
if not olddata then
|
||||||
--lostdatawarning(prefix,sender,"End")
|
--lostdatawarning(prefix,sender,"End")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
spool[key] = nil
|
spool[key] = nil
|
||||||
|
|
||||||
if type(olddata) == "table" then
|
if type(olddata) == "table" then
|
||||||
-- if we've received a "next", the spooled data will be a table for rapid & garbage-free tconcat
|
-- if we've received a "next", the spooled data will be a table for rapid & garbage-free tconcat
|
||||||
tinsert(olddata, message)
|
tinsert(olddata, message)
|
||||||
@@ -222,47 +230,31 @@ end
|
|||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
if not AceComm.callbacks then
|
if not AceComm.callbacks then
|
||||||
-- ensure that 'prefix to watch' table is consistent with registered
|
|
||||||
-- callbacks
|
|
||||||
AceComm.__prefixes = {}
|
|
||||||
|
|
||||||
AceComm.callbacks = CallbackHandler:New(AceComm,
|
AceComm.callbacks = CallbackHandler:New(AceComm,
|
||||||
"_RegisterComm",
|
"_RegisterComm",
|
||||||
"UnregisterComm",
|
"UnregisterComm",
|
||||||
"UnregisterAllComm")
|
"UnregisterAllComm")
|
||||||
end
|
end
|
||||||
|
|
||||||
function AceComm.callbacks:OnUsed(target, prefix)
|
AceComm.callbacks.OnUsed = nil
|
||||||
AceComm.multipart_origprefixes[prefix..MSG_MULTI_FIRST] = prefix
|
AceComm.callbacks.OnUnused = nil
|
||||||
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
|
|
||||||
|
|
||||||
function AceComm.callbacks:OnUnused(target, prefix)
|
local function OnEvent(self, event, prefix, message, distribution, sender)
|
||||||
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, ...)
|
|
||||||
if event == "CHAT_MSG_ADDON" then
|
if event == "CHAT_MSG_ADDON" then
|
||||||
local prefix,message,distribution,sender = ...
|
sender = Ambiguate(sender, "none")
|
||||||
local reassemblername = AceComm.multipart_reassemblers[prefix]
|
local control, rest = match(message, "^([\001-\009])(.*)")
|
||||||
if reassemblername then
|
if control then
|
||||||
-- multipart: reassemble
|
if control==MSG_MULTI_FIRST then
|
||||||
local aceCommReassemblerFunc = AceComm[reassemblername]
|
AceComm:OnReceiveMultipartFirst(prefix, rest, distribution, sender)
|
||||||
local origprefix = AceComm.multipart_origprefixes[prefix]
|
elseif control==MSG_MULTI_NEXT then
|
||||||
aceCommReassemblerFunc(AceComm, origprefix, message, distribution, sender)
|
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
|
else
|
||||||
-- single part: fire it off immediately and let CallbackHandler decide if it's registered or not
|
-- single part: fire it off immediately and let CallbackHandler decide if it's registered or not
|
||||||
AceComm.callbacks:Fire(prefix, message, distribution, sender)
|
AceComm.callbacks:Fire(prefix, message, distribution, sender)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
--
|
--
|
||||||
-- Manages AddOn chat output to keep player from getting kicked off.
|
-- 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.
|
-- a Priority ("BULK", "NORMAL", "ALERT") as well as prefix for SendChatMessage.
|
||||||
--
|
--
|
||||||
-- Priorities get an equal share of available bandwidth when fully loaded.
|
-- 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! :-)
|
-- 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
|
local _G = _G
|
||||||
|
|
||||||
@@ -71,8 +73,8 @@ local math_min = math.min
|
|||||||
local math_max = math.max
|
local math_max = math.max
|
||||||
local next = next
|
local next = next
|
||||||
local strlen = string.len
|
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
|
||||||
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
|
-- Recycling bin for pipes
|
||||||
-- A pipe is a plain integer-indexed queue, which also happens to be a ring member
|
-- 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
|
ChatThrottleLib.PipeBin = nil -- pre-v19, drastically different
|
||||||
local PipeBin = setmetatable({}, {__mode="k"})
|
local PipeBin = setmetatable({}, {__mode="k"})
|
||||||
|
|
||||||
local function DelPipe(pipe)
|
local function DelPipe(pipe)
|
||||||
for i = #pipe, 1, -1 do
|
|
||||||
pipe[i] = nil
|
|
||||||
end
|
|
||||||
pipe.prev = nil
|
|
||||||
pipe.next = nil
|
|
||||||
|
|
||||||
PipeBin[pipe] = true
|
PipeBin[pipe] = true
|
||||||
end
|
end
|
||||||
|
|
||||||
local function NewPipe()
|
local function NewPipe()
|
||||||
local pipe = next(PipeBin)
|
local pipe = next(PipeBin)
|
||||||
if pipe then
|
if pipe then
|
||||||
|
wipe(pipe)
|
||||||
PipeBin[pipe] = nil
|
PipeBin[pipe] = nil
|
||||||
return pipe
|
return pipe
|
||||||
end
|
end
|
||||||
@@ -169,7 +184,7 @@ end
|
|||||||
-- Initialize queues, set up frame for OnUpdate, etc
|
-- Initialize queues, set up frame for OnUpdate, etc
|
||||||
|
|
||||||
|
|
||||||
function ChatThrottleLib:Init()
|
function ChatThrottleLib:Init()
|
||||||
|
|
||||||
-- Set up queues
|
-- Set up queues
|
||||||
if not self.Prio then
|
if not self.Prio then
|
||||||
@@ -179,6 +194,13 @@ function ChatThrottleLib:Init()
|
|||||||
self.Prio["BULK"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
|
self.Prio["BULK"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
|
||||||
end
|
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
|
-- v4: total send counters per priority
|
||||||
for _, Prio in pairs(self.Prio) do
|
for _, Prio in pairs(self.Prio) do
|
||||||
Prio.nTotalSent = Prio.nTotalSent or 0
|
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: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.Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||||
self.OnUpdateDelay = 0
|
self.OnUpdateDelay = 0
|
||||||
|
self.BlockedQueuesDelay = 0
|
||||||
self.LastAvailUpdate = GetTime()
|
self.LastAvailUpdate = GetTime()
|
||||||
self.HardThrottlingBeginTime = GetTime() -- v11: Throttle hard for a few seconds after startup
|
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.
|
-- Use secure hooks as of v16. Old regular hook support yanked out in v21.
|
||||||
self.securelyHooked = true
|
self.securelyHooked = true
|
||||||
--SendChatMessage
|
--SendChatMessage
|
||||||
hooksecurefunc("SendChatMessage", function(...)
|
if _G.C_ChatInfo and _G.C_ChatInfo.SendChatMessage then
|
||||||
return ChatThrottleLib.Hook_SendChatMessage(...)
|
hooksecurefunc(_G.C_ChatInfo, "SendChatMessage", function(...)
|
||||||
end)
|
return ChatThrottleLib.Hook_SendChatMessage(...)
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
hooksecurefunc("SendChatMessage", function(...)
|
||||||
|
return ChatThrottleLib.Hook_SendChatMessage(...)
|
||||||
|
end)
|
||||||
|
end
|
||||||
--SendAddonMessage
|
--SendAddonMessage
|
||||||
hooksecurefunc("SendAddonMessage", function(...)
|
hooksecurefunc(_G.C_ChatInfo, "SendAddonMessage", function(...)
|
||||||
return ChatThrottleLib.Hook_SendAddonMessage(...)
|
return ChatThrottleLib.Hook_SendAddonMessage(...)
|
||||||
end)
|
end)
|
||||||
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
|
self.nBypass = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -245,6 +297,12 @@ function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destinati
|
|||||||
self.avail = self.avail - size
|
self.avail = self.avail - size
|
||||||
self.nBypass = self.nBypass + size -- just a statistic
|
self.nBypass = self.nBypass + size -- just a statistic
|
||||||
end
|
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
|
-- 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)
|
function ChatThrottleLib:Despool(Prio)
|
||||||
local ring = Prio.Ring
|
local ring = Prio.Ring
|
||||||
while ring.pos and Prio.avail > ring.pos[1].nSize do
|
while ring.pos and Prio.avail > ring.pos[1].nSize do
|
||||||
local msg = table_remove(Prio.Ring.pos, 1)
|
local pipe = ring.pos
|
||||||
if not Prio.Ring.pos[1] then
|
local msg = pipe[1]
|
||||||
local pipe = Prio.Ring.pos
|
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.Ring:Remove(pipe)
|
||||||
Prio.ByName[pipe.name] = nil
|
Prio.Blocked:Add(pipe)
|
||||||
DelPipe(pipe)
|
|
||||||
else
|
else
|
||||||
Prio.Ring.pos = Prio.Ring.pos.next
|
-- Dequeue message after submission.
|
||||||
end
|
table_remove(pipe, 1)
|
||||||
Prio.avail = Prio.avail - msg.nSize
|
DelMsg(msg)
|
||||||
bMyTraffic = true
|
|
||||||
msg.f(unpack(msg, 1, msg.n))
|
if not pipe[1] then -- did we remove last msg in this pipe?
|
||||||
bMyTraffic = false
|
Prio.Ring:Remove(pipe)
|
||||||
Prio.nTotalSent = Prio.nTotalSent + msg.nSize
|
Prio.ByName[pipe.name] = nil
|
||||||
DelMsg(msg)
|
DelPipe(pipe)
|
||||||
if msg.callbackFn then
|
else
|
||||||
msg.callbackFn (msg.callbackArg)
|
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
|
end
|
||||||
end
|
end
|
||||||
@@ -321,6 +445,7 @@ function ChatThrottleLib.OnUpdate(this,delay)
|
|||||||
local self = ChatThrottleLib
|
local self = ChatThrottleLib
|
||||||
|
|
||||||
self.OnUpdateDelay = self.OnUpdateDelay + delay
|
self.OnUpdateDelay = self.OnUpdateDelay + delay
|
||||||
|
self.BlockedQueuesDelay = self.BlockedQueuesDelay + delay
|
||||||
if self.OnUpdateDelay < 0.08 then
|
if self.OnUpdateDelay < 0.08 then
|
||||||
return
|
return
|
||||||
end
|
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.
|
return -- argh. some bastard is spewing stuff past the lib. just bail early to save cpu.
|
||||||
end
|
end
|
||||||
|
|
||||||
-- See how many of our priorities have queued messages (we only have 3, don't worry about the loop)
|
-- Integrate blocked queues back into their rings periodically.
|
||||||
local n = 0
|
if self.BlockedQueuesDelay >= 0.35 then
|
||||||
for prioname,Prio in pairs(self.Prio) do
|
for _, Prio in pairs(self.Prio) do
|
||||||
if Prio.Ring.pos or Prio.avail < 0 then
|
Ring_Link(Prio.Ring, Prio.Blocked)
|
||||||
n = n + 1
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self.BlockedQueuesDelay = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Anything queued still?
|
-- See how many of our priorities have queued messages. This is split
|
||||||
if n<1 then
|
-- into two counters because priorities that consist only of blocked
|
||||||
-- Nope. Move spillover bandwidth to global availability gauge and clear self.bQueueing
|
-- queues must keep our OnUpdate alive, but shouldn't count toward
|
||||||
for prioname, Prio in pairs(self.Prio) do
|
-- 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
|
self.avail = self.avail + Prio.avail
|
||||||
Prio.avail = 0
|
Prio.avail = 0
|
||||||
end
|
end
|
||||||
self.bQueueing = false
|
end
|
||||||
self.Frame:Hide()
|
|
||||||
|
-- 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
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- There's stuff queued. Hand out available bandwidth to priorities as needed and despool their queues
|
-- 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
|
self.avail = 0
|
||||||
|
|
||||||
for prioname, Prio in pairs(self.Prio) do
|
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
|
Prio.avail = Prio.avail + avail
|
||||||
if Prio.Ring.pos and Prio.avail > Prio.Ring.pos[1].nSize then
|
self:Despool(Prio)
|
||||||
self:Despool(Prio)
|
|
||||||
-- Note: We might not get here if the user-supplied callback function errors out! Take care!
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -374,7 +519,6 @@ end
|
|||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
-- Spooling logic
|
-- Spooling logic
|
||||||
|
|
||||||
|
|
||||||
function ChatThrottleLib:Enqueue(prioname, pipename, msg)
|
function ChatThrottleLib:Enqueue(prioname, pipename, msg)
|
||||||
local Prio = self.Prio[prioname]
|
local Prio = self.Prio[prioname]
|
||||||
local pipe = Prio.ByName[pipename]
|
local pipe = Prio.ByName[pipename]
|
||||||
@@ -391,8 +535,6 @@ function ChatThrottleLib:Enqueue(prioname, pipename, msg)
|
|||||||
self.bQueueing = true
|
self.bQueueing = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, language, destination, queueName, callbackFn, callbackArg)
|
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
|
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)
|
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
|
-- Check if there's room in the global available bandwidth gauge to send directly
|
||||||
if not self.bQueueing and nSize < self:UpdateAvail() then
|
if not self.bQueueing and nSize < self:UpdateAvail() then
|
||||||
self.avail = self.avail - nSize
|
local sendResult = PerformSend(_G.C_ChatInfo.SendChatMessage or _G.SendChatMessage, text, chattype, language, destination)
|
||||||
bMyTraffic = true
|
|
||||||
_G.SendChatMessage(text, chattype, language, destination)
|
if not IsThrottledSendResult(sendResult) then
|
||||||
bMyTraffic = false
|
local didSend = (sendResult == SendAddonMessageResult.Success)
|
||||||
self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
|
|
||||||
if callbackFn then
|
if didSend then
|
||||||
callbackFn (callbackArg)
|
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
|
end
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Message needs to be queued
|
-- Message needs to be queued
|
||||||
local msg = NewMsg()
|
local msg = NewMsg()
|
||||||
msg.f = _G.SendChatMessage
|
msg.f = _G.C_ChatInfo.SendChatMessage or _G.SendChatMessage
|
||||||
msg[1] = text
|
msg[1] = text
|
||||||
msg[2] = chattype or "SAY"
|
msg[2] = chattype or "SAY"
|
||||||
msg[3] = language
|
msg[3] = language
|
||||||
@@ -434,42 +583,36 @@ function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, languag
|
|||||||
msg.callbackFn = callbackFn
|
msg.callbackFn = callbackFn
|
||||||
msg.callbackArg = callbackArg
|
msg.callbackArg = callbackArg
|
||||||
|
|
||||||
self:Enqueue(prio, queueName or (prefix..(chattype or "SAY")..(destination or "")), msg)
|
self:Enqueue(prio, queueName or prefix, msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg)
|
local function SendAddonMessageInternal(self, sendFunction, 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
|
local nSize = #text + self.MSG_OVERHEAD
|
||||||
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;
|
|
||||||
|
|
||||||
-- Check if there's room in the global available bandwidth gauge to send directly
|
-- Check if there's room in the global available bandwidth gauge to send directly
|
||||||
if not self.bQueueing and nSize < self:UpdateAvail() then
|
if not self.bQueueing and nSize < self:UpdateAvail() then
|
||||||
self.avail = self.avail - nSize
|
local sendResult = PerformSend(sendFunction, prefix, text, chattype, target)
|
||||||
bMyTraffic = true
|
|
||||||
_G.SendAddonMessage(prefix, text, chattype, target)
|
if not IsThrottledSendResult(sendResult) then
|
||||||
bMyTraffic = false
|
local didSend = (sendResult == SendAddonMessageResult.Success)
|
||||||
self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
|
|
||||||
if callbackFn then
|
if didSend then
|
||||||
callbackFn (callbackArg)
|
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
|
end
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Message needs to be queued
|
-- Message needs to be queued
|
||||||
local msg = NewMsg()
|
local msg = NewMsg()
|
||||||
msg.f = _G.SendAddonMessage
|
msg.f = sendFunction
|
||||||
msg[1] = prefix
|
msg[1] = prefix
|
||||||
msg[2] = text
|
msg[2] = text
|
||||||
msg[3] = chattype
|
msg[3] = chattype
|
||||||
@@ -479,10 +622,65 @@ function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target,
|
|||||||
msg.callbackFn = callbackFn
|
msg.callbackFn = callbackFn
|
||||||
msg.callbackArg = callbackArg
|
msg.callbackArg = callbackArg
|
||||||
|
|
||||||
self:Enqueue(prio, queueName or (prefix..chattype..(target or "")), msg)
|
self:Enqueue(prio, queueName or prefix, msg)
|
||||||
end
|
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
|
||||||
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
-- You can register slash commands to your custom functions and use the `GetArgs` function to parse them
|
-- You can register slash commands to your custom functions and use the `GetArgs` function to parse them
|
||||||
-- to your addons individual needs.
|
-- 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
|
-- 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.\\
|
-- 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
|
-- It is recommended to embed AceConsole, otherwise you'll have to specify a custom `self` on all calls you
|
||||||
-- make into AceConsole.
|
-- make into AceConsole.
|
||||||
-- @class file
|
-- @class file
|
||||||
-- @name AceConsole-3.0
|
-- @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 MAJOR,MINOR = "AceConsole-3.0", 7
|
||||||
|
|
||||||
local AceConsole, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
local AceConsole, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||||
@@ -29,10 +29,6 @@ local max = math.max
|
|||||||
-- WoW APIs
|
-- WoW APIs
|
||||||
local _G = _G
|
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 tmp={}
|
||||||
local function Print(self,frame,...)
|
local function Print(self,frame,...)
|
||||||
local n=0
|
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)
|
-- @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 )
|
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 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
|
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()
|
local name = "ACECONSOLE_"..command:upper()
|
||||||
|
|
||||||
if type( func ) == "string" then
|
if type( func ) == "string" then
|
||||||
SlashCmdList[name] = function(input, editBox)
|
SlashCmdList[name] = function(input, editBox)
|
||||||
self[func](self, input, editBox)
|
self[func](self, input, editBox)
|
||||||
@@ -132,11 +128,11 @@ local function nils(n, ...)
|
|||||||
return ...
|
return ...
|
||||||
end
|
end
|
||||||
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.
|
-- 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 numargs How many arguments to get (default 1)
|
||||||
-- @param startpos Where in the string to start scanning (default 1)
|
-- @param startpos Where in the string to start scanning (default 1)
|
||||||
-- @return Returns arg1, arg2, ..., nextposition\\
|
-- @return Returns arg1, arg2, ..., nextposition\\
|
||||||
@@ -144,7 +140,7 @@ end
|
|||||||
function AceConsole:GetArgs(str, numargs, startpos)
|
function AceConsole:GetArgs(str, numargs, startpos)
|
||||||
numargs = numargs or 1
|
numargs = numargs or 1
|
||||||
startpos = max(startpos or 1, 1)
|
startpos = max(startpos or 1, 1)
|
||||||
|
|
||||||
local pos=startpos
|
local pos=startpos
|
||||||
|
|
||||||
-- find start of new arg
|
-- find start of new arg
|
||||||
@@ -169,24 +165,24 @@ function AceConsole:GetArgs(str, numargs, startpos)
|
|||||||
else
|
else
|
||||||
delim_or_pipe="([| ])"
|
delim_or_pipe="([| ])"
|
||||||
end
|
end
|
||||||
|
|
||||||
startpos = pos
|
startpos = pos
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
-- find delimiter or hyperlink
|
-- find delimiter or hyperlink
|
||||||
local ch,_
|
local _
|
||||||
pos,_,ch = strfind(str, delim_or_pipe, pos)
|
pos,_,ch = strfind(str, delim_or_pipe, pos)
|
||||||
|
|
||||||
if not pos then break end
|
if not pos then break end
|
||||||
|
|
||||||
if ch=="|" then
|
if ch=="|" then
|
||||||
-- some kind of escape
|
-- some kind of escape
|
||||||
|
|
||||||
if strsub(str,pos,pos+1)=="|H" then
|
if strsub(str,pos,pos+1)=="|H" then
|
||||||
-- It's a |H....|hhyper link!|h
|
-- It's a |H....|hhyper link!|h
|
||||||
pos=strfind(str, "|h", pos+2) -- first |h
|
pos=strfind(str, "|h", pos+2) -- first |h
|
||||||
if not pos then break end
|
if not pos then break end
|
||||||
|
|
||||||
pos=strfind(str, "|h", pos+2) -- second |h
|
pos=strfind(str, "|h", pos+2) -- second |h
|
||||||
if not pos then break end
|
if not pos then break end
|
||||||
elseif strsub(str,pos, pos+1) == "|T" then
|
elseif strsub(str,pos, pos+1) == "|T" then
|
||||||
@@ -194,16 +190,16 @@ function AceConsole:GetArgs(str, numargs, startpos)
|
|||||||
pos=strfind(str, "|t", pos+2)
|
pos=strfind(str, "|t", pos+2)
|
||||||
if not pos then break end
|
if not pos then break end
|
||||||
end
|
end
|
||||||
|
|
||||||
pos=pos+2 -- skip past this escape (last |h if it was a hyperlink)
|
pos=pos+2 -- skip past this escape (last |h if it was a hyperlink)
|
||||||
|
|
||||||
else
|
else
|
||||||
-- found delimiter, done with this arg
|
-- found delimiter, done with this arg
|
||||||
return strsub(str, startpos, pos-1), AceConsole:GetArgs(str, numargs-1, pos+1)
|
return strsub(str, startpos, pos-1), AceConsole:GetArgs(str, numargs-1, pos+1)
|
||||||
end
|
end
|
||||||
|
|
||||||
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)
|
-- 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)
|
return strsub(str, startpos), nils(numargs-1, 1e9)
|
||||||
end
|
end
|
||||||
@@ -214,10 +210,10 @@ end
|
|||||||
local mixins = {
|
local mixins = {
|
||||||
"Print",
|
"Print",
|
||||||
"Printf",
|
"Printf",
|
||||||
"RegisterChatCommand",
|
"RegisterChatCommand",
|
||||||
"UnregisterChatCommand",
|
"UnregisterChatCommand",
|
||||||
"GetArgs",
|
"GetArgs",
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Embeds AceConsole into the target object making the functions from the mixins list available on target:..
|
-- Embeds AceConsole into the target object making the functions from the mixins list available on target:..
|
||||||
-- @param target target object to embed AceBucket in
|
-- @param target target object to embed AceBucket in
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
-- * **race** Race-specific data. All of the players characters of the same race share this database.
|
-- * **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.
|
-- * **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.
|
-- * **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.
|
-- * **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.
|
-- * **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
|
-- end
|
||||||
-- @class file
|
-- @class file
|
||||||
-- @name AceDB-3.0.lua
|
-- @name AceDB-3.0.lua
|
||||||
-- @release $Id: AceDB-3.0.lua 940 2010-06-19 08:01:47Z nevcairiel $
|
-- @release $Id$
|
||||||
local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 21
|
local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 33
|
||||||
local AceDB, oldminor = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR)
|
local AceDB = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR)
|
||||||
|
|
||||||
if not AceDB then return end -- No upgrade needed
|
if not AceDB then return end -- No upgrade needed
|
||||||
|
|
||||||
-- Lua APIs
|
-- Lua APIs
|
||||||
local type, pairs, next, error = type, pairs, next, error
|
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
|
-- WoW APIs
|
||||||
local _G = _G
|
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.db_registry = AceDB.db_registry or {}
|
||||||
AceDB.frame = AceDB.frame or CreateFrame("Frame")
|
AceDB.frame = AceDB.frame or CreateFrame("Frame")
|
||||||
|
|
||||||
@@ -97,11 +94,11 @@ local function copyDefaults(dest, src)
|
|||||||
-- This is a metatable used for table defaults
|
-- This is a metatable used for table defaults
|
||||||
local mt = {
|
local mt = {
|
||||||
-- This handles the lookup and creation of new subtables
|
-- This handles the lookup and creation of new subtables
|
||||||
__index = function(t,k)
|
__index = function(t,k2)
|
||||||
if k == nil then return nil end
|
if k2 == nil then return nil end
|
||||||
local tbl = {}
|
local tbl = {}
|
||||||
copyDefaults(tbl, v)
|
copyDefaults(tbl, v)
|
||||||
rawset(t, k, tbl)
|
rawset(t, k2, tbl)
|
||||||
return tbl
|
return tbl
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
@@ -114,7 +111,7 @@ local function copyDefaults(dest, src)
|
|||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- Values are not tables, so this is just a simple return
|
-- 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)
|
setmetatable(dest, mt)
|
||||||
end
|
end
|
||||||
elseif type(v) == "table" then
|
elseif type(v) == "table" then
|
||||||
@@ -260,6 +257,12 @@ local _, classKey = UnitClass("player")
|
|||||||
local _, raceKey = UnitRace("player")
|
local _, raceKey = UnitRace("player")
|
||||||
local factionKey = UnitFactionGroup("player")
|
local factionKey = UnitFactionGroup("player")
|
||||||
local factionrealmKey = factionKey .. " - " .. realmKey
|
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
|
-- Actual database initialization function
|
||||||
local function initdb(sv, defaults, defaultProfile, olddb, parent)
|
local function initdb(sv, defaults, defaultProfile, olddb, parent)
|
||||||
-- Generate the database keys for each section
|
-- Generate the database keys for each section
|
||||||
@@ -295,7 +298,9 @@ local function initdb(sv, defaults, defaultProfile, olddb, parent)
|
|||||||
["race"] = raceKey,
|
["race"] = raceKey,
|
||||||
["faction"] = factionKey,
|
["faction"] = factionKey,
|
||||||
["factionrealm"] = factionrealmKey,
|
["factionrealm"] = factionrealmKey,
|
||||||
|
["factionrealmregion"] = factionrealmregionKey,
|
||||||
["profile"] = profileKey,
|
["profile"] = profileKey,
|
||||||
|
["locale"] = localeKey,
|
||||||
["global"] = true,
|
["global"] = true,
|
||||||
["profiles"] = true,
|
["profiles"] = true,
|
||||||
}
|
}
|
||||||
@@ -352,10 +357,10 @@ local function logoutHandler(frame, event)
|
|||||||
for db in pairs(AceDB.db_registry) do
|
for db in pairs(AceDB.db_registry) do
|
||||||
db.callbacks:Fire("OnDatabaseShutdown", db)
|
db.callbacks:Fire("OnDatabaseShutdown", db)
|
||||||
db:RegisterDefaults(nil)
|
db:RegisterDefaults(nil)
|
||||||
|
|
||||||
-- cleanup sections that are empty without defaults
|
-- cleanup sections that are empty without defaults
|
||||||
local sv = rawget(db, "sv")
|
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
|
if rawget(sv, section) then
|
||||||
-- global is special, all other sections have sub-entrys
|
-- global is special, all other sections have sub-entrys
|
||||||
-- also don't delete empty profiles on main dbs, only on namespaces
|
-- also don't delete empty profiles on main dbs, only on namespaces
|
||||||
@@ -372,6 +377,26 @@ local function logoutHandler(frame, event)
|
|||||||
end
|
end
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -388,7 +413,7 @@ AceDB.frame:SetScript("OnEvent", logoutHandler)
|
|||||||
-- @param defaults A table of defaults for this database
|
-- @param defaults A table of defaults for this database
|
||||||
function DBObjectLib:RegisterDefaults(defaults)
|
function DBObjectLib:RegisterDefaults(defaults)
|
||||||
if defaults and type(defaults) ~= "table" then
|
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
|
end
|
||||||
|
|
||||||
validateDefaults(defaults, self.keys)
|
validateDefaults(defaults, self.keys)
|
||||||
@@ -420,7 +445,7 @@ end
|
|||||||
-- @param name The name of the profile to set as the current profile
|
-- @param name The name of the profile to set as the current profile
|
||||||
function DBObjectLib:SetProfile(name)
|
function DBObjectLib:SetProfile(name)
|
||||||
if type(name) ~= "string" then
|
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
|
end
|
||||||
|
|
||||||
-- changing to the same profile, dont do anything
|
-- changing to the same profile, dont do anything
|
||||||
@@ -462,7 +487,7 @@ end
|
|||||||
-- @param tbl A table to store the profile names in (optional)
|
-- @param tbl A table to store the profile names in (optional)
|
||||||
function DBObjectLib:GetProfiles(tbl)
|
function DBObjectLib:GetProfiles(tbl)
|
||||||
if tbl and type(tbl) ~= "table" then
|
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
|
end
|
||||||
|
|
||||||
-- Clear the container table
|
-- Clear the container table
|
||||||
@@ -500,15 +525,15 @@ end
|
|||||||
-- @param silent If true, do not raise an error when the profile does not exist
|
-- @param silent If true, do not raise an error when the profile does not exist
|
||||||
function DBObjectLib:DeleteProfile(name, silent)
|
function DBObjectLib:DeleteProfile(name, silent)
|
||||||
if type(name) ~= "string" then
|
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
|
end
|
||||||
|
|
||||||
if self.keys.profile == name then
|
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
|
end
|
||||||
|
|
||||||
if not rawget(self.profiles, name) and not silent then
|
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
|
end
|
||||||
|
|
||||||
self.profiles[name] = nil
|
self.profiles[name] = nil
|
||||||
@@ -520,6 +545,26 @@ function DBObjectLib:DeleteProfile(name, silent)
|
|||||||
end
|
end
|
||||||
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
|
-- Callback: OnProfileDeleted, database, profileKey
|
||||||
self.callbacks:Fire("OnProfileDeleted", self, name)
|
self.callbacks:Fire("OnProfileDeleted", self, name)
|
||||||
end
|
end
|
||||||
@@ -530,15 +575,15 @@ end
|
|||||||
-- @param silent If true, do not raise an error when the profile does not exist
|
-- @param silent If true, do not raise an error when the profile does not exist
|
||||||
function DBObjectLib:CopyProfile(name, silent)
|
function DBObjectLib:CopyProfile(name, silent)
|
||||||
if type(name) ~= "string" then
|
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
|
end
|
||||||
|
|
||||||
if name == self.keys.profile then
|
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
|
end
|
||||||
|
|
||||||
if not rawget(self.profiles, name) and not silent then
|
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
|
end
|
||||||
|
|
||||||
-- Reset the profile before copying
|
-- Reset the profile before copying
|
||||||
@@ -556,6 +601,20 @@ function DBObjectLib:CopyProfile(name, silent)
|
|||||||
end
|
end
|
||||||
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
|
-- Callback: OnProfileCopied, database, sourceProfileKey
|
||||||
self.callbacks:Fire("OnProfileCopied", self, name)
|
self.callbacks:Fire("OnProfileCopied", self, name)
|
||||||
end
|
end
|
||||||
@@ -582,6 +641,18 @@ function DBObjectLib:ResetProfile(noChildren, noCallbacks)
|
|||||||
end
|
end
|
||||||
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
|
-- Callback: OnProfileReset, database
|
||||||
if not noCallbacks then
|
if not noCallbacks then
|
||||||
self.callbacks:Fire("OnProfileReset", self)
|
self.callbacks:Fire("OnProfileReset", self)
|
||||||
@@ -592,8 +663,8 @@ end
|
|||||||
-- profile.
|
-- profile.
|
||||||
-- @param defaultProfile The profile name to use as the default
|
-- @param defaultProfile The profile name to use as the default
|
||||||
function DBObjectLib:ResetDB(defaultProfile)
|
function DBObjectLib:ResetDB(defaultProfile)
|
||||||
if defaultProfile and type(defaultProfile) ~= "string" then
|
if defaultProfile and type(defaultProfile) ~= "string" and defaultProfile ~= true then
|
||||||
error("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or nil expected.", 2)
|
error(("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or true expected, got %q."):format(type(defaultProfile)), 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
local sv = self.sv
|
local sv = self.sv
|
||||||
@@ -601,8 +672,6 @@ function DBObjectLib:ResetDB(defaultProfile)
|
|||||||
sv[k] = nil
|
sv[k] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local parent = self.parent
|
|
||||||
|
|
||||||
initdb(sv, self.defaults, defaultProfile, self)
|
initdb(sv, self.defaults, defaultProfile, self)
|
||||||
|
|
||||||
-- fix the child namespaces
|
-- fix the child namespaces
|
||||||
@@ -629,13 +698,13 @@ end
|
|||||||
-- @param defaults A table of values to use as defaults
|
-- @param defaults A table of values to use as defaults
|
||||||
function DBObjectLib:RegisterNamespace(name, defaults)
|
function DBObjectLib:RegisterNamespace(name, defaults)
|
||||||
if type(name) ~= "string" then
|
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
|
end
|
||||||
if defaults and type(defaults) ~= "table" then
|
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
|
end
|
||||||
if self.children and self.children[name] then
|
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
|
end
|
||||||
|
|
||||||
local sv = self.sv
|
local sv = self.sv
|
||||||
@@ -659,10 +728,10 @@ end
|
|||||||
-- @return the namespace object if found
|
-- @return the namespace object if found
|
||||||
function DBObjectLib:GetNamespace(name, silent)
|
function DBObjectLib:GetNamespace(name, silent)
|
||||||
if type(name) ~= "string" then
|
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
|
end
|
||||||
if not silent and not (self.children and self.children[name]) then
|
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
|
end
|
||||||
if not self.children then self.children = {} end
|
if not self.children then self.children = {} end
|
||||||
return self.children[name]
|
return self.children[name]
|
||||||
@@ -701,15 +770,15 @@ function AceDB:New(tbl, defaults, defaultProfile)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if type(tbl) ~= "table" then
|
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
|
end
|
||||||
|
|
||||||
if defaults and type(defaults) ~= "table" then
|
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
|
end
|
||||||
|
|
||||||
if defaultProfile and type(defaultProfile) ~= "string" and defaultProfile ~= true then
|
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
|
end
|
||||||
|
|
||||||
return initdb(tbl, defaults, defaultProfile)
|
return initdb(tbl, defaults, defaultProfile)
|
||||||
|
|||||||
@@ -2,15 +2,17 @@
|
|||||||
-- All dispatching is done using **CallbackHandler-1.0**. AceEvent is a simple wrapper around
|
-- 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.
|
-- 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
|
-- 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.\\
|
-- 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
|
-- It is recommended to embed AceEvent, otherwise you'll have to specify a custom `self` on all calls you
|
||||||
-- make into AceEvent.
|
-- make into AceEvent.
|
||||||
-- @class file
|
-- @class file
|
||||||
-- @name AceEvent-3.0
|
-- @name AceEvent-3.0
|
||||||
-- @release $Id: AceEvent-3.0.lua 877 2009-11-02 15:56:50Z nevcairiel $
|
-- @release $Id$
|
||||||
local MAJOR, MINOR = "AceEvent-3.0", 3
|
local CallbackHandler = LibStub("CallbackHandler-1.0")
|
||||||
|
|
||||||
|
local MAJOR, MINOR = "AceEvent-3.0", 4
|
||||||
local AceEvent = LibStub:NewLibrary(MAJOR, MINOR)
|
local AceEvent = LibStub:NewLibrary(MAJOR, MINOR)
|
||||||
|
|
||||||
if not AceEvent then return end
|
if not AceEvent then return end
|
||||||
@@ -18,29 +20,27 @@ if not AceEvent then return end
|
|||||||
-- Lua APIs
|
-- Lua APIs
|
||||||
local pairs = pairs
|
local pairs = pairs
|
||||||
|
|
||||||
local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0")
|
|
||||||
|
|
||||||
AceEvent.frame = AceEvent.frame or CreateFrame("Frame", "AceEvent30Frame") -- our event frame
|
AceEvent.frame = AceEvent.frame or CreateFrame("Frame", "AceEvent30Frame") -- our event frame
|
||||||
AceEvent.embeds = AceEvent.embeds or {} -- what objects embed this lib
|
AceEvent.embeds = AceEvent.embeds or {} -- what objects embed this lib
|
||||||
|
|
||||||
-- APIs and registry for blizzard events, using CallbackHandler lib
|
-- APIs and registry for blizzard events, using CallbackHandler lib
|
||||||
if not AceEvent.events then
|
if not AceEvent.events then
|
||||||
AceEvent.events = CallbackHandler:New(AceEvent,
|
AceEvent.events = CallbackHandler:New(AceEvent,
|
||||||
"RegisterEvent", "UnregisterEvent", "UnregisterAllEvents")
|
"RegisterEvent", "UnregisterEvent", "UnregisterAllEvents")
|
||||||
end
|
end
|
||||||
|
|
||||||
function AceEvent.events:OnUsed(target, eventname)
|
function AceEvent.events:OnUsed(target, eventname)
|
||||||
AceEvent.frame:RegisterEvent(eventname)
|
AceEvent.frame:RegisterEvent(eventname)
|
||||||
end
|
end
|
||||||
|
|
||||||
function AceEvent.events:OnUnused(target, eventname)
|
function AceEvent.events:OnUnused(target, eventname)
|
||||||
AceEvent.frame:UnregisterEvent(eventname)
|
AceEvent.frame:UnregisterEvent(eventname)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- APIs and registry for IPC messages, using CallbackHandler lib
|
-- APIs and registry for IPC messages, using CallbackHandler lib
|
||||||
if not AceEvent.messages then
|
if not AceEvent.messages then
|
||||||
AceEvent.messages = CallbackHandler:New(AceEvent,
|
AceEvent.messages = CallbackHandler:New(AceEvent,
|
||||||
"RegisterMessage", "UnregisterMessage", "UnregisterAllMessages"
|
"RegisterMessage", "UnregisterMessage", "UnregisterAllMessages"
|
||||||
)
|
)
|
||||||
AceEvent.SendMessage = AceEvent.messages.Fire
|
AceEvent.SendMessage = AceEvent.messages.Fire
|
||||||
@@ -55,7 +55,7 @@ local mixins = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
--- Register for a Blizzard Event.
|
--- 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.
|
-- Any arguments to the event will be passed on after that.
|
||||||
-- @name AceEvent:RegisterEvent
|
-- @name AceEvent:RegisterEvent
|
||||||
-- @class function
|
-- @class function
|
||||||
@@ -71,7 +71,7 @@ local mixins = {
|
|||||||
-- @param event The event to unregister
|
-- @param event The event to unregister
|
||||||
|
|
||||||
--- Register for a custom AceEvent-internal message.
|
--- 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.
|
-- Any arguments to the event will be passed on after that.
|
||||||
-- @name AceEvent:RegisterMessage
|
-- @name AceEvent:RegisterMessage
|
||||||
-- @class function
|
-- @class function
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
--- **AceSerializer-3.0** can serialize any variable (except functions or userdata) into a string format,
|
--- **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
|
-- 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.
|
-- 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
|
-- 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.\\
|
-- 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
|
-- It is recommended to embed AceSerializer, otherwise you'll have to specify a custom `self` on all calls you
|
||||||
-- make into AceSerializer.
|
-- make into AceSerializer.
|
||||||
-- @class file
|
-- @class file
|
||||||
-- @name AceSerializer-3.0
|
-- @name AceSerializer-3.0
|
||||||
-- @release $Id: AceSerializer-3.0.lua 910 2010-02-11 21:54:24Z mikk $
|
-- @release $Id$
|
||||||
local MAJOR,MINOR = "AceSerializer-3.0", 3
|
local MAJOR,MINOR = "AceSerializer-3.0", 5
|
||||||
local AceSerializer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
local AceSerializer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||||
|
|
||||||
if not AceSerializer then return end
|
if not AceSerializer then return end
|
||||||
@@ -24,9 +24,11 @@ local pairs, select, frexp = pairs, select, math.frexp
|
|||||||
local tconcat = table.concat
|
local tconcat = table.concat
|
||||||
|
|
||||||
-- quick copies of string representations of wonky numbers
|
-- quick copies of string representations of wonky numbers
|
||||||
local serNaN = tostring(0/0)
|
local inf = math.huge
|
||||||
local serInf = tostring(1/0)
|
|
||||||
local serNegInf = tostring(-1/0)
|
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
|
-- Serialization functions
|
||||||
@@ -38,7 +40,7 @@ local function SerializeStringHelper(ch) -- Used by SerializeValue for strings
|
|||||||
return "\126\122"
|
return "\126\122"
|
||||||
elseif n<=32 then -- nonprint + space
|
elseif n<=32 then -- nonprint + space
|
||||||
return "\126"..strchar(n+64)
|
return "\126"..strchar(n+64)
|
||||||
elseif n==94 then -- value separator
|
elseif n==94 then -- value separator
|
||||||
return "\126\125"
|
return "\126\125"
|
||||||
elseif n==126 then -- our own escape character
|
elseif n==126 then -- our own escape character
|
||||||
return "\126\124"
|
return "\126\124"
|
||||||
@@ -52,19 +54,23 @@ end
|
|||||||
local function SerializeValue(v, res, nres)
|
local function SerializeValue(v, res, nres)
|
||||||
-- We use "^" as a value separator, followed by one byte for type indicator
|
-- We use "^" as a value separator, followed by one byte for type indicator
|
||||||
local t=type(v)
|
local t=type(v)
|
||||||
|
|
||||||
if t=="string" then -- ^S = string (escaped to remove nonprints, "^"s, etc)
|
if t=="string" then -- ^S = string (escaped to remove nonprints, "^"s, etc)
|
||||||
res[nres+1] = "^S"
|
res[nres+1] = "^S"
|
||||||
res[nres+2] = gsub(v,"[%c \94\126\127]", SerializeStringHelper)
|
res[nres+2] = gsub(v,"[%c \94\126\127]", SerializeStringHelper)
|
||||||
nres=nres+2
|
nres=nres+2
|
||||||
|
|
||||||
elseif t=="number" then -- ^N = number (just tostring()ed) or ^F (float components)
|
elseif t=="number" then -- ^N = number (just tostring()ed) or ^F (float components)
|
||||||
local str = tostring(v)
|
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
|
-- translates just fine, transmit as-is
|
||||||
res[nres+1] = "^N"
|
res[nres+1] = "^N"
|
||||||
res[nres+2] = str
|
res[nres+2] = str
|
||||||
nres=nres+2
|
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
|
else
|
||||||
local m,e = frexp(v)
|
local m,e = frexp(v)
|
||||||
res[nres+1] = "^F"
|
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
|
res[nres+4] = tostring(e-53) -- adjust exponent to counteract mantissa manipulation
|
||||||
nres=nres+4
|
nres=nres+4
|
||||||
end
|
end
|
||||||
|
|
||||||
elseif t=="table" then -- ^T...^t = table (list of key,value pairs)
|
elseif t=="table" then -- ^T...^t = table (list of key,value pairs)
|
||||||
nres=nres+1
|
nres=nres+1
|
||||||
res[nres] = "^T"
|
res[nres] = "^T"
|
||||||
for k,v in pairs(v) do
|
for key,value in pairs(v) do
|
||||||
nres = SerializeValue(k, res, nres)
|
nres = SerializeValue(key, res, nres)
|
||||||
nres = SerializeValue(v, res, nres)
|
nres = SerializeValue(value, res, nres)
|
||||||
end
|
end
|
||||||
nres=nres+1
|
nres=nres+1
|
||||||
res[nres] = "^t"
|
res[nres] = "^t"
|
||||||
|
|
||||||
elseif t=="boolean" then -- ^B = true, ^b = false
|
elseif t=="boolean" then -- ^B = true, ^b = false
|
||||||
nres=nres+1
|
nres=nres+1
|
||||||
if v then
|
if v then
|
||||||
@@ -91,15 +97,15 @@ local function SerializeValue(v, res, nres)
|
|||||||
else
|
else
|
||||||
res[nres] = "^b" -- false
|
res[nres] = "^b" -- false
|
||||||
end
|
end
|
||||||
|
|
||||||
elseif t=="nil" then -- ^Z = nil (zero, "N" was taken :P)
|
elseif t=="nil" then -- ^Z = nil (zero, "N" was taken :P)
|
||||||
nres=nres+1
|
nres=nres+1
|
||||||
res[nres] = "^Z"
|
res[nres] = "^Z"
|
||||||
|
|
||||||
else
|
else
|
||||||
error(MAJOR..": Cannot serialize a value of type '"..t.."'") -- can't produce error on right level, this is wildly recursive
|
error(MAJOR..": Cannot serialize a value of type '"..t.."'") -- can't produce error on right level, this is wildly recursive
|
||||||
end
|
end
|
||||||
|
|
||||||
return nres
|
return nres
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -115,14 +121,14 @@ local serializeTbl = { "^1" } -- "^1" = Hi, I'm data serialized by AceSerializer
|
|||||||
-- @return The data in its serialized form (string)
|
-- @return The data in its serialized form (string)
|
||||||
function AceSerializer:Serialize(...)
|
function AceSerializer:Serialize(...)
|
||||||
local nres = 1
|
local nres = 1
|
||||||
|
|
||||||
for i=1,select("#", ...) do
|
for i=1,select("#", ...) do
|
||||||
local v = select(i, ...)
|
local v = select(i, ...)
|
||||||
nres = SerializeValue(v, serializeTbl, nres)
|
nres = SerializeValue(v, serializeTbl, nres)
|
||||||
end
|
end
|
||||||
|
|
||||||
serializeTbl[nres+1] = "^^" -- "^^" = End of serialized data
|
serializeTbl[nres+1] = "^^" -- "^^" = End of serialized data
|
||||||
|
|
||||||
return tconcat(serializeTbl, "", 1, nres+1)
|
return tconcat(serializeTbl, "", 1, nres+1)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -143,12 +149,12 @@ local function DeserializeStringHelper(escape)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function DeserializeNumberHelper(number)
|
local function DeserializeNumberHelper(number)
|
||||||
if number == serNaN then
|
--[[ not in 4.3 if number == serNaN then
|
||||||
return 0/0
|
return 0/0
|
||||||
elseif number == serNegInf then
|
else]]if number == serNegInf or number == serNegInfMac then
|
||||||
return -1/0
|
return -inf
|
||||||
elseif number == serInf then
|
elseif number == serInf or number == serInfMac then
|
||||||
return 1/0
|
return inf
|
||||||
else
|
else
|
||||||
return tonumber(number)
|
return tonumber(number)
|
||||||
end
|
end
|
||||||
@@ -169,9 +175,9 @@ local function DeserializeValue(iter,single,ctl,data)
|
|||||||
ctl,data = iter()
|
ctl,data = iter()
|
||||||
end
|
end
|
||||||
|
|
||||||
if not ctl then
|
if not ctl then
|
||||||
error("Supplied data misses AceSerializer terminator ('^^')")
|
error("Supplied data misses AceSerializer terminator ('^^')")
|
||||||
end
|
end
|
||||||
|
|
||||||
if ctl=="^^" then
|
if ctl=="^^" then
|
||||||
-- ignore extraneous data
|
-- ignore extraneous data
|
||||||
@@ -179,7 +185,7 @@ local function DeserializeValue(iter,single,ctl,data)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local res
|
local res
|
||||||
|
|
||||||
if ctl=="^S" then
|
if ctl=="^S" then
|
||||||
res = gsub(data, "~.", DeserializeStringHelper)
|
res = gsub(data, "~.", DeserializeStringHelper)
|
||||||
elseif ctl=="^N" then
|
elseif ctl=="^N" then
|
||||||
@@ -212,7 +218,7 @@ local function DeserializeValue(iter,single,ctl,data)
|
|||||||
ctl,data = iter()
|
ctl,data = iter()
|
||||||
if ctl=="^t" then break end -- ignore ^t's data
|
if ctl=="^t" then break end -- ignore ^t's data
|
||||||
k = DeserializeValue(iter,true,ctl,data)
|
k = DeserializeValue(iter,true,ctl,data)
|
||||||
if k==nil then
|
if k==nil then
|
||||||
error("Invalid AceSerializer table format (no table end marker)")
|
error("Invalid AceSerializer table format (no table end marker)")
|
||||||
end
|
end
|
||||||
ctl,data = iter()
|
ctl,data = iter()
|
||||||
@@ -225,7 +231,7 @@ local function DeserializeValue(iter,single,ctl,data)
|
|||||||
else
|
else
|
||||||
error("Invalid AceSerializer control code '"..ctl.."'")
|
error("Invalid AceSerializer control code '"..ctl.."'")
|
||||||
end
|
end
|
||||||
|
|
||||||
if not single then
|
if not single then
|
||||||
return res,DeserializeValue(iter)
|
return res,DeserializeValue(iter)
|
||||||
else
|
else
|
||||||
@@ -278,4 +284,4 @@ end
|
|||||||
-- Update embeds
|
-- Update embeds
|
||||||
for target, v in pairs(AceSerializer.embeds) do
|
for target, v in pairs(AceSerializer.embeds) do
|
||||||
AceSerializer:Embed(target)
|
AceSerializer:Embed(target)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
--- **AceTimer-3.0** provides a central facility for registering timers.
|
--- **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
|
-- 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.\\
|
-- 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
|
-- AceTimer is currently limited to firing timers at a frequency of 0.01s as this is what the WoW timer API
|
||||||
-- in the future, but for now it seemed like a good compromise in efficiency and accuracy.
|
-- restricts us to.
|
||||||
--
|
--
|
||||||
-- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you
|
-- 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
|
-- **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
|
-- 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.
|
-- make into AceTimer.
|
||||||
-- @class file
|
-- @class file
|
||||||
-- @name AceTimer-3.0
|
-- @name AceTimer-3.0
|
||||||
-- @release $Id: AceTimer-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $
|
-- @release $Id$
|
||||||
|
|
||||||
--[[
|
local MAJOR, MINOR = "AceTimer-3.0", 17 -- Bump minor on changes
|
||||||
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 AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||||
|
|
||||||
if not AceTimer then return end -- No upgrade needed
|
if not AceTimer then return end -- No upgrade needed
|
||||||
|
AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list
|
||||||
AceTimer.hash = AceTimer.hash or {} -- Array of [0..BUCKET-1] = linked list of timers (using .next member)
|
local activeTimers = AceTimer.activeTimers -- Upvalue our private data
|
||||||
-- 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")
|
|
||||||
|
|
||||||
-- Lua APIs
|
-- Lua APIs
|
||||||
local assert, error, loadstring = assert, error, loadstring
|
local type, unpack, next, error, select = type, unpack, next, error, select
|
||||||
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
|
|
||||||
|
|
||||||
-- WoW APIs
|
-- 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
|
local function new(self, loop, func, delay, ...)
|
||||||
-- List them here for Mikk's FindGlobals script
|
if delay < 0.01 then
|
||||||
-- GLOBALS: DEFAULT_CHAT_FRAME, geterrorhandler
|
delay = 0.01 -- Restrict to the lowest time that the C_Timer API allows us
|
||||||
|
|
||||||
-- 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
|
|
||||||
end
|
end
|
||||||
})
|
|
||||||
Dispatchers[0] = function(func)
|
|
||||||
return xpcall(func, errorhandler)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function safecall(func, ...)
|
local timer = {
|
||||||
return Dispatchers[select('#', ...)](func, ...)
|
object = self,
|
||||||
end
|
func = func,
|
||||||
|
looping = loop,
|
||||||
|
argsCount = select("#", ...),
|
||||||
|
delay = delay,
|
||||||
|
ends = GetTime() + delay,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
local lastint = floor(GetTime() * HZ)
|
activeTimers[timer] = timer
|
||||||
|
|
||||||
-- --------------------------------------------------------------------
|
-- Create new timer closure to wrap the "timer" object
|
||||||
-- OnUpdate handler
|
timer.callback = function()
|
||||||
--
|
if not timer.cancelled then
|
||||||
-- traverse buckets, always chasing "now", and fire timers that have expired
|
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()
|
if timer.looping and not timer.cancelled then
|
||||||
local now = GetTime()
|
-- Compensate delay to get a perfect average delay, even if individual times don't match up perfectly
|
||||||
local nowint = floor(now * HZ)
|
-- due to fps differences
|
||||||
|
local time = GetTime()
|
||||||
-- Have we passed into a new hash bucket?
|
local ndelay = timer.delay - (time - timer.ends)
|
||||||
if nowint == lastint then return end
|
-- Ensure the delay doesn't go below the threshold
|
||||||
|
if ndelay < 0.01 then ndelay = 0.01 end
|
||||||
local soon = now + 1 -- +1 is safe as long as 1 < HZ < BUCKETS/2
|
C_TimerAfter(ndelay, timer.callback)
|
||||||
|
timer.ends = time + ndelay
|
||||||
-- Pass through each bucket at most once
|
else
|
||||||
-- Happens on e.g. instance loads, but COULD happen on high local load situations also
|
activeTimers[timer.handle or timer] = nil
|
||||||
for curint = (max(lastint, nowint - BUCKETS) + 1), nowint do -- loop until we catch up with "now", usually only 1 iteration
|
end
|
||||||
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 when<soon ... else
|
|
||||||
end -- while nexttimer do
|
|
||||||
end -- for curint=lastint,nowint
|
|
||||||
|
|
||||||
lastint = nowint
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ---------------------------------------------------------------------
|
|
||||||
-- Reg( callback, delay, arg, repeating )
|
|
||||||
--
|
|
||||||
-- callback( function or string ) - direct function ref or method name in our object for the callback
|
|
||||||
-- delay(int) - delay for the timer
|
|
||||||
-- arg(variant) - any argument to be passed to the callback function
|
|
||||||
-- repeating(boolean) - repeating timer, or oneshot
|
|
||||||
--
|
|
||||||
-- returns the handle of the timer for later processing (canceling etc)
|
|
||||||
local function Reg(self, callback, delay, arg, repeating)
|
|
||||||
if type(callback) ~= "string" and type(callback) ~= "function" then
|
|
||||||
local error_origin = repeating and "ScheduleRepeatingTimer" or "ScheduleTimer"
|
|
||||||
error(MAJOR..": " .. error_origin .. "(callback, delay, arg): 'callback' - function or method name expected.", 3)
|
|
||||||
end
|
|
||||||
if type(callback) == "string" then
|
|
||||||
if type(self)~="table" then
|
|
||||||
local error_origin = repeating and "ScheduleRepeatingTimer" or "ScheduleTimer"
|
|
||||||
error(MAJOR..": " .. error_origin .. "(\"methodName\", delay, arg): 'self' - must be a table.", 3)
|
|
||||||
end
|
|
||||||
if type(self[callback]) ~= "function" then
|
|
||||||
local error_origin = repeating and "ScheduleRepeatingTimer" or "ScheduleTimer"
|
|
||||||
error(MAJOR..": " .. error_origin .. "(\"methodName\", delay, arg): 'methodName' - method not found on target object.", 3)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if delay < (1 / (HZ - 1)) then
|
C_TimerAfter(delay, timer.callback)
|
||||||
delay = 1 / (HZ - 1)
|
return timer
|
||||||
end
|
|
||||||
|
|
||||||
-- Create and stuff timer in the correct hash bucket
|
|
||||||
local now = GetTime()
|
|
||||||
|
|
||||||
local timer = timerCache or {} -- Get new timer object (from cache if available)
|
|
||||||
timerCache = nil
|
|
||||||
|
|
||||||
timer.object = self
|
|
||||||
timer.callback = callback
|
|
||||||
timer.delay = (repeating and delay)
|
|
||||||
timer.arg = arg
|
|
||||||
timer.when = now + delay
|
|
||||||
|
|
||||||
local bucket = (floor((now+delay)*HZ) % BUCKETS) + 1
|
|
||||||
timer.next = hash[bucket]
|
|
||||||
hash[bucket] = timer
|
|
||||||
|
|
||||||
-- Insert timer in our self->handle->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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Schedule a new one-shot timer.
|
--- Schedule a new one-shot timer.
|
||||||
-- The timer will fire once in `delay` seconds, unless canceled before.
|
-- 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 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
|
-- @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)
|
-- self:ScheduleTimer("TimerFeedback", 5)
|
||||||
-- end
|
-- end
|
||||||
--
|
--
|
||||||
-- function MyAddon:TimerFeedback()
|
-- function MyAddOn:TimerFeedback()
|
||||||
-- print("5 seconds passed")
|
-- print("5 seconds passed")
|
||||||
-- end
|
-- end
|
||||||
function AceTimer:ScheduleTimer(callback, delay, arg)
|
function AceTimer:ScheduleTimer(func, delay, ...)
|
||||||
return Reg(self, callback, delay, arg)
|
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
|
end
|
||||||
|
|
||||||
--- Schedule a repeating timer.
|
--- Schedule a repeating timer.
|
||||||
-- The timer will fire every `delay` seconds, until canceled.
|
-- 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 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
|
-- @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.timerCount = 0
|
||||||
-- self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5)
|
-- self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5)
|
||||||
-- end
|
-- end
|
||||||
--
|
--
|
||||||
-- function MyAddon:TimerFeedback()
|
-- function MyAddOn:TimerFeedback()
|
||||||
-- self.timerCount = self.timerCount + 1
|
-- self.timerCount = self.timerCount + 1
|
||||||
-- print(("%d seconds passed"):format(5 * self.timerCount))
|
-- print(("%d seconds passed"):format(5 * self.timerCount))
|
||||||
-- -- run 30 seconds in total
|
-- -- run 30 seconds in total
|
||||||
@@ -298,129 +126,124 @@ end
|
|||||||
-- self:CancelTimer(self.testTimer)
|
-- self:CancelTimer(self.testTimer)
|
||||||
-- end
|
-- end
|
||||||
-- end
|
-- end
|
||||||
function AceTimer:ScheduleRepeatingTimer(callback, delay, arg)
|
function AceTimer:ScheduleRepeatingTimer(func, delay, ...)
|
||||||
return Reg(self, callback, delay, arg, true)
|
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
|
end
|
||||||
|
|
||||||
--- Cancels a timer with the given handle, registered by the same addon object as used for `:ScheduleTimer`
|
--- 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 `handle` is valid
|
-- 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.
|
-- 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 id The id 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)
|
function AceTimer:CancelTimer(id)
|
||||||
-- @return True if the timer was successfully cancelled.
|
local timer = activeTimers[id]
|
||||||
function AceTimer:CancelTimer(handle, silent)
|
|
||||||
if not handle then return end -- nil handle -> bail out without erroring
|
if not timer then
|
||||||
if type(handle) ~= "string" then
|
return false
|
||||||
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.
|
|
||||||
else
|
else
|
||||||
if not timer then
|
timer.cancelled = true
|
||||||
geterrorhandler()(MAJOR..": CancelTimer(handle[, silent]): '"..tostring(handle).."' - no such timer registered")
|
activeTimers[id] = nil
|
||||||
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
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Cancels all timers registered to the current addon object ('self')
|
--- Cancels all timers registered to the current addon object ('self')
|
||||||
function AceTimer:CancelAllTimers()
|
function AceTimer:CancelAllTimers()
|
||||||
if not(type(self) == "string" or type(self) == "table") then
|
for k,v in next, activeTimers do
|
||||||
error(MAJOR..": CancelAllTimers(): 'self' - must be a string or a table",2)
|
if v.object == self then
|
||||||
end
|
AceTimer.CancelTimer(self, k)
|
||||||
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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns the time left for a timer with the given handle, registered by the current addon object ('self').
|
--- Returns the time left for a timer with the given id, registered by the current addon object ('self').
|
||||||
-- This function will raise a warning when the handle is invalid, but not stop execution.
|
-- This function will return 0 when the id is invalid.
|
||||||
-- @param handle The handle of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
|
-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
|
||||||
-- @return The time left on the timer, or false if the handle is invalid.
|
-- @return The time left on the timer.
|
||||||
function AceTimer:TimeLeft(handle)
|
function AceTimer:TimeLeft(id)
|
||||||
if not handle then return end
|
local timer = activeTimers[id]
|
||||||
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]
|
|
||||||
if not timer then
|
if not timer then
|
||||||
geterrorhandler()(MAJOR..": TimeLeft(handle): '"..tostring(handle).."' - no such timer registered")
|
return 0
|
||||||
return false
|
else
|
||||||
|
return timer.ends - GetTime()
|
||||||
end
|
end
|
||||||
return timer.when - GetTime()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- ---------------------------------------------------------------------
|
-- ---------------------------------------------------------------------
|
||||||
-- PLAYER_REGEN_ENABLED: Run through our .selfs[] array step by step
|
-- Upgrading
|
||||||
-- 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
|
|
||||||
|
|
||||||
local lastCleaned = nil
|
-- Upgrade from old hash-bucket based timers to C_Timer.After timers.
|
||||||
|
if oldminor and oldminor < 10 then
|
||||||
local function OnEvent(this, event)
|
-- disable old timer logic
|
||||||
if event~="PLAYER_REGEN_ENABLED" then
|
AceTimer.frame:SetScript("OnUpdate", nil)
|
||||||
return
|
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
|
end
|
||||||
|
|
||||||
-- Get the next 'self' to process
|
-- Migrate transitional handles
|
||||||
local selfs = AceTimer.selfs
|
if oldminor < 13 and AceTimer.hashCompatTable then
|
||||||
local self = next(selfs, lastCleaned)
|
for handle, id in next, AceTimer.hashCompatTable do
|
||||||
if not self then
|
local t = activeTimers[id]
|
||||||
self = next(selfs)
|
if t then
|
||||||
|
activeTimers[id] = nil
|
||||||
|
activeTimers[handle] = t
|
||||||
|
t.handle = handle
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AceTimer.hashCompatTable = nil
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
-- ---------------------------------------------------------------------
|
-- ---------------------------------------------------------------------
|
||||||
@@ -436,38 +259,20 @@ local mixins = {
|
|||||||
|
|
||||||
function AceTimer:Embed(target)
|
function AceTimer:Embed(target)
|
||||||
AceTimer.embeds[target] = true
|
AceTimer.embeds[target] = true
|
||||||
for _,v in pairs(mixins) do
|
for _,v in next, mixins do
|
||||||
target[v] = AceTimer[v]
|
target[v] = AceTimer[v]
|
||||||
end
|
end
|
||||||
return target
|
return target
|
||||||
end
|
end
|
||||||
|
|
||||||
-- AceTimer:OnEmbedDisable( target )
|
-- AceTimer:OnEmbedDisable(target)
|
||||||
-- target (object) - target object that AceTimer is embedded in.
|
-- target (object) - target object that AceTimer is embedded in.
|
||||||
--
|
--
|
||||||
-- cancel all timers registered for the object
|
-- cancel all timers registered for the object
|
||||||
function AceTimer:OnEmbedDisable( target )
|
function AceTimer:OnEmbedDisable(target)
|
||||||
target:CancelAllTimers()
|
target:CancelAllTimers()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
for addon in next, AceTimer.embeds do
|
||||||
for addon in pairs(AceTimer.embeds) do
|
|
||||||
AceTimer:Embed(addon)
|
AceTimer:Embed(addon)
|
||||||
end
|
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.
|
|
||||||
|
|||||||
@@ -1,61 +1,26 @@
|
|||||||
--[[ $Id: CallbackHandler-1.0.lua 3 2008-09-29 16:54:20Z nevcairiel $ ]]
|
--[[ $Id: CallbackHandler-1.0.lua 25 2022-12-12 15:02:36Z nevcairiel $ ]]
|
||||||
local MAJOR, MINOR = "CallbackHandler-1.0", 3
|
local MAJOR, MINOR = "CallbackHandler-1.0", 8
|
||||||
local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR)
|
local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR)
|
||||||
|
|
||||||
if not CallbackHandler then return end -- No upgrade needed
|
if not CallbackHandler then return end -- No upgrade needed
|
||||||
|
|
||||||
local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end}
|
local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end}
|
||||||
|
|
||||||
local type = type
|
-- Lua APIs
|
||||||
local pcall = pcall
|
local securecallfunction, error = securecallfunction, error
|
||||||
local pairs = pairs
|
local setmetatable, rawget = setmetatable, rawget
|
||||||
local assert = assert
|
local next, select, pairs, type, tostring = next, select, pairs, type, tostring
|
||||||
local concat = table.concat
|
|
||||||
local loadstring = loadstring
|
|
||||||
local next = next
|
|
||||||
local select = select
|
|
||||||
local type = type
|
|
||||||
local xpcall = xpcall
|
|
||||||
|
|
||||||
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
|
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
|
-- CallbackHandler:New
|
||||||
--
|
--
|
||||||
@@ -64,9 +29,7 @@ end})
|
|||||||
-- UnregisterName - name of the callback unregistration API, default "UnregisterCallback"
|
-- 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.
|
-- 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)
|
function CallbackHandler.New(_self, target, RegisterName, UnregisterName, UnregisterAllName)
|
||||||
-- 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")
|
|
||||||
|
|
||||||
RegisterName = RegisterName or "RegisterCallback"
|
RegisterName = RegisterName or "RegisterCallback"
|
||||||
UnregisterName = UnregisterName or "UnregisterCallback"
|
UnregisterName = UnregisterName or "UnregisterCallback"
|
||||||
@@ -88,19 +51,19 @@ function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAll
|
|||||||
local oldrecurse = registry.recurse
|
local oldrecurse = registry.recurse
|
||||||
registry.recurse = oldrecurse + 1
|
registry.recurse = oldrecurse + 1
|
||||||
|
|
||||||
Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...)
|
Dispatch(events[eventname], eventname, ...)
|
||||||
|
|
||||||
registry.recurse = oldrecurse
|
registry.recurse = oldrecurse
|
||||||
|
|
||||||
if registry.insertQueue and oldrecurse==0 then
|
if registry.insertQueue and oldrecurse==0 then
|
||||||
-- Something in one of our callbacks wanted to register more callbacks; they got queued
|
-- Something in one of our callbacks wanted to register more callbacks; they got queued
|
||||||
for eventname,callbacks in pairs(registry.insertQueue) do
|
for event,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.
|
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 self,func in pairs(callbacks) do
|
for object,func in pairs(callbacks) do
|
||||||
events[eventname][self] = func
|
events[event][object] = func
|
||||||
-- fire OnUsed callback?
|
-- fire OnUsed callback?
|
||||||
if first and registry.OnUsed then
|
if first and registry.OnUsed then
|
||||||
registry.OnUsed(registry, target, eventname)
|
registry.OnUsed(registry, target, event)
|
||||||
first = nil
|
first = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -146,9 +109,9 @@ function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAll
|
|||||||
regfunc = function(...) self[method](self,...) end
|
regfunc = function(...) self[method](self,...) end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- function ref with self=object or self="addonId"
|
-- function ref with self=object or self="addonId" or self=thread
|
||||||
if type(self)~="table" and type(self)~="string" then
|
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 expected.", 2)
|
error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread expected.", 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
|
if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
|
||||||
|
|||||||
@@ -7,24 +7,24 @@ if not LibStub or LibStub.minor < LIBSTUB_MINOR then
|
|||||||
LibStub = LibStub or {libs = {}, minors = {} }
|
LibStub = LibStub or {libs = {}, minors = {} }
|
||||||
_G[LIBSTUB_MAJOR] = LibStub
|
_G[LIBSTUB_MAJOR] = LibStub
|
||||||
LibStub.minor = LIBSTUB_MINOR
|
LibStub.minor = LIBSTUB_MINOR
|
||||||
|
|
||||||
function LibStub:NewLibrary(major, minor)
|
function LibStub:NewLibrary(major, minor)
|
||||||
assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
|
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]
|
local oldminor = self.minors[major]
|
||||||
if oldminor and oldminor >= minor then return nil end
|
if oldminor and oldminor >= minor then return nil end
|
||||||
self.minors[major], self.libs[major] = minor, self.libs[major] or {}
|
self.minors[major], self.libs[major] = minor, self.libs[major] or {}
|
||||||
return self.libs[major], oldminor
|
return self.libs[major], oldminor
|
||||||
end
|
end
|
||||||
|
|
||||||
function LibStub:GetLibrary(major, silent)
|
function LibStub:GetLibrary(major, silent)
|
||||||
if not self.libs[major] and not silent then
|
if not self.libs[major] and not silent then
|
||||||
error(("Cannot find a library instance of %q."):format(tostring(major)), 2)
|
error(("Cannot find a library instance of %q."):format(tostring(major)), 2)
|
||||||
end
|
end
|
||||||
return self.libs[major], self.minors[major]
|
return self.libs[major], self.minors[major]
|
||||||
end
|
end
|
||||||
|
|
||||||
function LibStub:IterateLibraries() return pairs(self.libs) end
|
function LibStub:IterateLibraries() return pairs(self.libs) end
|
||||||
setmetatable(LibStub, { __call = LibStub.GetLibrary })
|
setmetatable(LibStub, { __call = LibStub.GetLibrary })
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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
|
|
||||||
Reference in New Issue
Block a user