mirror of
https://github.com/Bluewhale1337/ElvUIModernized.git
synced 2026-07-27 08:24:44 +00:00
replace Ace3v with Ace3 ElvUI-TBC version
or easy tracking of code difference
This commit is contained in:
@@ -35,10 +35,6 @@ local AceAddon, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not AceAddon then return end -- No Upgrade needed.
|
||||
|
||||
local AceCore = LibStub("AceCore-3.0")
|
||||
local new, del = AceCore.new, AceCore.del
|
||||
local wipe, truncate = AceCore.wipe, AceCore.truncate
|
||||
|
||||
AceAddon.frame = AceAddon.frame or CreateFrame("Frame", "AceAddon30Frame") -- Our very own frame
|
||||
AceAddon.addons = AceAddon.addons or {} -- addons in general
|
||||
AceAddon.statuses = AceAddon.statuses or {} -- statuses of addon.
|
||||
@@ -47,9 +43,9 @@ AceAddon.enablequeue = AceAddon.enablequeue or {} -- addons that are initialized
|
||||
AceAddon.embeds = AceAddon.embeds or setmetatable({}, {__index = function(tbl, key) tbl[key] = {} return tbl[key] end }) -- contains a list of libraries embedded in an addon
|
||||
|
||||
-- Lua APIs
|
||||
local tinsert, tconcat, tremove, tgetn = table.insert, table.concat, table.remove, table.getn
|
||||
local strfmt, tostring = string.format, tostring
|
||||
local pairs, next, type, unpack = pairs, next, type, unpack
|
||||
local tinsert, tconcat, tremove = table.insert, table.concat, table.remove
|
||||
local fmt, tostring = string.format, tostring
|
||||
local select, pairs, next, type, unpack = select, pairs, next, type, unpack
|
||||
local loadstring, assert, error = loadstring, assert, error
|
||||
local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget
|
||||
|
||||
@@ -60,7 +56,51 @@ local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, r
|
||||
--[[
|
||||
xpcall safecall implementation
|
||||
]]
|
||||
local safecall = AceCore.safecall
|
||||
local xpcall = xpcall
|
||||
|
||||
local function errorhandler(err)
|
||||
return geterrorhandler()(err)
|
||||
end
|
||||
|
||||
local function CreateDispatcher(argCount)
|
||||
local code = [[
|
||||
local xpcall, eh = ...
|
||||
local method, ARGS
|
||||
local function call() return method(ARGS) end
|
||||
|
||||
local function dispatch(func, ...)
|
||||
method = func
|
||||
if not method then return end
|
||||
ARGS = ...
|
||||
return xpcall(call, eh)
|
||||
end
|
||||
|
||||
return dispatch
|
||||
]]
|
||||
|
||||
local ARGS = {}
|
||||
for i = 1, argCount do ARGS[i] = "arg"..i end
|
||||
code = code:gsub("ARGS", tconcat(ARGS, ", "))
|
||||
return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler)
|
||||
end
|
||||
|
||||
local Dispatchers = setmetatable({}, {__index=function(self, argCount)
|
||||
local dispatcher = CreateDispatcher(argCount)
|
||||
rawset(self, argCount, dispatcher)
|
||||
return dispatcher
|
||||
end})
|
||||
Dispatchers[0] = function(func)
|
||||
return xpcall(func, errorhandler)
|
||||
end
|
||||
|
||||
local function safecall(func, ...)
|
||||
-- we check to see if the func is passed is actually a function here and don't error when it isn't
|
||||
-- this safecall is used for optional functions like OnInitialize OnEnable etc. When they are not
|
||||
-- present execution should continue without hinderance
|
||||
if type(func) == "function" then
|
||||
return Dispatchers[select('#', ...)](func, ...)
|
||||
end
|
||||
end
|
||||
|
||||
-- local functions that will be implemented further down
|
||||
local Enable, Disable, EnableModule, DisableModule, Embed, NewModule, GetModule, GetName, SetDefaultModuleState, SetDefaultModuleLibraries, SetEnabledState, SetDefaultModulePrototype
|
||||
@@ -70,7 +110,7 @@ local function addontostring( self ) return self.name end
|
||||
|
||||
-- Check if the addon is queued for initialization
|
||||
local function queuedForInitialization(addon)
|
||||
for i = 1, tgetn(AceAddon.initializequeue) do
|
||||
for i = 1, #AceAddon.initializequeue do
|
||||
if AceAddon.initializequeue[i] == addon then
|
||||
return true
|
||||
end
|
||||
@@ -93,34 +133,23 @@ end
|
||||
-- -- Create a Addon object based on the table of a frame
|
||||
-- local MyFrame = CreateFrame("Frame")
|
||||
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon(MyFrame, "MyAddon", "AceEvent-3.0")
|
||||
function AceAddon:NewAddon(objectorname,a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
|
||||
|
||||
function AceAddon:NewAddon(objectorname, ...)
|
||||
local object,name
|
||||
local i=1
|
||||
if type(objectorname)=="table" then
|
||||
object=objectorname
|
||||
name=a0
|
||||
name=...
|
||||
i=2
|
||||
else
|
||||
name=objectorname
|
||||
end
|
||||
if type(name)~="string" then
|
||||
error(strfmt("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - string expected got '%s'.", type(name)), 2)
|
||||
error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2)
|
||||
end
|
||||
if self.addons[name] then
|
||||
error(strfmt("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - Addon '%s' already exists.", name), 2)
|
||||
error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - Addon '%s' already exists."):format(name), 2)
|
||||
end
|
||||
|
||||
local args = new()
|
||||
args[1] = a1
|
||||
args[2] = a2
|
||||
args[3] = a3
|
||||
args[4] = a4
|
||||
args[5] = a5
|
||||
args[6] = a6
|
||||
args[7] = a7
|
||||
args[8] = a8
|
||||
args[9] = a9
|
||||
args[10] = a10
|
||||
|
||||
object = object or {}
|
||||
object.name = name
|
||||
|
||||
@@ -137,18 +166,14 @@ function AceAddon:NewAddon(objectorname,a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
|
||||
object.orderedModules = {}
|
||||
object.defaultModuleLibraries = {}
|
||||
Embed( object ) -- embed NewModule, GetModule methods
|
||||
if type(objectorname)=="table" then
|
||||
self:EmbedLibraries(object,nil,args)
|
||||
elseif a0 then
|
||||
self:EmbedLibraries(object,a0,args)
|
||||
end
|
||||
del(args)
|
||||
self:EmbedLibraries(object, select(i,...))
|
||||
|
||||
-- add to queue of addons to be initialized upon ADDON_LOADED
|
||||
tinsert(self.initializequeue, object)
|
||||
return object
|
||||
end
|
||||
|
||||
|
||||
--- Get the addon object by its name from the internal AceAddon registry.
|
||||
-- Throws an error if the addon object cannot be found (except if silent is set).
|
||||
-- @param name unique name of the addon object
|
||||
@@ -158,7 +183,7 @@ end
|
||||
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
||||
function AceAddon:GetAddon(name, silent)
|
||||
if not silent and not self.addons[name] then
|
||||
error(strfmt("Usage: GetAddon(name): 'name' - Cannot find an AceAddon '%s'.", tostring(name)), 2)
|
||||
error(("Usage: GetAddon(name): 'name' - Cannot find an AceAddon '%s'."):format(tostring(name)), 2)
|
||||
end
|
||||
return self.addons[name]
|
||||
end
|
||||
@@ -171,12 +196,10 @@ end
|
||||
-- @paramsig addon, [lib, ...]
|
||||
-- @param addon addon object to embed the libs in
|
||||
-- @param lib List of libraries to embed into the addon
|
||||
function AceAddon:EmbedLibraries(addon,a1,arg)
|
||||
if a1 then self:EmbedLibrary(addon, a1, false, 4) end
|
||||
-- 10 is the max number of variable arguments in the function NewAddon and NewModule
|
||||
for i=1,10 do
|
||||
if not arg[i] then return end
|
||||
self:EmbedLibrary(addon, arg[i], false, 4)
|
||||
function AceAddon:EmbedLibraries(addon, ...)
|
||||
for i=1,select("#", ... ) do
|
||||
local libname = select(i, ...)
|
||||
self:EmbedLibrary(addon, libname, false, 4)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -194,13 +217,13 @@ end
|
||||
function AceAddon:EmbedLibrary(addon, libname, silent, offset)
|
||||
local lib = LibStub:GetLibrary(libname, true)
|
||||
if not lib and not silent then
|
||||
error(strfmt("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Cannot find a library instance of %q.", tostring(libname)), offset or 2)
|
||||
error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Cannot find a library instance of %q."):format(tostring(libname)), offset or 2)
|
||||
elseif lib and type(lib.Embed) == "function" then
|
||||
lib:Embed(addon)
|
||||
tinsert(self.embeds[addon], libname)
|
||||
return true
|
||||
elseif lib then
|
||||
error(strfmt("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Library '%s' is not Embed capable", libname), offset or 2)
|
||||
error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Library '%s' is not Embed capable"):format(libname), offset or 2)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -217,7 +240,7 @@ end
|
||||
-- MyModule = MyAddon:GetModule("MyModule")
|
||||
function GetModule(self, name, silent)
|
||||
if not self.modules[name] and not silent then
|
||||
error(strfmt("Usage: GetModule(name, silent): 'name' - Cannot find module '%s'.", tostring(name)), 2)
|
||||
error(("Usage: GetModule(name, silent): 'name' - Cannot find module '%s'."):format(tostring(name)), 2)
|
||||
end
|
||||
return self.modules[name]
|
||||
end
|
||||
@@ -240,39 +263,26 @@ local function IsModuleTrue(self) return true end
|
||||
-- -- Create a module with a prototype
|
||||
-- local prototype = { OnEnable = function(self) print("OnEnable called!") end }
|
||||
-- MyModule = MyAddon:NewModule("MyModule", prototype, "AceEvent-3.0", "AceHook-3.0")
|
||||
function NewModule(self, name, prototype, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
|
||||
if type(name) ~= "string" then error(strfmt("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - string expected got '%s'.", type(name)), 2) end
|
||||
if type(prototype) ~= "string" and type(prototype) ~= "table" and type(prototype) ~= "nil" then error(strfmt("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'prototype' - table (prototype), string (lib) or nil expected got '%s'.", type(prototype)), 2) end
|
||||
function NewModule(self, name, prototype, ...)
|
||||
if type(name) ~= "string" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) end
|
||||
if type(prototype) ~= "string" and type(prototype) ~= "table" and type(prototype) ~= "nil" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'prototype' - table (prototype), string (lib) or nil expected got '%s'."):format(type(prototype)), 2) end
|
||||
|
||||
if self.modules[name] then error(strfmt("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - Module '%s' already exists.", 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.
|
||||
-- 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(strfmt("%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:SetEnabledState(self.defaultModuleState)
|
||||
module.moduleName = name
|
||||
|
||||
local args = new()
|
||||
args[1] = a1
|
||||
args[2] = a2
|
||||
args[3] = a3
|
||||
args[4] = a4
|
||||
args[5] = a5
|
||||
args[6] = a6
|
||||
args[7] = a7
|
||||
args[8] = a8
|
||||
args[9] = a9
|
||||
args[10] = a10
|
||||
|
||||
if type(prototype) == "string" then
|
||||
AceAddon:EmbedLibraries(module, prototype, args)
|
||||
AceAddon:EmbedLibraries(module, prototype, ...)
|
||||
else
|
||||
AceAddon:EmbedLibraries(module, nil, args)
|
||||
AceAddon:EmbedLibraries(module, ...)
|
||||
end
|
||||
del(args)
|
||||
AceAddon:EmbedLibraries(module, nil, self.defaultModuleLibraries)
|
||||
AceAddon:EmbedLibraries(module, unpack(self.defaultModuleLibraries))
|
||||
|
||||
if not prototype or type(prototype) == "string" then
|
||||
prototype = self.defaultModulePrototype or nil
|
||||
@@ -284,7 +294,7 @@ function NewModule(self, name, prototype, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
|
||||
setmetatable(module, mt) -- More of a Base class type feel.
|
||||
end
|
||||
|
||||
safecall(self.OnModuleCreated, 2, 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
|
||||
tinsert(self.orderedModules, module)
|
||||
|
||||
@@ -385,23 +395,11 @@ end
|
||||
-- MyAddon:SetDefaultModuleLibraries("AceEvent-3.0")
|
||||
-- -- Create a module
|
||||
-- MyModule = MyAddon:NewModule("MyModule")
|
||||
function SetDefaultModuleLibraries(self,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
|
||||
function SetDefaultModuleLibraries(self, ...)
|
||||
if next(self.modules) then
|
||||
error("Usage: SetDefaultModuleLibraries(...): cannot change the module defaults after a module has been registered.", 2)
|
||||
end
|
||||
local args = self.defaultModuleLibraries or {}
|
||||
args[1] = a1
|
||||
args[2] = a2
|
||||
args[3] = a3
|
||||
args[4] = a4
|
||||
args[5] = a5
|
||||
args[6] = a6
|
||||
args[7] = a7
|
||||
args[8] = a8
|
||||
args[9] = a9
|
||||
args[10] = a10
|
||||
|
||||
self.defaultModuleLibraries = args
|
||||
self.defaultModuleLibraries = {...}
|
||||
end
|
||||
|
||||
--- Set the default state in which new modules are being created.
|
||||
@@ -444,7 +442,7 @@ function SetDefaultModulePrototype(self, prototype)
|
||||
error("Usage: SetDefaultModulePrototype(prototype): cannot change the module defaults after a module has been registered.", 2)
|
||||
end
|
||||
if type(prototype) ~= "table" then
|
||||
error(strfmt("Usage: SetDefaultModulePrototype(prototype): 'prototype' - table expected got '%s'.", type(prototype)), 2)
|
||||
error(("Usage: SetDefaultModulePrototype(prototype): 'prototype' - table expected got '%s'."):format(type(prototype)), 2)
|
||||
end
|
||||
self.defaultModulePrototype = prototype
|
||||
end
|
||||
@@ -528,12 +526,12 @@ end
|
||||
-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing.
|
||||
-- @param addon addon object to intialize
|
||||
function AceAddon:InitializeAddon(addon)
|
||||
safecall(addon.OnInitialize, 1, addon)
|
||||
safecall(addon.OnInitialize, addon)
|
||||
|
||||
local embeds = self.embeds[addon]
|
||||
for i = 1, tgetn(embeds) do
|
||||
for i = 1, #embeds do
|
||||
local lib = LibStub:GetLibrary(embeds[i], true)
|
||||
if lib then safecall(lib.OnEmbedInitialize, 2, lib, addon) end
|
||||
if lib then safecall(lib.OnEmbedInitialize, lib, addon) end
|
||||
end
|
||||
|
||||
-- we don't call InitializeAddon on modules specifically, this is handled
|
||||
@@ -557,19 +555,19 @@ function AceAddon:EnableAddon(addon)
|
||||
-- set the statuses first, before calling the OnEnable. this allows for Disabling of the addon in OnEnable.
|
||||
self.statuses[addon.name] = true
|
||||
|
||||
safecall(addon.OnEnable, 1, addon)
|
||||
safecall(addon.OnEnable, addon)
|
||||
|
||||
-- make sure we're still enabled before continueing
|
||||
if self.statuses[addon.name] then
|
||||
local embeds = self.embeds[addon]
|
||||
for i = 1, tgetn(embeds) do
|
||||
for i = 1, #embeds do
|
||||
local lib = LibStub:GetLibrary(embeds[i], true)
|
||||
if lib then safecall(lib.OnEmbedEnable, 2, lib, addon) end
|
||||
if lib then safecall(lib.OnEmbedEnable, lib, addon) end
|
||||
end
|
||||
|
||||
-- enable possible modules.
|
||||
local modules = addon.orderedModules
|
||||
for i = 1, tgetn(modules) do
|
||||
for i = 1, #modules do
|
||||
self:EnableAddon(modules[i])
|
||||
end
|
||||
end
|
||||
@@ -592,18 +590,18 @@ function AceAddon:DisableAddon(addon)
|
||||
-- set statuses first before calling OnDisable, this allows for aborting the disable in OnDisable.
|
||||
self.statuses[addon.name] = false
|
||||
|
||||
safecall(addon.OnDisable, 1, addon)
|
||||
safecall( addon.OnDisable, addon )
|
||||
|
||||
-- make sure we're still disabling...
|
||||
if not self.statuses[addon.name] then
|
||||
local embeds = self.embeds[addon]
|
||||
for i = 1, tgetn(embeds) do
|
||||
for i = 1, #embeds do
|
||||
local lib = LibStub:GetLibrary(embeds[i], true)
|
||||
if lib then safecall(lib.OnEmbedDisable, 2, lib, addon) end
|
||||
if lib then safecall(lib.OnEmbedDisable, lib, addon) end
|
||||
end
|
||||
-- disable possible modules.
|
||||
local modules = addon.orderedModules
|
||||
for i = 1, tgetn(modules) do
|
||||
for i = 1, #modules do
|
||||
self:DisableAddon(modules[i])
|
||||
end
|
||||
end
|
||||
@@ -634,22 +632,12 @@ function AceAddon:IterateAddonStatus() return pairs(self.statuses) end
|
||||
function AceAddon:IterateEmbedsOnAddon(addon) return pairs(self.embeds[addon]) end
|
||||
function AceAddon:IterateModulesOfAddon(addon) return pairs(addon.modules) end
|
||||
|
||||
|
||||
local onEvent
|
||||
do
|
||||
local IsLoggedIn = false
|
||||
-- Event Handling
|
||||
function onEvent()
|
||||
-- 2011-08-17 nevcairiel - ignore the load event of Blizzard_DebugTools, so a potential startup error isn't swallowed up
|
||||
-- Ace3v: When the ADDON_LOADED event is triggerd, global arg1 is the loaded addon name
|
||||
-- so onEvent(event, arg1) won't work because it will cover the global variables
|
||||
if (event == "ADDON_LOADED" and arg1 ~= "Blizzard_DebugTools") or event == "PLAYER_LOGIN" then
|
||||
local function onEvent(this, event, arg1)
|
||||
-- 2011-08-17 nevcairiel - ignore the load event of !DebugTools, so a potential startup error isn't swallowed up
|
||||
if (event == "ADDON_LOADED" and arg1 ~= "!DebugTools") 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
|
||||
-- Ace3v: When an Ace3 addons is loaded, then he initializeque should not be empty unless
|
||||
-- the addon does not use the library. If an addon that does not use Ace3 library
|
||||
-- is loaded, we will also receive the ADDON_LOADED event but in this case the
|
||||
-- queue will be empty so we have nothing to do.
|
||||
while(tgetn(AceAddon.initializequeue) > 0) do
|
||||
while(#AceAddon.initializequeue > 0) do
|
||||
local addon = tremove(AceAddon.initializequeue, 1)
|
||||
-- this might be an issue with recursion - TODO: validate
|
||||
if event == "ADDON_LOADED" then addon.baseName = arg1 end
|
||||
@@ -657,19 +645,14 @@ function onEvent()
|
||||
tinsert(AceAddon.enablequeue, addon)
|
||||
end
|
||||
|
||||
if event == "PLAYER_LOGIN" then
|
||||
IsLoggedIn = true
|
||||
end
|
||||
|
||||
if IsLoggedIn then
|
||||
while(tgetn(AceAddon.enablequeue) > 0) do
|
||||
if IsLoggedIn() then
|
||||
while(#AceAddon.enablequeue > 0) do
|
||||
local addon = tremove(AceAddon.enablequeue, 1)
|
||||
AceAddon:EnableAddon(addon)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end -- onEvent
|
||||
|
||||
AceAddon.frame:RegisterEvent("ADDON_LOADED")
|
||||
AceAddon.frame:RegisterEvent("PLAYER_LOGIN")
|
||||
@@ -679,3 +662,13 @@ AceAddon.frame:SetScript("OnEvent", onEvent)
|
||||
for name, addon in pairs(AceAddon.addons) do
|
||||
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
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="AceAddon-3.0.lua"/>
|
||||
</Ui>
|
||||
@@ -9,7 +9,7 @@
|
||||
-- make into AceComm.
|
||||
-- @class file
|
||||
-- @name AceComm-3.0
|
||||
-- @release $Id: AceComm-3.0.lua 1107 2014-02-19 16:40:32Z nevcairiel $
|
||||
-- @release $Id: AceComm-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $
|
||||
|
||||
--[[ AceComm-3.0
|
||||
|
||||
@@ -17,25 +17,23 @@ TODO: Time out old data rotting around from dead senders? Not a HUGE deal since
|
||||
|
||||
]]
|
||||
|
||||
local MAJOR, MINOR = "AceComm-3.0", 9
|
||||
local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0")
|
||||
local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib")
|
||||
|
||||
local MAJOR, MINOR = "AceComm-3.0", 10
|
||||
local AceComm,oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not AceComm then return end
|
||||
|
||||
local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0")
|
||||
local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib")
|
||||
|
||||
-- Lua APIs
|
||||
local type, next, pairs, tostring = type, next, pairs, tostring
|
||||
local strlen, strsub, strfind = string.len, string.sub, string.find
|
||||
local tinsert, tconcat, tgetn, tremove = table.insert, table.concat, table.getn, table.remove
|
||||
local strsub, strfind = string.sub, string.find
|
||||
local tinsert, tconcat = table.insert, table.concat
|
||||
local error, assert = error, assert
|
||||
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: LibStub, DEFAULT_CHAT_FRAME, geterrorhandler, RegisterAddonMessagePrefix
|
||||
-- GLOBALS: LibStub, DEFAULT_CHAT_FRAME, geterrorhandler
|
||||
|
||||
AceComm.embeds = AceComm.embeds or {}
|
||||
|
||||
@@ -43,27 +41,21 @@ AceComm.embeds = AceComm.embeds or {}
|
||||
local MSG_MULTI_FIRST = "\001"
|
||||
local MSG_MULTI_NEXT = "\002"
|
||||
local MSG_MULTI_LAST = "\003"
|
||||
local MSG_ESCAPE = "\004"
|
||||
|
||||
-- remove old structures (pre WoW 4.0)
|
||||
AceComm.multipart_origprefixes = nil
|
||||
AceComm.multipart_reassemblers = nil
|
||||
AceComm.multipart_origprefixes = AceComm.multipart_origprefixes or {} -- e.g. "Prefix\001"="Prefix", "Prefix\002"="Prefix"
|
||||
AceComm.multipart_reassemblers = AceComm.multipart_reassemblers or {} -- e.g. "Prefix\001"="OnReceiveMultipartFirst"
|
||||
|
||||
-- the multipart message spool: indexed by a combination of sender+distribution+
|
||||
AceComm.multipart_spool = AceComm.multipart_spool or {}
|
||||
|
||||
--- Register for Addon Traffic on a specified prefix
|
||||
-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent), max 16 characters
|
||||
-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent)
|
||||
-- @param method Callback to call on message reception: Function reference, or method name (string) to call on self. Defaults to "OnCommReceived"
|
||||
function AceComm:RegisterComm(prefix, method)
|
||||
if method == nil then
|
||||
method = "OnCommReceived"
|
||||
end
|
||||
|
||||
if strlen(prefix) > 16 then -- TODO: 15?
|
||||
error("AceComm:RegisterComm(prefix,method): prefix length is limited to 16 characters")
|
||||
end
|
||||
|
||||
return AceComm._RegisterComm(self, prefix, method) -- created by CallbackHandler
|
||||
end
|
||||
|
||||
@@ -88,10 +80,19 @@ function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callb
|
||||
error('Usage: SendCommMessage(addon, "prefix", "text", "distribution"[, "target"[, "prio"[, callbackFn, callbackarg]]])', 2)
|
||||
end
|
||||
|
||||
local textlen = strlen(text)
|
||||
-- Yes, the max is 255 even if the dev post said 256. I tested. Char 256+ get silently truncated. /Mikk, 20110327
|
||||
-- Ace3v: substract the prefix length
|
||||
local maxtextlen = 254 - strlen(prefix)
|
||||
if strfind(prefix, "[\001-\009]") then
|
||||
if strfind(prefix, "[\001-\003]") then
|
||||
error("SendCommMessage: Characters \\001--\\003 in prefix are reserved for AceComm metadata", 2)
|
||||
elseif not warnedPrefix then
|
||||
-- I have some ideas about future extensions that require more control characters /mikk, 20090808
|
||||
geterrorhandler()("SendCommMessage: Heads-up developers: Characters \\004--\\009 in prefix are reserved for AceComm future extension")
|
||||
warnedPrefix = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local textlen = #text
|
||||
local maxtextlen = 254 - #prefix -- 254 is the max length of prefix + text that can be sent in one message
|
||||
local queueName = prefix..distribution..(target or "")
|
||||
|
||||
local ctlCallback = nil
|
||||
@@ -101,38 +102,29 @@ function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callb
|
||||
end
|
||||
end
|
||||
|
||||
local forceMultipart
|
||||
if strfind(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
|
||||
if textlen <= maxtextlen then
|
||||
-- fits all in one message
|
||||
CTL:SendAddonMessage(prio, prefix, text, distribution, target, queueName, ctlCallback, textlen)
|
||||
else
|
||||
maxtextlen = maxtextlen - 1 -- 1 extra byte for part indicator in prefix(4.0)/start of message(4.1)
|
||||
maxtextlen = maxtextlen - 1 -- 1 extra byte for part indicator in prefix
|
||||
|
||||
-- first part
|
||||
local chunk = strsub(text, 1, maxtextlen)
|
||||
CTL:SendAddonMessage(prio, prefix, MSG_MULTI_FIRST..chunk, distribution, target, queueName, ctlCallback, maxtextlen)
|
||||
CTL:SendAddonMessage(prio, prefix..MSG_MULTI_FIRST, chunk, distribution, target, queueName, ctlCallback, maxtextlen)
|
||||
|
||||
-- continuation
|
||||
local pos = 1+maxtextlen
|
||||
local prefix2 = prefix..MSG_MULTI_NEXT
|
||||
|
||||
while pos+maxtextlen <= textlen do
|
||||
chunk = strsub(text, pos, pos+maxtextlen-1)
|
||||
CTL:SendAddonMessage(prio, prefix, MSG_MULTI_NEXT..chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1)
|
||||
CTL:SendAddonMessage(prio, prefix2, chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1)
|
||||
pos = pos + maxtextlen
|
||||
end
|
||||
|
||||
-- final part
|
||||
chunk = strsub(text, pos)
|
||||
CTL:SendAddonMessage(prio, prefix, MSG_MULTI_LAST..chunk, distribution, target, queueName, ctlCallback, textlen)
|
||||
CTL:SendAddonMessage(prio, prefix..MSG_MULTI_LAST, chunk, distribution, target, queueName, ctlCallback, textlen)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -147,8 +139,8 @@ do
|
||||
local t = next(compost)
|
||||
if t then
|
||||
compost[t]=nil
|
||||
for i=tgetn(t),3,-1 do -- faster than pairs loop. don't even nil out 1/2 since they'll be overwritten
|
||||
tremove(t) -- Ace3v: t[i] = nil wont affect the tgetn return value
|
||||
for i=#t,3,-1 do -- faster than pairs loop. don't even nil out 1/2 since they'll be overwritten
|
||||
t[i]=nil
|
||||
end
|
||||
return t
|
||||
end
|
||||
@@ -210,11 +202,11 @@ do
|
||||
if type(olddata) == "table" then
|
||||
-- if we've received a "next", the spooled data will be a table for rapid & garbage-free tconcat
|
||||
tinsert(olddata, message)
|
||||
AceComm.callbacks:Fire(prefix, 3, tconcat(olddata, ""), distribution, sender)
|
||||
AceComm.callbacks:Fire(prefix, tconcat(olddata, ""), distribution, sender)
|
||||
compost[olddata] = true
|
||||
else
|
||||
-- if we've only received a "first", the spooled data will still only be a string
|
||||
AceComm.callbacks:Fire(prefix, 3, olddata..message, distribution, sender)
|
||||
AceComm.callbacks:Fire(prefix, olddata..message, distribution, sender)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -229,40 +221,50 @@ end
|
||||
----------------------------------------
|
||||
|
||||
if not AceComm.callbacks then
|
||||
-- ensure that 'prefix to watch' table is consistent with registered
|
||||
-- callbacks
|
||||
AceComm.__prefixes = {}
|
||||
|
||||
AceComm.callbacks = CallbackHandler:New(AceComm,
|
||||
"_RegisterComm",
|
||||
"UnregisterComm",
|
||||
"UnregisterAllComm")
|
||||
end
|
||||
|
||||
AceComm.callbacks.OnUsed = nil
|
||||
AceComm.callbacks.OnUnused = nil
|
||||
function AceComm.callbacks:OnUsed(target, prefix)
|
||||
AceComm.multipart_origprefixes[prefix..MSG_MULTI_FIRST] = prefix
|
||||
AceComm.multipart_reassemblers[prefix..MSG_MULTI_FIRST] = "OnReceiveMultipartFirst"
|
||||
|
||||
-- Ace3v: in vanilla, global vars:
|
||||
-- event -> event type
|
||||
-- arg1 -> prefix
|
||||
-- arg2 -> message
|
||||
-- arg3 -> channel
|
||||
-- arg4 -> sender
|
||||
local function OnEvent()
|
||||
local prefix, message, distribution, sender = arg1, arg2, arg3, arg4
|
||||
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)
|
||||
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
|
||||
local _, _, control, rest = strfind(message, "^([\001-\009])(.*)")
|
||||
if control then
|
||||
if control==MSG_MULTI_FIRST then
|
||||
AceComm:OnReceiveMultipartFirst(prefix, rest, distribution, sender)
|
||||
elseif control==MSG_MULTI_NEXT then
|
||||
AceComm:OnReceiveMultipartNext(prefix, rest, distribution, sender)
|
||||
elseif control==MSG_MULTI_LAST then
|
||||
AceComm:OnReceiveMultipartLast(prefix, rest, distribution, sender)
|
||||
elseif control==MSG_ESCAPE then
|
||||
AceComm.callbacks:Fire(prefix, 3, rest, distribution, sender)
|
||||
else
|
||||
-- unknown control character, ignore SILENTLY (dont warn unnecessarily about future extensions!)
|
||||
end
|
||||
local prefix,message,distribution,sender = ...
|
||||
local reassemblername = AceComm.multipart_reassemblers[prefix]
|
||||
if reassemblername then
|
||||
-- multipart: reassemble
|
||||
local aceCommReassemblerFunc = AceComm[reassemblername]
|
||||
local origprefix = AceComm.multipart_origprefixes[prefix]
|
||||
aceCommReassemblerFunc(AceComm, origprefix, message, distribution, sender)
|
||||
else
|
||||
-- single part: fire it off immediately and let CallbackHandler decide if it's registered or not
|
||||
AceComm.callbacks:Fire(prefix, 3, message, distribution, sender)
|
||||
AceComm.callbacks:Fire(prefix, message, distribution, sender)
|
||||
end
|
||||
else
|
||||
assert(false, "Received "..tostring(event).." event?!")
|
||||
@@ -303,4 +305,4 @@ end
|
||||
-- Update embeds
|
||||
for target, v in pairs(AceComm.embeds) do
|
||||
AceComm:Embed(target)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="ChatThrottleLib.lua"/>
|
||||
<Script file="AceComm-3.0.lua"/>
|
||||
</Ui>
|
||||
@@ -20,15 +20,10 @@
|
||||
--
|
||||
-- Can run as a standalone addon also, but, really, just embed it! :-)
|
||||
--
|
||||
-- LICENSE: ChatThrottleLib is released into the Public Domain
|
||||
--
|
||||
|
||||
local CTL_VERSION = 23
|
||||
local CTL_VERSION = 21
|
||||
|
||||
local AceCore = LibStub("AceCore-3.0")
|
||||
local _G = AceCore._G
|
||||
local hooksecurefunc = AceCore.hooksecurefunc
|
||||
local wipe = AceCore.wipe
|
||||
local _G = _G
|
||||
|
||||
if _G.ChatThrottleLib then
|
||||
if _G.ChatThrottleLib.version >= CTL_VERSION then
|
||||
@@ -70,7 +65,6 @@ ChatThrottleLib.MIN_FPS = 20 -- Reduce output CPS to half (and don't burst) i
|
||||
|
||||
local setmetatable = setmetatable
|
||||
local table_remove = table.remove
|
||||
local tinsert = table.insert
|
||||
local tostring = tostring
|
||||
local GetTime = GetTime
|
||||
local math_min = math.min
|
||||
@@ -78,9 +72,7 @@ local math_max = math.max
|
||||
local next = next
|
||||
local strlen = string.len
|
||||
local GetFramerate = GetFramerate
|
||||
local strlower = string.lower
|
||||
local unpack,type,pairs,wipe = unpack,type,pairs,wipe
|
||||
local UnitInRaid,UnitInParty = UnitInRaid,UnitInParty
|
||||
|
||||
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
@@ -123,20 +115,24 @@ end
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- Recycling bin for pipes
|
||||
-- A pipe is a plain integer-indexed queue of messages
|
||||
-- Pipes normally live in Rings of pipes (3 rings total, one per priority)
|
||||
-- A pipe is a plain integer-indexed queue, which also happens to be a ring member
|
||||
|
||||
ChatThrottleLib.PipeBin = nil -- pre-v19, drastically different
|
||||
local PipeBin = setmetatable({}, {__mode="k"})
|
||||
|
||||
local function DelPipe(pipe)
|
||||
for i = #pipe, 1, -1 do
|
||||
pipe[i] = nil
|
||||
end
|
||||
pipe.prev = nil
|
||||
pipe.next = nil
|
||||
|
||||
PipeBin[pipe] = true
|
||||
end
|
||||
|
||||
local function NewPipe()
|
||||
local pipe = next(PipeBin)
|
||||
if pipe then
|
||||
wipe(pipe)
|
||||
PipeBin[pipe] = nil
|
||||
return pipe
|
||||
end
|
||||
@@ -213,12 +209,12 @@ function ChatThrottleLib:Init()
|
||||
-- Use secure hooks as of v16. Old regular hook support yanked out in v21.
|
||||
self.securelyHooked = true
|
||||
--SendChatMessage
|
||||
hooksecurefunc("SendChatMessage", function(text, chattype, language, destination)
|
||||
return ChatThrottleLib.Hook_SendChatMessage(text, chattype, language, destination)
|
||||
hooksecurefunc("SendChatMessage", function(...)
|
||||
return ChatThrottleLib.Hook_SendChatMessage(...)
|
||||
end)
|
||||
--SendAddonMessage
|
||||
hooksecurefunc("SendAddonMessage", function(prefix, text, chattype, destination)
|
||||
return ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destination)
|
||||
hooksecurefunc("SendAddonMessage", function(...)
|
||||
return ChatThrottleLib.Hook_SendAddonMessage(...)
|
||||
end)
|
||||
end
|
||||
self.nBypass = 0
|
||||
@@ -230,7 +226,7 @@ end
|
||||
|
||||
local bMyTraffic = false
|
||||
|
||||
function ChatThrottleLib.Hook_SendChatMessage(text, chattype, language, destination)
|
||||
function ChatThrottleLib.Hook_SendChatMessage(text, chattype, language, destination, ...)
|
||||
if bMyTraffic then
|
||||
return
|
||||
end
|
||||
@@ -239,13 +235,13 @@ function ChatThrottleLib.Hook_SendChatMessage(text, chattype, language, destinat
|
||||
self.avail = self.avail - size
|
||||
self.nBypass = self.nBypass + size -- just a statistic
|
||||
end
|
||||
function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destination)
|
||||
function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destination, ...)
|
||||
if bMyTraffic then
|
||||
return
|
||||
end
|
||||
local self = ChatThrottleLib
|
||||
local size = strlen(tostring(text or "")) + strlen(tostring(prefix or ""));
|
||||
size = size + strlen(tostring(destination or "")) + self.MSG_OVERHEAD
|
||||
local size = tostring(text or ""):len() + tostring(prefix or ""):len();
|
||||
size = size + tostring(destination or ""):len() + self.MSG_OVERHEAD
|
||||
self.avail = self.avail - size
|
||||
self.nBypass = self.nBypass + size -- just a statistic
|
||||
end
|
||||
@@ -266,7 +262,7 @@ function ChatThrottleLib:UpdateAvail()
|
||||
-- First 5 seconds after startup/zoning: VERY hard clamping to avoid irritating the server rate limiter, it seems very cranky then
|
||||
avail = math_min(avail + (newavail*0.1), MAX_CPS*0.5)
|
||||
self.bChoking = true
|
||||
elseif GetFramerate() < self.MIN_FPS then -- GetFrameRate call takes ~0.002 secs
|
||||
elseif GetFramerate() < self.MIN_FPS then -- GetFramerate call takes ~0.002 secs
|
||||
avail = math_min(MAX_CPS, avail + newavail*0.5)
|
||||
self.bChoking = true -- just a statistic
|
||||
else
|
||||
@@ -285,16 +281,12 @@ end
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- Despooling logic
|
||||
-- Reminder:
|
||||
-- - We have 3 Priorities, each containing a "Ring" construct ...
|
||||
-- - ... made up of N "Pipe"s (1 for each destination/pipename)
|
||||
-- - and each pipe contains messages
|
||||
|
||||
function ChatThrottleLib:Despool(Prio)
|
||||
local ring = Prio.Ring
|
||||
while ring.pos and Prio.avail > ring.pos[1].nSize do
|
||||
local msg = table_remove(ring.pos, 1)
|
||||
if not ring.pos[1] then -- did we remove last msg in this pipe?
|
||||
local msg = table_remove(Prio.Ring.pos, 1)
|
||||
if not Prio.Ring.pos[1] then
|
||||
local pipe = Prio.Ring.pos
|
||||
Prio.Ring:Remove(pipe)
|
||||
Prio.ByName[pipe.name] = nil
|
||||
@@ -302,31 +294,20 @@ function ChatThrottleLib:Despool(Prio)
|
||||
else
|
||||
Prio.Ring.pos = Prio.Ring.pos.next
|
||||
end
|
||||
local didSend=false
|
||||
local lowerDest = strlower(msg[3] or "")
|
||||
if lowerDest == "raid" and not UnitInRaid("player") then
|
||||
-- do nothing
|
||||
elseif lowerDest == "party" and not UnitInParty("player") then
|
||||
-- do nothing
|
||||
else
|
||||
Prio.avail = Prio.avail - msg.nSize
|
||||
bMyTraffic = true
|
||||
msg.f(unpack(msg, 1, msg.n))
|
||||
bMyTraffic = false
|
||||
Prio.nTotalSent = Prio.nTotalSent + msg.nSize
|
||||
DelMsg(msg)
|
||||
didSend = true
|
||||
end
|
||||
-- notify caller of delivery (even if we didn't send it)
|
||||
Prio.avail = Prio.avail - msg.nSize
|
||||
bMyTraffic = true
|
||||
msg.f(unpack(msg, 1, msg.n))
|
||||
bMyTraffic = false
|
||||
Prio.nTotalSent = Prio.nTotalSent + msg.nSize
|
||||
DelMsg(msg)
|
||||
if msg.callbackFn then
|
||||
msg.callbackFn (msg.callbackArg, didSend)
|
||||
msg.callbackFn (msg.callbackArg)
|
||||
end
|
||||
-- USER CALLBACK MAY ERROR
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function ChatThrottleLib.OnEvent()
|
||||
function ChatThrottleLib.OnEvent(this,event)
|
||||
-- v11: We know that the rate limiter is touchy after login. Assume that it's touchy after zoning, too.
|
||||
local self = ChatThrottleLib
|
||||
if event == "PLAYER_ENTERING_WORLD" then
|
||||
@@ -336,10 +317,10 @@ function ChatThrottleLib.OnEvent()
|
||||
end
|
||||
|
||||
|
||||
function ChatThrottleLib.OnUpdate()
|
||||
function ChatThrottleLib.OnUpdate(this,delay)
|
||||
local self = ChatThrottleLib
|
||||
|
||||
self.OnUpdateDelay = self.OnUpdateDelay + arg1
|
||||
self.OnUpdateDelay = self.OnUpdateDelay + delay
|
||||
if self.OnUpdateDelay < 0.08 then
|
||||
return
|
||||
end
|
||||
@@ -393,6 +374,7 @@ end
|
||||
-----------------------------------------------------------------------
|
||||
-- Spooling logic
|
||||
|
||||
|
||||
function ChatThrottleLib:Enqueue(prioname, pipename, msg)
|
||||
local Prio = self.Prio[prioname]
|
||||
local pipe = Prio.ByName[pipename]
|
||||
@@ -404,11 +386,13 @@ function ChatThrottleLib:Enqueue(prioname, pipename, msg)
|
||||
Prio.Ring:Add(pipe)
|
||||
end
|
||||
|
||||
tinsert(pipe,msg)
|
||||
pipe[#pipe + 1] = msg
|
||||
|
||||
self.bQueueing = true
|
||||
end
|
||||
|
||||
|
||||
|
||||
function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, language, destination, queueName, callbackFn, callbackArg)
|
||||
if not self or not prio or not prefix or not text or not self.Prio[prio] then
|
||||
error('Usage: ChatThrottleLib:SendChatMessage("{BULK||NORMAL||ALERT}", "prefix", "text"[, "chattype"[, "language"[, "destination"]]]', 2)
|
||||
@@ -417,7 +401,7 @@ function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, languag
|
||||
error('ChatThrottleLib:ChatMessage(): callbackFn: expected function, got '..type(callbackFn), 2)
|
||||
end
|
||||
|
||||
local nSize = strlen(text)
|
||||
local nSize = text:len()
|
||||
|
||||
if nSize>255 then
|
||||
error("ChatThrottleLib:SendChatMessage(): message length cannot exceed 255 bytes", 2)
|
||||
@@ -433,9 +417,8 @@ function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, languag
|
||||
bMyTraffic = false
|
||||
self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
|
||||
if callbackFn then
|
||||
callbackFn (callbackArg, true)
|
||||
callbackFn (callbackArg)
|
||||
end
|
||||
-- USER CALLBACK MAY ERROR
|
||||
return
|
||||
end
|
||||
|
||||
@@ -463,17 +446,10 @@ function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target,
|
||||
error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2)
|
||||
end
|
||||
|
||||
local nSize = strlen(text);
|
||||
local nSize = prefix:len() + 1 + text:len();
|
||||
|
||||
if RegisterAddonMessagePrefix then
|
||||
if nSize>255 then
|
||||
error("ChatThrottleLib:SendAddonMessage(): message length cannot exceed 255 bytes", 2)
|
||||
end
|
||||
else
|
||||
nSize = nSize + strlen(prefix) + 1
|
||||
if nSize>255 then
|
||||
error("ChatThrottleLib:SendAddonMessage(): prefix + message length cannot exceed 254 bytes", 2)
|
||||
end
|
||||
if nSize>255 then
|
||||
error("ChatThrottleLib:SendAddonMessage(): prefix + message length cannot exceed 254 bytes", 2)
|
||||
end
|
||||
|
||||
nSize = nSize + self.MSG_OVERHEAD;
|
||||
@@ -486,9 +462,8 @@ function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target,
|
||||
bMyTraffic = false
|
||||
self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
|
||||
if callbackFn then
|
||||
callbackFn (callbackArg, true)
|
||||
callbackFn (callbackArg)
|
||||
end
|
||||
-- USER CALLBACK MAY ERROR
|
||||
return
|
||||
end
|
||||
|
||||
@@ -525,3 +500,4 @@ if(WOWB_VER) then
|
||||
end
|
||||
]]
|
||||
|
||||
|
||||
|
||||
@@ -16,110 +16,87 @@ local AceConsole, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not AceConsole then return end -- No upgrade needed
|
||||
|
||||
local AceCore = LibStub("AceCore-3.0")
|
||||
|
||||
AceConsole.embeds = AceConsole.embeds or {} -- table containing objects AceConsole is embedded in.
|
||||
AceConsole.commands = AceConsole.commands or {} -- table containing commands registered
|
||||
AceConsole.weakcommands = AceConsole.weakcommands or {} -- table containing self, command => func references for weak commands that don't persist through enable/disable
|
||||
|
||||
-- Lua APIs
|
||||
local tinsert, tconcat, tgetn, tsetn = table.insert, table.concat, table.getn, table.setn
|
||||
local tostring = tostring
|
||||
local tconcat, tostring, select = table.concat, tostring, select
|
||||
local type, pairs, error = type, pairs, error
|
||||
local format, strfind, strsub = string.format, string.find, string.sub
|
||||
local max = math.max
|
||||
local strupper, strlower = string.upper, string.lower
|
||||
|
||||
-- WoW APIs
|
||||
local _G = AceCore._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 Print
|
||||
do
|
||||
local tmp = {}
|
||||
function Print(self, frame, arg)
|
||||
|
||||
local tmp={}
|
||||
local function Print(self,frame,...)
|
||||
local n=0
|
||||
if self ~= AceConsole then
|
||||
tmp[1] = "|cff33ff99"..tostring(self).."|r:"
|
||||
else
|
||||
tmp[1] = ''
|
||||
n=n+1
|
||||
tmp[n] = "|cff33ff99"..tostring( self ).."|r:"
|
||||
end
|
||||
if type(arg) == "string" then
|
||||
frame:AddMessage(tmp[1]..arg)
|
||||
else -- arg is table and may contain frame as first element if argument frame is nil
|
||||
local b, e = frame and 1 or 2, tgetn(arg)
|
||||
if e >= b then
|
||||
frame = frame or arg[1]
|
||||
for i=0,e-b do
|
||||
tmp[2+i] = tostring(arg[b+i])
|
||||
end
|
||||
frame:AddMessage(tconcat(tmp," ",1,e-b+2)) -- explicitly, because the length is not affected by assignment
|
||||
end
|
||||
for i=1, select("#", ...) do
|
||||
n=n+1
|
||||
tmp[n] = tostring(select(i, ...))
|
||||
end
|
||||
frame:AddMessage( tconcat(tmp," ",1,n) )
|
||||
end
|
||||
end -- Print
|
||||
|
||||
--- Print to DEFAULT_CHAT_FRAME or given ChatFrame (anything with an .AddMessage function)
|
||||
-- @paramsig [chatframe ,] ...
|
||||
-- @param chatframe Custom ChatFrame to print to (or any frame with an .AddMessage function)
|
||||
-- @param ... List of any values to be printed
|
||||
function AceConsole:Print(...)
|
||||
local frame = arg[1]
|
||||
local frame = ...
|
||||
if type(frame) == "table" and frame.AddMessage then -- Is first argument something with an .AddMessage member?
|
||||
return Print(self, nil, arg)
|
||||
return Print(self, frame, select(2,...))
|
||||
else
|
||||
return Print(self, DEFAULT_CHAT_FRAME, arg)
|
||||
return Print(self, DEFAULT_CHAT_FRAME, ...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Formatted (using format()) print to DEFAULT_CHAT_FRAME or given ChatFrame (anything with an .AddMessage function)
|
||||
-- @paramsig [chatframe ,] "format"[, ...]
|
||||
-- @param chatframe Custom ChatFrame to print to (or any frame with an .AddMessage function)
|
||||
-- @param format Format string - same syntax as standard Lua format()
|
||||
-- @param ... Arguments to the format string
|
||||
function AceConsole:Printf(a1, ...)
|
||||
local frame, succ, s
|
||||
if type(a1) == "table" and a1.AddMessage then -- Is first argument something with an .AddMessage member?
|
||||
frame, succ, s = a1, pcall(format, unpack(arg))
|
||||
function AceConsole:Printf(...)
|
||||
local frame = ...
|
||||
if type(frame) == "table" and frame.AddMessage then -- Is first argument something with an .AddMessage member?
|
||||
return Print(self, frame, format(select(2,...)))
|
||||
else
|
||||
frame, succ, s = DEFAULT_CHAT_FRAME, pcall(format, a1, unpack(arg))
|
||||
return Print(self, DEFAULT_CHAT_FRAME, format(...))
|
||||
end
|
||||
if not succ then error(s,2) end
|
||||
return Print(self, frame, s)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
--- Register a simple chat command
|
||||
-- @param command Chat command to be registered WITHOUT leading "/"
|
||||
-- @param func Function to call when the slash command is being used (funcref or methodname)
|
||||
-- @param persist if false, the command will be soft disabled/enabled when aceconsole is used as a mixin (default: true)
|
||||
function AceConsole:RegisterChatCommand( command, func, persist )
|
||||
if type(command)~="string" then error([[Usage: AceConsole:RegisterChatCommand(command, func[, persist ]): 'command' - expected a string]], 2) end
|
||||
if type(command)~="string" then error([[Usage: AceConsole:RegisterChatCommand( "command", func[, persist ]): 'command' - expected a string]], 2) end
|
||||
|
||||
if persist==nil then persist=true end -- I'd rather have my addon's "/addon enable" around if the author screws up. Having some extra slash regged when it shouldnt be isn't as destructive. True is a better default. /Mikk
|
||||
|
||||
local name = "ACECONSOLE_"..strupper(command)
|
||||
local name = "ACECONSOLE_"..command:upper()
|
||||
|
||||
local t = type(func)
|
||||
|
||||
if t == "string" then
|
||||
-- Ace3v: prevent user from using AceConSole as self
|
||||
if self == AceConsole then
|
||||
error([[Usage: RegisterChatCommand(command, func[, persist]): 'self' - use your own 'self']], 2)
|
||||
end
|
||||
if type( func ) == "string" then
|
||||
SlashCmdList[name] = function(input, editBox)
|
||||
self[func](self, input, editBox)
|
||||
end
|
||||
elseif t == "function" then
|
||||
SlashCmdList[name] = func
|
||||
else
|
||||
error([[Usage: AceConsole:RegisterChatCommand(command, func[, persist ]): 'func' - expected a string or a function]], 2)
|
||||
SlashCmdList[name] = func
|
||||
end
|
||||
_G["SLASH_"..name.."1"] = "/"..strlower(command)
|
||||
_G["SLASH_"..name.."1"] = "/"..command:lower()
|
||||
AceConsole.commands[command] = name
|
||||
-- non-persisting commands are registered for enabling disabling
|
||||
if not persist then
|
||||
@@ -136,6 +113,7 @@ function AceConsole:UnregisterChatCommand( command )
|
||||
if name then
|
||||
SlashCmdList[name] = nil
|
||||
_G["SLASH_" .. name .. "1"] = nil
|
||||
hash_SlashCmdList["/" .. command:upper()] = nil
|
||||
AceConsole.commands[command] = nil
|
||||
end
|
||||
end
|
||||
@@ -144,16 +122,18 @@ end
|
||||
-- @return Iterator (pairs) over all commands
|
||||
function AceConsole:IterateChatCommands() return pairs(AceConsole.commands) end
|
||||
|
||||
local function nils(n,argc,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
|
||||
if n >= 1 then
|
||||
return nil, nils(n-1,argc,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
|
||||
elseif not argc or argc == 0 then
|
||||
return
|
||||
|
||||
local function nils(n, ...)
|
||||
if n>1 then
|
||||
return nil, nils(n-1, ...)
|
||||
elseif n==1 then
|
||||
return nil, ...
|
||||
else
|
||||
return a1, nils(0,argc-1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
|
||||
return ...
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Retreive one or more space-separated arguments from a string.
|
||||
-- Treats quoted strings and itemlinks as non-spaced.
|
||||
-- @param str The raw argument string
|
||||
@@ -170,7 +150,7 @@ function AceConsole:GetArgs(str, numargs, startpos)
|
||||
-- find start of new arg
|
||||
pos = strfind(str, "[^ ]", pos)
|
||||
if not pos then -- whoops, end of string
|
||||
return nils(numargs, 1, 1e9)
|
||||
return nils(numargs, 1e9)
|
||||
end
|
||||
|
||||
if numargs<1 then
|
||||
@@ -225,7 +205,7 @@ function AceConsole:GetArgs(str, numargs, startpos)
|
||||
end
|
||||
|
||||
-- search aborted, we hit end of string. return it all as one argument. (yes, even if it's an unterminated quote or hyperlink)
|
||||
return strsub(str, startpos), nils(numargs-1, 1, 1e9)
|
||||
return strsub(str, startpos), nils(numargs-1, 1e9)
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="AceConsole-3.0.lua"/>
|
||||
</Ui>
|
||||
@@ -1,204 +0,0 @@
|
||||
local ACECORE_MAJOR, ACECORE_MINOR = "AceCore-3.0", 1
|
||||
local AceCore, oldminor = LibStub:NewLibrary(ACECORE_MAJOR, ACECORE_MINOR)
|
||||
|
||||
if not AceCore then return end -- No upgrade needed
|
||||
|
||||
AceCore._G = AceCore._G or _G
|
||||
local _G = AceCore._G
|
||||
local strsub, strgsub, strfind = string.sub, string.gsub, string.find
|
||||
local tremove, tconcat = table.remove, table.concat
|
||||
local tgetn, tsetn = table.getn, table.setn
|
||||
|
||||
local new, del
|
||||
do
|
||||
local list = setmetatable({}, {__mode = "k"})
|
||||
function new()
|
||||
local t = next(list)
|
||||
if not t then
|
||||
return {}
|
||||
end
|
||||
list[t] = nil
|
||||
return t
|
||||
end
|
||||
|
||||
function del(t)
|
||||
setmetatable(t, nil)
|
||||
for k in pairs(t) do
|
||||
t[k] = nil
|
||||
end
|
||||
tsetn(t,0)
|
||||
list[t] = true
|
||||
end
|
||||
|
||||
-- debug
|
||||
function AceCore.listcount()
|
||||
local count = 0
|
||||
for k in list do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
end -- AceCore.new, AceCore.del
|
||||
AceCore.new, AceCore.del = new, del
|
||||
|
||||
local function errorhandler(err)
|
||||
return geterrorhandler()(err)
|
||||
end
|
||||
AceCore.errorhandler = errorhandler
|
||||
|
||||
local function CreateSafeDispatcher(argCount)
|
||||
local code = [[
|
||||
local errorhandler = LibStub("AceCore-3.0").errorhandler
|
||||
local method, UP_ARGS
|
||||
local function call()
|
||||
local func, ARGS = method, UP_ARGS
|
||||
method, UP_ARGS = nil, NILS
|
||||
return func(ARGS)
|
||||
end
|
||||
return function(func, ARGS)
|
||||
method, UP_ARGS = func, ARGS
|
||||
return xpcall(call, errorhandler)
|
||||
end
|
||||
]]
|
||||
local c = 4*argCount-1
|
||||
local s = "b01,b02,b03,b04,b05,b06,b07,b08,b09,b10,b11,b12,b13,b14,b15,b16,b17,b18,b19,b20"
|
||||
code = strgsub(code, "UP_ARGS", string.sub(s,1,c))
|
||||
s = "a01,a02,a03,a04,a05,a06,a07,a08,a09,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20"
|
||||
code = strgsub(code, "ARGS", string.sub(s,1,c))
|
||||
s = "nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil"
|
||||
code = strgsub(code, "NILS", string.sub(s,1,c))
|
||||
return assert(loadstring(code, "safecall SafeDispatcher["..tostring(argCount).."]"))()
|
||||
end
|
||||
|
||||
local SafeDispatchers = setmetatable({}, {__index=function(self, argCount)
|
||||
local dispatcher
|
||||
if argCount > 0 then
|
||||
dispatcher = CreateSafeDispatcher(argCount)
|
||||
else
|
||||
dispatcher = function(func) return xpcall(func,errorhandler) end
|
||||
end
|
||||
rawset(self, argCount, dispatcher)
|
||||
return dispatcher
|
||||
end})
|
||||
|
||||
local function safecall(func,argc,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
|
||||
-- we check to see if the func is passed is actually a function here and don't error when it isn't
|
||||
-- this safecall is used for optional functions like OnInitialize OnEnable etc. When they are not
|
||||
-- present execution should continue without hinderance
|
||||
if type(func) == "function" then
|
||||
return SafeDispatchers[argc](func,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)
|
||||
end
|
||||
end
|
||||
AceCore.safecall = safecall
|
||||
|
||||
local function CreateDispatcher(argCount)
|
||||
local code = [[
|
||||
return function(func,ARGS)
|
||||
return func(ARGS)
|
||||
end
|
||||
]]
|
||||
local s = "a01,a02,a03,a04,a05,a06,a07,a08,a09,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20"
|
||||
code = strgsub(code, "ARGS", string.sub(s,1,4*argCount-1))
|
||||
return assert(loadstring(code, "call Dispatcher["..tostring(argCount).."]"))()
|
||||
end
|
||||
|
||||
AceCore.Dispatchers = setmetatable({}, {__index=function(self, argCount)
|
||||
local dispatcher
|
||||
if argCount > 0 then
|
||||
dispatcher = CreateDispatcher(argCount)
|
||||
else
|
||||
dispatcher = function(func) return func() end
|
||||
end
|
||||
rawset(self, argCount, dispatcher)
|
||||
return dispatcher
|
||||
end})
|
||||
|
||||
-- some string functions
|
||||
-- vanilla available string operations:
|
||||
-- sub, gfind, rep, gsub, char, dump, find, upper, len, format, byte, lower
|
||||
-- we will just replace every string.match with string.find in the code
|
||||
function AceCore.strtrim(s)
|
||||
return strgsub(s, "^%s*(.-)%s*$", "%1")
|
||||
end
|
||||
|
||||
local function strsplit(delim, s, n)
|
||||
if n and n < 2 then return s end
|
||||
local beg = beg or 1
|
||||
local i,j = string.find(s,delim,beg)
|
||||
if not i then
|
||||
return s, nil
|
||||
end
|
||||
return string.sub(s,1,j-1), strsplit(delim, string.sub(s,j+1), n and n-1 or nil)
|
||||
end
|
||||
AceCore.strsplit = strsplit
|
||||
|
||||
-- Ace3v: fonctions copied from AceHook-2.1
|
||||
local protFuncs = {
|
||||
CameraOrSelectOrMoveStart = true, CameraOrSelectOrMoveStop = true,
|
||||
TurnOrActionStart = true, TurnOrActionStop = true,
|
||||
PitchUpStart = true, PitchUpStop = true,
|
||||
PitchDownStart = true, PitchDownStop = true,
|
||||
MoveBackwardStart = true, MoveBackwardStop = true,
|
||||
MoveForwardStart = true, MoveForwardStop = true,
|
||||
Jump = true, StrafeLeftStart = true,
|
||||
StrafeLeftStop = true, StrafeRightStart = true,
|
||||
StrafeRightStop = true, ToggleMouseMove = true,
|
||||
ToggleRun = true, TurnLeftStart = true,
|
||||
TurnLeftStop = true, TurnRightStart = true,
|
||||
TurnRightStop = true,
|
||||
}
|
||||
|
||||
local function issecurevariable(x)
|
||||
return protFuncs[x] and 1 or nil
|
||||
end
|
||||
AceCore.issecurevariable = issecurevariable
|
||||
|
||||
local function hooksecurefunc(arg1, arg2, arg3)
|
||||
if type(arg1) == "string" then
|
||||
arg1, arg2, arg3 = _G, arg1, arg2
|
||||
end
|
||||
local orig = arg1[arg2]
|
||||
if type(orig) ~= "function" then
|
||||
error("The function "..arg2.." does not exist", 2)
|
||||
end
|
||||
arg1[arg2] = function(...)
|
||||
local tmp = {orig(unpack(arg))}
|
||||
arg3(unpack(arg))
|
||||
return unpack(tmp)
|
||||
end
|
||||
end
|
||||
AceCore.hooksecurefunc = hooksecurefunc
|
||||
|
||||
-- pickfirstset() - picks the first non-nil value and returns it
|
||||
local function pickfirstset(argc,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
|
||||
if (argc <= 1) or (a1 ~= nil) then
|
||||
return a1
|
||||
else
|
||||
return pickfirstset(argc-1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
|
||||
end
|
||||
end
|
||||
AceCore.pickfirstset = pickfirstset
|
||||
|
||||
local function countargs(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
|
||||
if (a1 == nil) then return 0 end
|
||||
return 1 + countargs(a2,a3,a4,a5,a6,a7,a8,a9,a10)
|
||||
end
|
||||
AceCore.countargs = countargs
|
||||
|
||||
-- wipe preserves metatable
|
||||
function AceCore.wipe(t)
|
||||
for k,v in pairs(t) do t[k] = nil end
|
||||
tsetn(t,0)
|
||||
return t
|
||||
end
|
||||
|
||||
function AceCore.truncate(t,e)
|
||||
e = e or tgetn(t)
|
||||
for i=1,e do
|
||||
if t[i] == nil then
|
||||
tsetn(t,i-1)
|
||||
return
|
||||
end
|
||||
end
|
||||
tsetn(t,e)
|
||||
end
|
||||
@@ -1,3 +0,0 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
||||
<Script file="AceCore-3.0.lua"/>
|
||||
</Ui>
|
||||
@@ -40,22 +40,18 @@
|
||||
-- end
|
||||
-- @class file
|
||||
-- @name AceDB-3.0.lua
|
||||
-- @release $Id: AceDB-3.0.lua 1142 2016-07-11 08:36:19Z nevcairiel $
|
||||
-- @release $Id: AceDB-3.0.lua 1124 2014-10-27 21:00:07Z funkydude $
|
||||
local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 26
|
||||
local AceDB, oldminor = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR)
|
||||
|
||||
if not AceDB then return end -- No upgrade needed
|
||||
|
||||
local AceCore = LibStub("AceCore-3.0")
|
||||
local new, del = AceCore.new, AceCore.del
|
||||
|
||||
-- Lua APIs
|
||||
local type, pairs, next, error = type, pairs, next, error
|
||||
local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget
|
||||
local format, lower = string.format, string.lower
|
||||
|
||||
-- WoW APIs
|
||||
local _G = AceCore._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
|
||||
@@ -69,13 +65,6 @@ local CallbackDummy = { Fire = function() end }
|
||||
|
||||
local DBObjectLib = {}
|
||||
|
||||
-- Ace3v: for all recursive functions we must declare the iterator as local again, this step is necessary in vanilla
|
||||
-- Example:
|
||||
-- for k,v in pairs(table) do
|
||||
-- local v = v
|
||||
-- ...
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
AceDB Utility Functions
|
||||
---------------------------------------------------------------------------]]
|
||||
@@ -85,7 +74,6 @@ local function copyTable(src, dest)
|
||||
if type(dest) ~= "table" then dest = {} end
|
||||
if type(src) == "table" then
|
||||
for k,v in pairs(src) do
|
||||
local v = v
|
||||
if type(v) == "table" then
|
||||
-- try to index the key first so that the metatable creates the defaults, if set, and use that table
|
||||
v = copyTable(v, dest[k])
|
||||
@@ -105,7 +93,6 @@ local function copyDefaults(dest, src)
|
||||
-- this happens if some value in the SV overwrites our default value with a non-table
|
||||
--if type(dest) ~= "table" then return end
|
||||
for k, v in pairs(src) do
|
||||
local v = v
|
||||
if k == "*" or k == "**" then
|
||||
if type(v) == "table" then
|
||||
-- This is a metatable used for table defaults
|
||||
@@ -153,12 +140,10 @@ local function removeDefaults(db, defaults, blocker)
|
||||
setmetatable(db, nil)
|
||||
-- loop through the defaults and remove their content
|
||||
for k,v in pairs(defaults) do
|
||||
local v = v
|
||||
if k == "*" or k == "**" then
|
||||
if type(v) == "table" then
|
||||
-- Loop through all the actual k,v pairs and remove
|
||||
for key, value in pairs(db) do
|
||||
local value = value
|
||||
if type(value) == "table" then
|
||||
-- if the key was not explicitly specified in the defaults table, just strip everything from * and ** tables
|
||||
if defaults[key] == nil and (not blocker or blocker[key] == nil) then
|
||||
@@ -220,35 +205,36 @@ end
|
||||
-- Metatable to handle the dynamic creation of sections and copying of sections.
|
||||
local dbmt = {
|
||||
__index = function(t, section)
|
||||
local keys = rawget(t, "keys")
|
||||
local key = keys[section]
|
||||
if key then
|
||||
local defaultTbl = rawget(t, "defaults")
|
||||
local defaults = defaultTbl and defaultTbl[section]
|
||||
local keys = rawget(t, "keys")
|
||||
local key = keys[section]
|
||||
if key then
|
||||
local defaultTbl = rawget(t, "defaults")
|
||||
local defaults = defaultTbl and defaultTbl[section]
|
||||
|
||||
if section == "profile" then
|
||||
if initSection(t, section, "profiles", key, defaults) then
|
||||
-- Callback: OnNewProfile, database, newProfileKey
|
||||
t.callbacks:Fire("OnNewProfile", 2, t, key)
|
||||
if section == "profile" then
|
||||
local new = initSection(t, section, "profiles", key, defaults)
|
||||
if new then
|
||||
-- Callback: OnNewProfile, database, newProfileKey
|
||||
t.callbacks:Fire("OnNewProfile", t, key)
|
||||
end
|
||||
elseif section == "profiles" then
|
||||
local sv = rawget(t, "sv")
|
||||
if not sv.profiles then sv.profiles = {} end
|
||||
rawset(t, "profiles", sv.profiles)
|
||||
elseif section == "global" then
|
||||
local sv = rawget(t, "sv")
|
||||
if not sv.global then sv.global = {} end
|
||||
if defaults then
|
||||
copyDefaults(sv.global, defaults)
|
||||
end
|
||||
rawset(t, section, sv.global)
|
||||
else
|
||||
initSection(t, section, section, key, defaults)
|
||||
end
|
||||
elseif section == "profiles" then
|
||||
local sv = rawget(t, "sv")
|
||||
if not sv.profiles then sv.profiles = {} end
|
||||
rawset(t, "profiles", sv.profiles)
|
||||
elseif section == "global" then
|
||||
local sv = rawget(t, "sv")
|
||||
if not sv.global then sv.global = {} end
|
||||
if defaults then
|
||||
copyDefaults(sv.global, defaults)
|
||||
end
|
||||
rawset(t, section, sv.global)
|
||||
else
|
||||
initSection(t, section, section, key, defaults)
|
||||
end
|
||||
end
|
||||
|
||||
return rawget(t, section)
|
||||
end
|
||||
return rawget(t, section)
|
||||
end
|
||||
}
|
||||
|
||||
local function validateDefaults(defaults, keyTbl, offset)
|
||||
@@ -256,7 +242,7 @@ local function validateDefaults(defaults, keyTbl, offset)
|
||||
offset = offset or 0
|
||||
for k in pairs(defaults) do
|
||||
if not keyTbl[k] or k == "profiles" then
|
||||
error(format("Usage: AceDBObject:RegisterDefaults(defaults): '%s' is not a valid datatype.", k), 3 + offset)
|
||||
error(("Usage: AceDBObject:RegisterDefaults(defaults): '%s' is not a valid datatype."):format(k), 3 + offset)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -273,10 +259,10 @@ local realmKey = GetRealmName()
|
||||
local charKey = UnitName("player") .. " - " .. realmKey
|
||||
local _, classKey = UnitClass("player")
|
||||
local _, raceKey = UnitRace("player")
|
||||
local _, factionKey = UnitFactionGroup("player")
|
||||
-- Ace3v: the faction key may error when in GM mode
|
||||
factionKey = factionKey or "Others"
|
||||
local localeKey = lower(GetLocale())
|
||||
local factionKey = UnitFactionGroup("player")
|
||||
local factionrealmKey = factionKey .. " - " .. realmKey
|
||||
local factionrealmregionKey = factionrealmKey .. " - " .. string.sub(GetCVar("realmList"), 1, 2):upper()
|
||||
local localeKey = GetLocale():lower()
|
||||
|
||||
-- Actual database initialization function
|
||||
local function initdb(sv, defaults, defaultProfile, olddb, parent)
|
||||
@@ -312,7 +298,8 @@ local function initdb(sv, defaults, defaultProfile, olddb, parent)
|
||||
["class"] = classKey,
|
||||
["race"] = raceKey,
|
||||
["faction"] = factionKey,
|
||||
["factionrealm"] = factionKey .. " - " .. realmKey,
|
||||
["factionrealm"] = factionrealmKey,
|
||||
["factionrealmregion"] = factionrealmregionKey,
|
||||
["profile"] = profileKey,
|
||||
["locale"] = localeKey,
|
||||
["global"] = true,
|
||||
@@ -366,10 +353,10 @@ end
|
||||
-- handle PLAYER_LOGOUT
|
||||
-- strip all defaults from all databases
|
||||
-- and cleans up empty sections
|
||||
local function logoutHandler()
|
||||
local function logoutHandler(frame, event)
|
||||
if event == "PLAYER_LOGOUT" then
|
||||
for db in pairs(AceDB.db_registry) do
|
||||
db.callbacks:Fire("OnDatabaseShutdown", 1, db)
|
||||
db.callbacks:Fire("OnDatabaseShutdown", db)
|
||||
db:RegisterDefaults(nil)
|
||||
|
||||
-- cleanup sections that are empty without defaults
|
||||
@@ -449,7 +436,7 @@ function DBObjectLib:SetProfile(name)
|
||||
local defaults = self.defaults and self.defaults.profile
|
||||
|
||||
-- Callback: OnProfileShutdown, database
|
||||
self.callbacks:Fire("OnProfileShutdown", 1, self)
|
||||
self.callbacks:Fire("OnProfileShutdown", self)
|
||||
|
||||
if oldProfile and defaults then
|
||||
-- Remove the defaults from the old profile
|
||||
@@ -473,7 +460,7 @@ function DBObjectLib:SetProfile(name)
|
||||
end
|
||||
|
||||
-- Callback: OnProfileChanged, database, newProfileKey
|
||||
self.callbacks:Fire("OnProfileChanged", 2, self, name)
|
||||
self.callbacks:Fire("OnProfileChanged", self, name)
|
||||
end
|
||||
|
||||
--- Returns a table with the names of the existing profiles in the database.
|
||||
@@ -549,7 +536,7 @@ function DBObjectLib:DeleteProfile(name, silent)
|
||||
end
|
||||
|
||||
-- Callback: OnProfileDeleted, database, profileKey
|
||||
self.callbacks:Fire("OnProfileDeleted", 2, self, name)
|
||||
self.callbacks:Fire("OnProfileDeleted", self, name)
|
||||
end
|
||||
|
||||
--- Copies a named profile into the current profile, overwriting any conflicting
|
||||
@@ -585,7 +572,7 @@ function DBObjectLib:CopyProfile(name, silent)
|
||||
end
|
||||
|
||||
-- Callback: OnProfileCopied, database, sourceProfileKey
|
||||
self.callbacks:Fire("OnProfileCopied", 2, self, name)
|
||||
self.callbacks:Fire("OnProfileCopied", self, name)
|
||||
end
|
||||
|
||||
--- Resets the current profile to the default values (if specified).
|
||||
@@ -612,7 +599,7 @@ function DBObjectLib:ResetProfile(noChildren, noCallbacks)
|
||||
|
||||
-- Callback: OnProfileReset, database
|
||||
if not noCallbacks then
|
||||
self.callbacks:Fire("OnProfileReset", 2, self, self.keys["profile"])
|
||||
self.callbacks:Fire("OnProfileReset", self)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -643,9 +630,9 @@ function DBObjectLib:ResetDB(defaultProfile)
|
||||
end
|
||||
|
||||
-- Callback: OnDatabaseReset, database
|
||||
self.callbacks:Fire("OnDatabaseReset", 1, self)
|
||||
self.callbacks:Fire("OnDatabaseReset", self)
|
||||
-- Callback: OnProfileChanged, database, profileKey
|
||||
self.callbacks:Fire("OnProfileChanged", 2, self, self.keys["profile"])
|
||||
self.callbacks:Fire("OnProfileChanged", self, self.keys["profile"])
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="AceDB-3.0.lua"/>
|
||||
</Ui>
|
||||
@@ -2,7 +2,7 @@
|
||||
-- All dispatching is done using **CallbackHandler-1.0**. AceEvent is a simple wrapper around
|
||||
-- CallbackHandler, and dispatches all game events or addon message to the registrees.
|
||||
--
|
||||
-- **AceEvent-3.0** can be embeded into your addon, either explicitly by calling AceEvent:Embed(MyAddon) or by
|
||||
-- **AceEvent-3.0** can be embeded into your addon, either explicitly by calling AceEvent:Embed(MyAddon) or by
|
||||
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
|
||||
-- and can be accessed directly, without having to explicitly call AceEvent itself.\\
|
||||
-- It is recommended to embed AceEvent, otherwise you'll have to specify a custom `self` on all calls you
|
||||
@@ -10,7 +10,9 @@
|
||||
-- @class file
|
||||
-- @name AceEvent-3.0
|
||||
-- @release $Id: AceEvent-3.0.lua 975 2010-10-23 11:26:18Z nevcairiel $
|
||||
local MAJOR, MINOR = "AceEvent-3.0", 3
|
||||
local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0")
|
||||
|
||||
local MAJOR, MINOR = "AceEvent-3.0", 4
|
||||
local AceEvent = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not AceEvent then return end
|
||||
@@ -18,29 +20,27 @@ if not AceEvent then return end
|
||||
-- Lua APIs
|
||||
local pairs = pairs
|
||||
|
||||
local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0")
|
||||
|
||||
AceEvent.frame = AceEvent.frame or CreateFrame("Frame", "AceEvent30Frame") -- our event frame
|
||||
AceEvent.embeds = AceEvent.embeds or {} -- what objects embed this lib
|
||||
|
||||
-- APIs and registry for blizzard events, using CallbackHandler lib
|
||||
if not AceEvent.events then
|
||||
AceEvent.events = CallbackHandler:New(AceEvent,
|
||||
AceEvent.events = CallbackHandler:New(AceEvent,
|
||||
"RegisterEvent", "UnregisterEvent", "UnregisterAllEvents")
|
||||
end
|
||||
|
||||
function AceEvent.events:OnUsed(target, eventname)
|
||||
function AceEvent.events:OnUsed(target, eventname)
|
||||
AceEvent.frame:RegisterEvent(eventname)
|
||||
end
|
||||
|
||||
function AceEvent.events:OnUnused(target, eventname)
|
||||
function AceEvent.events:OnUnused(target, eventname)
|
||||
AceEvent.frame:UnregisterEvent(eventname)
|
||||
end
|
||||
|
||||
|
||||
-- APIs and registry for IPC messages, using CallbackHandler lib
|
||||
if not AceEvent.messages then
|
||||
AceEvent.messages = CallbackHandler:New(AceEvent,
|
||||
AceEvent.messages = CallbackHandler:New(AceEvent,
|
||||
"RegisterMessage", "UnregisterMessage", "UnregisterAllMessages"
|
||||
)
|
||||
AceEvent.SendMessage = AceEvent.messages.Fire
|
||||
@@ -115,12 +115,9 @@ function AceEvent:OnEmbedDisable(target)
|
||||
end
|
||||
|
||||
-- Script to fire blizzard events into the event listeners
|
||||
-- Ace3v: in vanilla the arguments are set to global:
|
||||
-- event, arg1, arg2, arg3 ...
|
||||
-- the user have always access to them, so we save the table cost here
|
||||
local events = AceEvent.events
|
||||
AceEvent.frame:SetScript("OnEvent", function()
|
||||
events:Fire(event)
|
||||
AceEvent.frame:SetScript("OnEvent", function(this, event, ...)
|
||||
events:Fire(event, ...)
|
||||
end)
|
||||
|
||||
--- Finally: upgrade our old embeds
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="AceEvent-3.0.lua"/>
|
||||
</Ui>
|
||||
@@ -15,9 +15,6 @@ local AceHook, oldminor = LibStub:NewLibrary(ACEHOOK_MAJOR, ACEHOOK_MINOR)
|
||||
|
||||
if not AceHook then return end -- No upgrade needed
|
||||
|
||||
local AceCore = LibStub("AceCore-3.0")
|
||||
local new, del = AceCore.new, AceCore.del
|
||||
|
||||
AceHook.embeded = AceHook.embeded or {}
|
||||
AceHook.registry = AceHook.registry or setmetatable({}, {__index = function(tbl, key) tbl[key] = {} return tbl[key] end })
|
||||
AceHook.handlers = AceHook.handlers or {}
|
||||
@@ -39,9 +36,11 @@ local format = string.format
|
||||
local assert, error = assert, error
|
||||
|
||||
-- WoW APIs
|
||||
local hooksecurefunc, issecurevariable = AceCore.hooksecurefunc, AceCore.issecurevariable
|
||||
local issecurevariable, hooksecurefunc = issecurevariable, hooksecurefunc
|
||||
local _G = _G
|
||||
|
||||
local _G = AceCore._G
|
||||
-- functions for later definition
|
||||
local donothing, createHook, hook
|
||||
|
||||
local protectedScripts = {
|
||||
OnClick = true,
|
||||
@@ -80,25 +79,20 @@ function AceHook:OnEmbedDisable( target )
|
||||
target:UnhookAll()
|
||||
end
|
||||
|
||||
-- failsafe means even the hooking method fails, the original method will
|
||||
-- always be called
|
||||
local function createHook(self, handler, orig, secure, failsafe)
|
||||
function createHook(self, handler, orig, secure, failsafe)
|
||||
local uid
|
||||
local method = type(handler) == "string"
|
||||
-- Ace3v: when this function called in the "hook" function, we have
|
||||
-- failsafe = not (raw or secure), so the first check condition
|
||||
-- is same as "if failsafe" or "if not raw and not secure"
|
||||
if failsafe and not secure then
|
||||
-- failsafe hook creation
|
||||
uid = function(...)
|
||||
if actives[uid] then
|
||||
if method then
|
||||
self[handler](self,unpack(arg))
|
||||
self[handler](self, ...)
|
||||
else
|
||||
handler(unpack(arg))
|
||||
handler(...)
|
||||
end
|
||||
end
|
||||
return orig(unpack(arg))
|
||||
return orig(...)
|
||||
end
|
||||
-- /failsafe hook
|
||||
else
|
||||
@@ -106,12 +100,12 @@ local function createHook(self, handler, orig, secure, failsafe)
|
||||
uid = function(...)
|
||||
if actives[uid] then
|
||||
if method then
|
||||
return self[handler](self,unpack(arg))
|
||||
return self[handler](self, ...)
|
||||
else
|
||||
return handler(unpack(arg))
|
||||
return handler(...)
|
||||
end
|
||||
elseif not secure then -- backup on non secure
|
||||
return orig(unpack(arg))
|
||||
return orig(...)
|
||||
end
|
||||
end
|
||||
-- /hook
|
||||
@@ -119,18 +113,9 @@ local function createHook(self, handler, orig, secure, failsafe)
|
||||
return uid
|
||||
end
|
||||
|
||||
local function donothing() end
|
||||
function donothing() end
|
||||
|
||||
-- @param self
|
||||
-- @param obj nil or a frame object, use with script = true
|
||||
-- @param method string, the name of a global function or the name of an object method
|
||||
-- @param handler function, or string when it's a method of 'self', if nil then will use the same value as method
|
||||
-- @param script boolean, must be true, if the hooked object is a frame
|
||||
-- @param secure boolean, if hooking a secure script, if true the original script will be called first, else later
|
||||
-- @param raw boolean, if raw hooking (replacing the original method)
|
||||
-- @param forceSecure boolean, if forcing to hook a secure method
|
||||
-- @param usage
|
||||
local function hook(self, obj, method, handler, script, secure, raw, forceSecure, usage)
|
||||
function hook(self, obj, method, handler, script, secure, raw, forceSecure, usage)
|
||||
if not handler then handler = method end
|
||||
|
||||
-- These asserts make sure AceHooks's devs play by the rules.
|
||||
@@ -215,7 +200,6 @@ local function hook(self, obj, method, handler, script, secure, raw, forceSecure
|
||||
|
||||
local orig
|
||||
if script then
|
||||
-- Sometimes there is not a original function for a script.
|
||||
orig = obj:GetScript(method) or donothing
|
||||
elseif obj then
|
||||
orig = obj[method]
|
||||
@@ -230,11 +214,11 @@ local function hook(self, obj, method, handler, script, secure, raw, forceSecure
|
||||
uid = createHook(self, handler, orig, secure, not (raw or secure))
|
||||
|
||||
if obj then
|
||||
registry[self][obj] = registry[self][obj] or new()
|
||||
self.hooks[obj] = self.hooks[obj] or {}
|
||||
registry[self][obj] = registry[self][obj] or {}
|
||||
registry[self][obj][method] = uid
|
||||
|
||||
if not secure then
|
||||
self.hooks[obj] = self.hooks[obj] or new()
|
||||
self.hooks[obj][method] = orig
|
||||
end
|
||||
|
||||
@@ -242,12 +226,7 @@ local function hook(self, obj, method, handler, script, secure, raw, forceSecure
|
||||
if not secure then
|
||||
obj:SetScript(method, uid)
|
||||
else
|
||||
obj:SetScript(method, function(...)
|
||||
local tmp = {orig(unpack(arg))}
|
||||
uid(unpack(arg))
|
||||
return unpack(tmp)
|
||||
end)
|
||||
--obj:HookScript(method, uid) -- Ace3v: vanilla frame has no HookScript
|
||||
obj:HookScript2(method, uid)
|
||||
end
|
||||
else
|
||||
if not secure then
|
||||
@@ -464,12 +443,8 @@ function AceHook:Unhook(obj, method)
|
||||
actives[uid], handlers[uid] = nil, nil
|
||||
|
||||
if obj then
|
||||
local tmp = registry[self][obj]
|
||||
tmp[method] = nil
|
||||
if not next(tmp) then
|
||||
del(tmp)
|
||||
registry[self][obj] = nil
|
||||
end
|
||||
registry[self][obj][method] = nil
|
||||
registry[self][obj] = next(registry[self][obj]) and registry[self][obj] or nil
|
||||
|
||||
-- if the hook reference doesnt exist, then its a secure hook, just bail out and dont do any unhooking
|
||||
if not self.hooks[obj] or not self.hooks[obj][method] then return true end
|
||||
@@ -481,12 +456,8 @@ function AceHook:Unhook(obj, method)
|
||||
obj[method] = self.hooks[obj][method]
|
||||
end
|
||||
|
||||
tmp = self.hooks[obj]
|
||||
tmp[method] = nil
|
||||
if not next(tmp) then
|
||||
del(tmp)
|
||||
self.hooks[obj] = nil
|
||||
end
|
||||
self.hooks[obj][method] = nil
|
||||
self.hooks[obj] = next(self.hooks[obj]) and self.hooks[obj] or nil
|
||||
else
|
||||
registry[self][method] = nil
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="AceHook-3.0.lua"/>
|
||||
</Ui>
|
||||
@@ -97,7 +97,7 @@ function AceLocale:NewLocale(application, locale, isDefault, silent)
|
||||
|
||||
local app = AceLocale.apps[application]
|
||||
|
||||
if silent and app and getmetatable(app) ~= readmetasilent then
|
||||
if silent and app and getmetatable(app) ~= readmetasilent and not isDefault then
|
||||
geterrorhandler()("Usage: NewLocale(application, locale[, isDefault[, silent]]): 'silent' must be specified for the first locale registered")
|
||||
end
|
||||
|
||||
@@ -112,7 +112,7 @@ function AceLocale:NewLocale(application, locale, isDefault, silent)
|
||||
end
|
||||
|
||||
if locale ~= gameLocale and not isDefault then
|
||||
return nil -- nop, we don't need these translations
|
||||
return -- nop, we don't need these translations
|
||||
end
|
||||
|
||||
registering = app -- remember globally for writeproxy and writedefaultproxy
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="AceLocale-3.0.lua"/>
|
||||
</Ui>
|
||||
@@ -20,7 +20,7 @@ if not AceSerializer then return end
|
||||
local strbyte, strchar, gsub, gmatch, format = string.byte, string.char, string.gsub, string.gmatch, string.format
|
||||
local assert, error, pcall = assert, error, pcall
|
||||
local type, tostring, tonumber = type, tostring, tonumber
|
||||
local pairs, frexp = pairs, math.frexp
|
||||
local pairs, select, frexp = pairs, select, math.frexp
|
||||
local tconcat = table.concat
|
||||
|
||||
-- quick copies of string representations of wonky numbers
|
||||
@@ -116,8 +116,9 @@ local serializeTbl = { "^1" } -- "^1" = Hi, I'm data serialized by AceSerializer
|
||||
function AceSerializer:Serialize(...)
|
||||
local nres = 1
|
||||
|
||||
for i = 1, arg.n do
|
||||
nres = SerializeValue(arg[i], serializeTbl, nres)
|
||||
for i=1,select("#", ...) do
|
||||
local v = select(i, ...)
|
||||
nres = SerializeValue(v, serializeTbl, nres)
|
||||
end
|
||||
|
||||
serializeTbl[nres+1] = "^^" -- "^^" = End of serialized data
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="AceSerializer-3.0.lua"/>
|
||||
</Ui>
|
||||
@@ -1,12 +1,12 @@
|
||||
--- **AceTimer-3.0** provides a central facility for registering timers.
|
||||
-- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient
|
||||
-- data structure that allows easy dispatching and fast rescheduling. Timers can be registered
|
||||
-- data structure that allows easy dispatching and fast rescheduling. Timers can be registered, rescheduled
|
||||
-- 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.01s as this is what the WoW timer API
|
||||
-- restricts us to.
|
||||
-- AceTimer is currently limited to firing timers at a frequency of 0.1s. This constant may change
|
||||
-- in the future, but for now it seemed like a good compromise in efficiency and accuracy.
|
||||
--
|
||||
-- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you
|
||||
-- need to cancel the timer you just registered.
|
||||
-- need to cancel or reschedule the timer you just registered.
|
||||
--
|
||||
-- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by
|
||||
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
|
||||
@@ -15,33 +15,54 @@
|
||||
-- make into AceTimer.
|
||||
-- @class file
|
||||
-- @name AceTimer-3.0
|
||||
-- @release $Id: AceTimer-3.0.lua 1119 2014-10-14 17:23:29Z nevcairiel $
|
||||
-- @release $Id: AceTimer-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $
|
||||
|
||||
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", 6
|
||||
local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not AceTimer then return end -- No upgrade needed
|
||||
|
||||
local AceCore = LibStub("AceCore-3.0")
|
||||
local safecall = AceCore.safecall
|
||||
|
||||
AceTimer.counter = AceTimer.counter or {}
|
||||
AceTimer.hash = AceTimer.hash or {} -- Array of [1..BUCKETS] = linked list of timers (using .next member)
|
||||
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)
|
||||
-- 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")
|
||||
|
||||
local counter = AceTimer.counter
|
||||
local activeTimers = AceTimer.activeTimers -- Upvalue our private data
|
||||
local timerFrame = AceTimer.frame
|
||||
|
||||
-- Lua APIs
|
||||
local type, unpack, next, error = type, unpack, next, error
|
||||
local floor, max, min, mod = math.floor, math.max, math.min, math.mod
|
||||
local tostring = tostring
|
||||
local assert, error, loadstring = assert, error, loadstring
|
||||
local setmetatable, rawset, rawget = setmetatable, rawset, rawget
|
||||
local select, pairs, type, next, tostring = select, pairs, type, next, tostring
|
||||
local floor, max, min = math.floor, math.max, math.min
|
||||
local tconcat = table.concat
|
||||
|
||||
-- WoW APIs
|
||||
local GetTime = GetTime
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: DEFAULT_CHAT_FRAME, geterrorhandler
|
||||
|
||||
-- Simple ONE-SHOT timer cache. Much more efficient than a full compost for our purposes.
|
||||
local timerCache = nil
|
||||
|
||||
--[[
|
||||
Timers will not be fired more often than HZ-1 times per second.
|
||||
Keep at intended speed PLUS ONE or we get bitten by floating point rounding errors (n.5 + 0.1 can be n.599999)
|
||||
@@ -49,7 +70,6 @@ local GetTime = GetTime
|
||||
If this number is ever changed, all entries need to be rehashed on lib upgrade.
|
||||
]]
|
||||
local HZ = 11
|
||||
local minDelay = 1/(HZ-1)
|
||||
|
||||
--[[
|
||||
Prime for good distribution
|
||||
@@ -62,188 +82,347 @@ for i=1,BUCKETS do
|
||||
hash[i] = hash[i] or false -- make it an integer-indexed array; it's faster than hashes
|
||||
end
|
||||
|
||||
local new, del
|
||||
do
|
||||
local list = setmetatable({}, {__mode = "k"})
|
||||
function new(self, loop, func, delay, argc,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
|
||||
local name = loop and "ScheduleRepeatingTimer" or "ScheduleTimer"
|
||||
if self == AceTimer then
|
||||
error(MAJOR..": " .. name .. "(callback, delay, argc, args...): use your own 'self'", 3)
|
||||
end
|
||||
if not func or not delay then
|
||||
error(MAJOR..": " .. name .. "(callback, delay, argc, args...): 'callback' and 'delay' must have set values.", 3)
|
||||
end
|
||||
if argc and (type(argc) ~= "number" or floor(argc) ~= argc) then
|
||||
error(MAJOR..": " .. name .. "(callback, delay, argc, args...): 'argc' must be an integer.", 3)
|
||||
end
|
||||
if type(func) == "string" then
|
||||
if type(self) ~= "table" then
|
||||
error(MAJOR..": " .. name .. "(callback, delay, argc, args...): 'self' - must be a table.", 3)
|
||||
elseif type(self[func]) ~= "function" then
|
||||
error(MAJOR..": " .. name .. "(callback, delay, argc, args...): Tried to register '"..func.."' as the callback, but it is not a method.", 3)
|
||||
--[[
|
||||
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
|
||||
})
|
||||
Dispatchers[0] = function(func)
|
||||
return xpcall(func, errorhandler)
|
||||
end
|
||||
|
||||
local function safecall(func, ...)
|
||||
return Dispatchers[select('#', ...)](func, ...)
|
||||
end
|
||||
|
||||
local lastint = floor(GetTime() * HZ)
|
||||
|
||||
-- --------------------------------------------------------------------
|
||||
-- OnUpdate handler
|
||||
--
|
||||
-- traverse buckets, always chasing "now", and fire timers that have expired
|
||||
|
||||
local function OnUpdate()
|
||||
local now = GetTime()
|
||||
local nowint = floor(now * HZ)
|
||||
|
||||
-- Have we passed into a new hash bucket?
|
||||
if nowint == lastint then return end
|
||||
|
||||
local soon = now + 1 -- +1 is safe as long as 1 < HZ < BUCKETS/2
|
||||
|
||||
-- Pass through each bucket at most once
|
||||
-- Happens on e.g. instance loads, but COULD happen on high local load situations also
|
||||
for curint = (max(lastint, nowint - BUCKETS) + 1), nowint do -- loop until we catch up with "now", usually only 1 iteration
|
||||
local curbucket = (curint % BUCKETS)+1
|
||||
-- Yank the list of timers out of the bucket and empty it. This allows reinsertion in the currently-processed bucket from callbacks.
|
||||
local nexttimer = hash[curbucket]
|
||||
hash[curbucket] = false -- false rather than nil to prevent the array from becoming a hash
|
||||
|
||||
while nexttimer do
|
||||
local timer = nexttimer
|
||||
nexttimer = timer.next
|
||||
local when = timer.when
|
||||
|
||||
if when < soon then
|
||||
-- Call the timer func, either as a method on given object, or a straight function ref
|
||||
local callback = timer.callback
|
||||
if type(callback) == "string" then
|
||||
safecall(timer.object[callback], timer.object, timer.arg)
|
||||
elseif callback then
|
||||
safecall(callback, timer.arg)
|
||||
else
|
||||
-- probably nilled out by CancelTimer
|
||||
timer.delay = nil -- don't reschedule it
|
||||
end
|
||||
|
||||
local delay = timer.delay -- NOW make a local copy, can't do it earlier in case the timer cancelled itself in the callback
|
||||
|
||||
if not delay then
|
||||
-- single-shot timer (or cancelled)
|
||||
AceTimer.selfs[timer.object][tostring(timer)] = nil
|
||||
timerCache = timer
|
||||
else
|
||||
-- repeating timer
|
||||
local newtime = when + delay
|
||||
if newtime < now then -- Keep lag from making us firing a timer unnecessarily. (Note that this still won't catch too-short-delay timers though.)
|
||||
newtime = now + delay
|
||||
end
|
||||
timer.when = newtime
|
||||
|
||||
-- add next timer execution to the correct bucket
|
||||
local bucket = (floor(newtime * HZ) % BUCKETS) + 1
|
||||
timer.next = hash[bucket]
|
||||
hash[bucket] = timer
|
||||
end
|
||||
else -- if when>=soon
|
||||
-- reinsert (yeah, somewhat expensive, but shouldn't be happening too often either due to hash distribution)
|
||||
timer.next = hash[curbucket]
|
||||
hash[curbucket] = timer
|
||||
end -- if 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
|
||||
elseif type(func) ~= "function" then
|
||||
error(MAJOR..": " .. name .. "(callback, delay, argc, args...): Tried to register '"..tostring(func).."' as the callback, but it is not a function.", 3)
|
||||
end
|
||||
|
||||
if delay < minDelay then
|
||||
delay = minDelay
|
||||
if delay < (1 / (HZ - 1)) then
|
||||
delay = 1 / (HZ - 1)
|
||||
end
|
||||
|
||||
-- Create and stuff timer in the correct hash bucket
|
||||
local now = GetTime()
|
||||
|
||||
local timer = next(list) or {}
|
||||
list[timer] = nil
|
||||
local timer = timerCache or {} -- Get new timer object (from cache if available)
|
||||
timerCache = nil
|
||||
|
||||
timer.object = self
|
||||
timer.func = func
|
||||
timer.delay = delay
|
||||
timer.status = loop and "loop" or "once"
|
||||
timer.ends = now + delay
|
||||
timer.argsCount = argc or 0
|
||||
timer[1] = a1
|
||||
timer[2] = a2
|
||||
timer[3] = a3
|
||||
timer[4] = a4
|
||||
timer[5] = a5
|
||||
timer[6] = a6
|
||||
timer[7] = a7
|
||||
timer[8] = a8
|
||||
timer[9] = a9
|
||||
timer[10] = a10
|
||||
timer.callback = callback
|
||||
timer.delay = (repeating and delay)
|
||||
timer.arg = arg
|
||||
timer.when = now + delay
|
||||
|
||||
local bucket = floor(mod((now+delay)*HZ,BUCKETS)) + 1
|
||||
local bucket = (floor((now+delay)*HZ) % BUCKETS) + 1
|
||||
timer.next = hash[bucket]
|
||||
hash[bucket] = timer
|
||||
|
||||
local id = tostring(timer) -- user has only access to the id but not the table itself
|
||||
activeTimers[id] = timer
|
||||
-- Insert timer in our self->handle->timer registry
|
||||
local handle = tostring(timer)
|
||||
|
||||
counter[self] = (counter[self] or 0) + 1
|
||||
|
||||
timerFrame:Show()
|
||||
return id
|
||||
end
|
||||
|
||||
function del(t)
|
||||
local id = tostring(t)
|
||||
activeTimers[id] = nil
|
||||
if not next(activeTimers) then
|
||||
timerFrame:Hide()
|
||||
end
|
||||
local self = t.object
|
||||
for k in pairs(t) do t[k] = nil end
|
||||
list[t] = true
|
||||
if counter[self] then
|
||||
counter[self] = counter[self] - 1
|
||||
else
|
||||
counter[self] = nil
|
||||
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 -- new, del
|
||||
|
||||
--- Schedule a new one-shot timer.
|
||||
-- The timer will fire once in `delay` seconds, unless canceled before.
|
||||
-- @param callback Callback function for the timer pulse (funcref or method name).
|
||||
-- @param delay Delay for the timer, in seconds.
|
||||
-- @param argc The numbers of arguments to be passed to the callback function
|
||||
-- @param a1,...,a10 The arguments
|
||||
-- @param arg An optional argument to be passed to the callback function.
|
||||
-- @usage
|
||||
-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
|
||||
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("TimerTest", "AceTimer-3.0")
|
||||
--
|
||||
-- function MyAddOn:OnEnable()
|
||||
-- function MyAddon:OnEnable()
|
||||
-- self:ScheduleTimer("TimerFeedback", 5)
|
||||
-- end
|
||||
--
|
||||
-- function MyAddOn:TimerFeedback()
|
||||
-- function MyAddon:TimerFeedback()
|
||||
-- print("5 seconds passed")
|
||||
-- end
|
||||
function AceTimer:ScheduleTimer(func, delay, argc, ...)
|
||||
return new(self, nil, func, delay, argc, unpack(arg))
|
||||
function AceTimer:ScheduleTimer(callback, delay, arg)
|
||||
return Reg(self, callback, delay, arg)
|
||||
end
|
||||
|
||||
--- Schedule a repeating timer.
|
||||
-- The timer will fire every `delay` seconds, until canceled.
|
||||
-- @param callback Callback function for the timer pulse (funcref or method name).
|
||||
-- @param delay Delay for the timer, in seconds.
|
||||
-- @param argc The numbers of arguments to be passed to the callback function
|
||||
-- @param a1,...,a10 The arguments
|
||||
-- @param arg An optional argument to be passed to the callback function.
|
||||
-- @usage
|
||||
-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
|
||||
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("TimerTest", "AceTimer-3.0")
|
||||
--
|
||||
-- function MyAddOn:OnEnable()
|
||||
-- function MyAddon:OnEnable()
|
||||
-- self.timerCount = 0
|
||||
-- self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5)
|
||||
-- end
|
||||
--
|
||||
-- function MyAddOn:TimerFeedback()
|
||||
-- function MyAddon:TimerFeedback()
|
||||
-- self.timerCount = self.timerCount + 1
|
||||
-- print(format("%d seconds passed", 5 * self.timerCount))
|
||||
-- print(("%d seconds passed"):format(5 * self.timerCount))
|
||||
-- -- run 30 seconds in total
|
||||
-- if self.timerCount == 6 then
|
||||
-- self:CancelTimer(self.testTimer)
|
||||
-- end
|
||||
-- end
|
||||
function AceTimer:ScheduleRepeatingTimer(func, delay, argc, ...)
|
||||
return new(self, true, func, delay, argc, unpack(arg))
|
||||
function AceTimer:ScheduleRepeatingTimer(callback, delay, arg)
|
||||
return Reg(self, callback, delay, arg, true)
|
||||
end
|
||||
|
||||
--- Cancels a timer with the given id, registered by the same addon object as used for `:ScheduleTimer`
|
||||
-- Both one-shot and repeating timers can be canceled with this function, as long as the `id` is valid
|
||||
--- Cancels a timer with the given handle, registered by the same addon object as used for `:ScheduleTimer`
|
||||
-- Both one-shot and repeating timers can be canceled with this function, as long as the `handle` is valid
|
||||
-- and the timer has not fired yet or was canceled before.
|
||||
-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
|
||||
function AceTimer:CancelTimer(id)
|
||||
local timer = activeTimers[id]
|
||||
|
||||
if not timer then
|
||||
return false
|
||||
-- @param handle The handle of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
|
||||
-- @param silent If true, no error is raised if the timer handle is invalid (expired or already canceled)
|
||||
-- @return True if the timer was successfully cancelled.
|
||||
function AceTimer:CancelTimer(handle, silent)
|
||||
if not handle then return end -- nil handle -> bail out without erroring
|
||||
if type(handle) ~= "string" then
|
||||
error(MAJOR..": CancelTimer(handle): 'handle' - expected a string", 2) -- for now, anyway
|
||||
end
|
||||
local selftimers = AceTimer.selfs[self]
|
||||
local timer = selftimers and selftimers[handle]
|
||||
if silent then
|
||||
if timer then
|
||||
timer.callback = nil -- don't run it again
|
||||
timer.delay = nil -- if this is the currently-executing one: don't even reschedule
|
||||
-- The timer object is removed in the OnUpdate loop
|
||||
end
|
||||
return not not timer -- might return "true" even if we double-cancel. we'll live.
|
||||
else
|
||||
-- Ace3v: the timer will always be collected in the next update but not here
|
||||
-- this is necessary for AceBucket to determinate if the bucket has been unregistered
|
||||
-- in the callback
|
||||
timer.status = nil
|
||||
activeTimers[id] = nil
|
||||
if not timer then
|
||||
geterrorhandler()(MAJOR..": CancelTimer(handle[, silent]): '"..tostring(handle).."' - no such timer registered")
|
||||
return false
|
||||
end
|
||||
if not timer.callback then
|
||||
geterrorhandler()(MAJOR..": CancelTimer(handle[, silent]): '"..tostring(handle).."' - timer already cancelled or expired")
|
||||
return false
|
||||
end
|
||||
timer.callback = nil -- don't run it again
|
||||
timer.delay = nil -- if this is the currently-executing one: don't even reschedule
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
--- Cancels all timers registered to the current addon object ('self')
|
||||
function AceTimer:CancelAllTimers()
|
||||
if type(self) ~= "table" then
|
||||
error(MAJOR..": CancelAllTimers(): 'self' - must be a table",2)
|
||||
if not(type(self) == "string" or type(self) == "table") then
|
||||
error(MAJOR..": CancelAllTimers(): 'self' - must be a string or a table",2)
|
||||
end
|
||||
if self == AceTimer then
|
||||
error(MAJOR..": CancelAllTimers(): supply a meaningful 'self'", 2)
|
||||
end
|
||||
|
||||
for k,v in pairs(activeTimers) do
|
||||
if v.object == self then
|
||||
AceTimer.CancelTimer(self, k)
|
||||
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
|
||||
|
||||
--- Returns the time left for a timer with the given id, registered by the current addon object ('self').
|
||||
-- This function will return 0 when the id is invalid.
|
||||
-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
|
||||
-- @return The time left on the timer.
|
||||
function AceTimer:TimeLeft(id)
|
||||
local timer = activeTimers[id]
|
||||
if not timer then
|
||||
return 0
|
||||
else
|
||||
return timer.ends - GetTime()
|
||||
--- Returns the time left for a timer with the given handle, registered by the current addon object ('self').
|
||||
-- This function will raise a warning when the handle is invalid, but not stop execution.
|
||||
-- @param handle The handle of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
|
||||
-- @return The time left on the timer, or false if the handle is invalid.
|
||||
function AceTimer:TimeLeft(handle)
|
||||
if not handle then return end
|
||||
if type(handle) ~= "string" then
|
||||
error(MAJOR..": TimeLeft(handle): 'handle' - expected a string", 2) -- for now, anyway
|
||||
end
|
||||
local selftimers = AceTimer.selfs[self]
|
||||
local timer = selftimers and selftimers[handle]
|
||||
if not timer then
|
||||
geterrorhandler()(MAJOR..": TimeLeft(handle): '"..tostring(handle).."' - no such timer registered")
|
||||
return false
|
||||
end
|
||||
return timer.when - GetTime()
|
||||
end
|
||||
|
||||
function AceTimer:TimerStatus(id)
|
||||
local timer = activeTimers[id]
|
||||
if not timer then
|
||||
return nil
|
||||
else
|
||||
return timer.status
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- PLAYER_REGEN_ENABLED: Run through our .selfs[] array step by step
|
||||
-- and clean it out - otherwise the table indices can grow indefinitely
|
||||
-- if an addon starts and stops a lot of timers. AceBucket does this!
|
||||
--
|
||||
-- See ACE-94 and tests/AceTimer-3.0-ACE-94.lua
|
||||
|
||||
local lastCleaned = nil
|
||||
|
||||
local function OnEvent(this, event)
|
||||
if event~="PLAYER_REGEN_ENABLED" then
|
||||
return
|
||||
end
|
||||
|
||||
-- Get the next 'self' to process
|
||||
local selfs = AceTimer.selfs
|
||||
local self = next(selfs, lastCleaned)
|
||||
if not self then
|
||||
self = next(selfs)
|
||||
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 (worst case!). 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
|
||||
if type(v)=="table" and v.callback then -- if the timer is actually live: count it
|
||||
n=n+1
|
||||
end
|
||||
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
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
@@ -265,115 +444,32 @@ function AceTimer:Embed(target)
|
||||
return target
|
||||
end
|
||||
|
||||
-- AceTimer:OnEmbedDisable(target)
|
||||
-- AceTimer:OnEmbedDisable( target )
|
||||
-- target (object) - target object that AceTimer is embedded in.
|
||||
--
|
||||
-- cancel all timers registered for the object
|
||||
function AceTimer:OnEmbedDisable(target)
|
||||
function AceTimer:OnEmbedDisable( target )
|
||||
target:CancelAllTimers()
|
||||
end
|
||||
|
||||
|
||||
for addon in pairs(AceTimer.embeds) do
|
||||
AceTimer:Embed(addon)
|
||||
end
|
||||
|
||||
-- --------------------------------------------------------------------
|
||||
-- OnUpdate handler
|
||||
--
|
||||
-- traverse buckets, always chasing "now", and fire timers that have expired
|
||||
local lastint = floor(GetTime() * HZ)
|
||||
local function OnUpdate()
|
||||
local now = GetTime()
|
||||
local nowint = floor(now * HZ)
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Debug tools (expose copies of internals to test suites)
|
||||
AceTimer.debug = AceTimer.debug or {}
|
||||
AceTimer.debug.HZ = HZ
|
||||
AceTimer.debug.BUCKETS = BUCKETS
|
||||
|
||||
-- Have we passed into a new hash bucket?
|
||||
if nowint == lastint then return end
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Finishing touchups
|
||||
|
||||
local soon = now + 1 -- +1 is safe as long as 1 < HZ < BUCKETS/2
|
||||
AceTimer.frame:SetScript("OnUpdate", OnUpdate)
|
||||
AceTimer.frame:SetScript("OnEvent", OnEvent)
|
||||
AceTimer.frame:RegisterEvent("PLAYER_REGEN_ENABLED")
|
||||
|
||||
-- Pass through each bucket at most once
|
||||
-- Happens on e.g. instance loads, but COULD happen on high local load situations also
|
||||
for curint = (max(lastint, nowint-BUCKETS) + 1), nowint do -- loop until we catch up with "now", usually only 1 iteratio
|
||||
local curbucket = mod(curint,BUCKETS) + 1 -- Ace3v: both int so no floor here
|
||||
-- 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 status = timer.status
|
||||
if not status then
|
||||
del(timer)
|
||||
else
|
||||
local ends = timer.ends
|
||||
if (status == "loop" or status == "once") and ends < soon then
|
||||
local object = timer.object
|
||||
local callback = timer.func
|
||||
if type(callback) == "string" then
|
||||
callback = (type(object) == "table") and object[callback]
|
||||
if type(callback) == "function" then
|
||||
safecall(callback, timer.argsCount+1, object,
|
||||
timer[1], timer[2], timer[3], timer[4], timer[5],
|
||||
timer[6], timer[7], timer[8], timer[9], timer[10])
|
||||
else
|
||||
status = "once"
|
||||
end
|
||||
elseif type(callback) == "function" then
|
||||
safecall(callback, timer.argsCount,
|
||||
timer[1], timer[2], timer[3], timer[4], timer[5],
|
||||
timer[6], timer[7], timer[8], timer[9], timer[10])
|
||||
else
|
||||
-- probably nilled out by CancelTimer
|
||||
status = "once" -- don't reschedule it
|
||||
end
|
||||
|
||||
if status == "once" then
|
||||
del(timer)
|
||||
else
|
||||
local delay = timer.delay
|
||||
local newends = ends + delay
|
||||
if newends < now then -- Keep lag from making us firing a timer unnecessarily. (Note that this still won't catch too-short-delay timers though.)
|
||||
newends = now + delay
|
||||
end
|
||||
timer.ends = newends
|
||||
-- add next timer execution to the correct bucket
|
||||
local bucket = floor(mod(newends*HZ,BUCKETS)) + 1
|
||||
timer.next = hash[bucket]
|
||||
hash[bucket] = timer
|
||||
end
|
||||
else
|
||||
-- reinsert (yeah, somewhat expensive, but shouldn't be happening too often either due to hash distribution)
|
||||
timer.next = hash[curbucket]
|
||||
hash[curbucket] = timer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
lastint = nowint
|
||||
end
|
||||
|
||||
local lastchecked = nil
|
||||
local function OnEvent()
|
||||
if event ~= "PLAYER_REGEN_ENABLED" then return end
|
||||
|
||||
local addon = next(counter, lastchecked)
|
||||
if not addon then
|
||||
addon = next(counter)
|
||||
end
|
||||
lastchecked = addon
|
||||
if not addon then -- should only happen if counter is empty
|
||||
return
|
||||
end
|
||||
|
||||
local n = counter[addon]
|
||||
if n > BUCKETS then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: The addon/module '"..tostring(addon).."' has "..tostring(n).." live timers. Surely that's not intended?")
|
||||
end
|
||||
end
|
||||
|
||||
timerFrame:SetScript("OnUpdate", OnUpdate)
|
||||
timerFrame:SetScript("OnEvent", OnEvent)
|
||||
timerFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
|
||||
timerFrame:Hide()
|
||||
-- In theory, we could 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 least.
|
||||
@@ -1,3 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="AceTimer-3.0.lua"/>
|
||||
</Ui>
|
||||
@@ -10,7 +10,7 @@ local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end}
|
||||
local tconcat = table.concat
|
||||
local assert, error, loadstring = assert, error, loadstring
|
||||
local setmetatable, rawset, rawget = setmetatable, rawset, rawget
|
||||
local next, pairs, type, tostring = next, pairs, type, tostring
|
||||
local next, select, pairs, type, tostring = next, select, pairs, type, tostring
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
@@ -24,6 +24,8 @@ end
|
||||
|
||||
local function CreateDispatcher(argCount)
|
||||
local code = [[
|
||||
local next, xpcall, eh = ...
|
||||
|
||||
local method, ARGS
|
||||
local function call() method(ARGS) end
|
||||
|
||||
@@ -32,9 +34,9 @@ local function CreateDispatcher(argCount)
|
||||
index, method = next(handlers)
|
||||
if not method then return end
|
||||
local OLD_ARGS = ARGS
|
||||
ARGS = unpack(arg)
|
||||
ARGS = ...
|
||||
repeat
|
||||
xpcall(call, function(err) return geterrorhandler()(err) end)
|
||||
xpcall(call, eh)
|
||||
index, method = next(handlers, index)
|
||||
until not method
|
||||
ARGS = OLD_ARGS
|
||||
@@ -45,7 +47,7 @@ local function CreateDispatcher(argCount)
|
||||
|
||||
local ARGS, OLD_ARGS = {}, {}
|
||||
for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end
|
||||
code = gsub(gsub(code, "OLD_ARGS", tconcat(OLD_ARGS, ", ")), "ARGS", tconcat(ARGS, ", "))
|
||||
code = code:gsub("OLD_ARGS", tconcat(OLD_ARGS, ", ")):gsub("ARGS", tconcat(ARGS, ", "))
|
||||
return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler)
|
||||
end
|
||||
|
||||
@@ -85,7 +87,7 @@ function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAll
|
||||
local oldrecurse = registry.recurse
|
||||
registry.recurse = oldrecurse + 1
|
||||
|
||||
Dispatchers[getn(arg) + 1](events[eventname], eventname, unpack(arg))
|
||||
Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...)
|
||||
|
||||
registry.recurse = oldrecurse
|
||||
|
||||
@@ -136,10 +138,11 @@ function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAll
|
||||
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2)
|
||||
end
|
||||
|
||||
if getn(arg)>=1 then -- this is not the same as testing for arg==nil!
|
||||
regfunc = function(...) self[method](self,arg1,unpack(arg)) end
|
||||
if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
|
||||
local arg=select(1,...)
|
||||
regfunc = function(...) self[method](self,arg,...) end
|
||||
else
|
||||
regfunc = function(...) self[method](self,unpack(arg)) end
|
||||
regfunc = function(...) self[method](self,...) end
|
||||
end
|
||||
else
|
||||
-- function ref with self=object or self="addonId" or self=thread
|
||||
@@ -147,8 +150,9 @@ function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAll
|
||||
error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread expected.", 2)
|
||||
end
|
||||
|
||||
if getn(arg)>=1 then -- this is not the same as testing for arg==nil!
|
||||
regfunc = function(...) method(arg1,unpack(arg)) end
|
||||
if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
|
||||
local arg=select(1,...)
|
||||
regfunc = function(...) method(arg,...) end
|
||||
else
|
||||
regfunc = method
|
||||
end
|
||||
@@ -194,16 +198,16 @@ function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAll
|
||||
-- OPTIONAL: Unregister all callbacks for given selfs/addonIds
|
||||
if UnregisterAllName then
|
||||
target[UnregisterAllName] = function(...)
|
||||
if getn(arg)<1 then
|
||||
if select("#",...)<1 then
|
||||
error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2)
|
||||
end
|
||||
if getn(arg)==1 and arg1==target then
|
||||
if select("#",...)==1 and ...==target then
|
||||
error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2)
|
||||
end
|
||||
|
||||
|
||||
for i=1,getn(arg) do
|
||||
local self = arg[i]
|
||||
for i=1,select("#",...) do
|
||||
local self = select(i,...)
|
||||
if registry.insertQueue then
|
||||
for eventname, callbacks in pairs(registry.insertQueue) do
|
||||
if callbacks[self] then
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info
|
||||
-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
|
||||
-- $Id: LibStub.lua 103 2014-10-16 03:02:50Z mikk $
|
||||
-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/addons/libstub/ for more info
|
||||
-- LibStub is hereby placed in the Public Domain
|
||||
-- Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
|
||||
local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS!
|
||||
local _G = _G
|
||||
local strfind, strfmt = string.find, string.format
|
||||
local LibStub = _G[LIBSTUB_MAJOR]
|
||||
|
||||
-- Check to see is this version of the stub is obsolete
|
||||
if not LibStub or LibStub.minor < LIBSTUB_MINOR then
|
||||
LibStub = LibStub or { libs = {}, minors = {} }
|
||||
LibStub = LibStub or {libs = {}, minors = {} }
|
||||
_G[LIBSTUB_MAJOR] = LibStub
|
||||
LibStub.minor = LIBSTUB_MINOR
|
||||
|
||||
-- LibStub:NewLibrary(major, minor)
|
||||
-- major (string) - the major version of the library
|
||||
-- minor (string or number ) - the minor version of the library
|
||||
--
|
||||
-- returns nil if a newer or same version of the lib is already present
|
||||
-- returns empty library object or old library object if upgrade is needed
|
||||
function LibStub:NewLibrary(major, minor)
|
||||
assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
|
||||
local _,_,num = strfind(minor, "(%d+)")
|
||||
minor = assert(tonumber(num), "Minor version must either be a number or contain a number.")
|
||||
minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.")
|
||||
|
||||
local oldminor = self.minors[major]
|
||||
if oldminor and oldminor >= minor then return nil end
|
||||
@@ -21,13 +27,25 @@ if not LibStub or LibStub.minor < LIBSTUB_MINOR then
|
||||
return self.libs[major], oldminor
|
||||
end
|
||||
|
||||
-- LibStub:GetLibrary(major, [silent])
|
||||
-- major (string) - the major version of the library
|
||||
-- silent (boolean) - if true, library is optional, silently return nil if its not found
|
||||
--
|
||||
-- throws an error if the library can not be found (except silent is set)
|
||||
-- returns the library object if found
|
||||
function LibStub:GetLibrary(major, silent)
|
||||
if not self.libs[major] and not silent then
|
||||
error(strfmt("Cannot find a library instance of %q.", tostring(major)), 2)
|
||||
error(("Cannot find a library instance of %q."):format(tostring(major)), 2)
|
||||
end
|
||||
return self.libs[major], self.minors[major]
|
||||
end
|
||||
|
||||
function LibStub:IterateLibraries() return pairs(self.libs) end
|
||||
-- LibStub:IterateLibraries()
|
||||
--
|
||||
-- Returns an iterator for the currently registered libraries
|
||||
function LibStub:IterateLibraries()
|
||||
return pairs(self.libs)
|
||||
end
|
||||
|
||||
setmetatable(LibStub, { __call = LibStub.GetLibrary })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/">
|
||||
<Include file="LibStub\LibStub.xml"/>
|
||||
<Include file="CallbackHandler-1.0\CallbackHandler-1.0.xml"/>
|
||||
<Include file="AceCore-3.0\AceCore-3.0.xml"/>
|
||||
<Include file="AceAddon-3.0\AceAddon-3.0.xml"/>
|
||||
<Include file="AceEvent-3.0\AceEvent-3.0.xml"/>
|
||||
<Include file="AceConsole-3.0\AceConsole-3.0.xml"/>
|
||||
|
||||
Reference in New Issue
Block a user