chore(libs): sync Ace3 to coa-ace3 (WoWUIDev master @ 52e5f2c)
Bring every embedded Ace3 / CallbackHandler / LibStub copy in line with the canonical Exiles/coa-ace3 bundle so LibStub resolution is predictable across all Exiles forks regardless of which addons are enabled. Libraries updated in this fork: AceAddon-3.0 13 (5 → 13) AceComm-3.0 14 (6 → 14) AceConfig-3.0 3 (2 → 3) AceConfigCmd-3.0 14 (12 → 14) AceConfigDialog-3.0 92 (49 → 92) AceConfigRegistry-3.0 22 (12 → 22) AceConsole-3.0 7 AceDB-3.0 33 (21 → 33) AceDBOptions-3.0 15 (12 → 15) AceEvent-3.0 4 (3 → 4) AceGUI-3.0 41 (33 → 41) AceHook-3.0 9 (5 → 9) AceLocale-3.0 6 (2 → 6) AceTimer-3.0 17 (5 → 17) CallbackHandler-1.0 8 (6 → 8) LibStub 2
This commit is contained in:
@@ -2,14 +2,14 @@
|
||||
-- It'll automatically split the messages into multiple parts and rebuild them on the receiving end.\\
|
||||
-- **ChatThrottleLib** is of course being used to avoid being disconnected by the server.
|
||||
--
|
||||
-- **AceComm-3.0** can be embeded into your addon, either explicitly by calling AceComm:Embed(MyAddon) or by
|
||||
-- **AceComm-3.0** can be embeded into your addon, either explicitly by calling AceComm:Embed(MyAddon) or by
|
||||
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
|
||||
-- and can be accessed directly, without having to explicitly call AceComm itself.\\
|
||||
-- It is recommended to embed AceComm, otherwise you'll have to specify a custom `self` on all calls you
|
||||
-- make into AceComm.
|
||||
-- @class file
|
||||
-- @name AceComm-3.0
|
||||
-- @release $Id: AceComm-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $
|
||||
-- @release $Id$
|
||||
|
||||
--[[ AceComm-3.0
|
||||
|
||||
@@ -17,24 +17,23 @@ TODO: Time out old data rotting around from dead senders? Not a HUGE deal since
|
||||
|
||||
]]
|
||||
|
||||
local MAJOR, MINOR = "AceComm-3.0", 6
|
||||
local CallbackHandler = LibStub("CallbackHandler-1.0")
|
||||
local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib")
|
||||
|
||||
local MAJOR, MINOR = "AceComm-3.0", 14
|
||||
local AceComm,oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not AceComm then return end
|
||||
|
||||
local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0")
|
||||
local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib")
|
||||
|
||||
-- Lua APIs
|
||||
local type, next, pairs, tostring = type, next, pairs, tostring
|
||||
local strsub, strfind = string.sub, string.find
|
||||
local match = string.match
|
||||
local tinsert, tconcat = table.insert, table.concat
|
||||
local error, assert = error, assert
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: LibStub, DEFAULT_CHAT_FRAME, geterrorhandler
|
||||
-- WoW APIs
|
||||
local Ambiguate = Ambiguate
|
||||
|
||||
AceComm.embeds = AceComm.embeds or {}
|
||||
|
||||
@@ -42,21 +41,32 @@ AceComm.embeds = AceComm.embeds or {}
|
||||
local MSG_MULTI_FIRST = "\001"
|
||||
local MSG_MULTI_NEXT = "\002"
|
||||
local MSG_MULTI_LAST = "\003"
|
||||
local MSG_ESCAPE = "\004"
|
||||
|
||||
AceComm.multipart_origprefixes = AceComm.multipart_origprefixes or {} -- e.g. "Prefix\001"="Prefix", "Prefix\002"="Prefix"
|
||||
AceComm.multipart_reassemblers = AceComm.multipart_reassemblers or {} -- e.g. "Prefix\001"="OnReceiveMultipartFirst"
|
||||
-- remove old structures (pre WoW 4.0)
|
||||
AceComm.multipart_origprefixes = nil
|
||||
AceComm.multipart_reassemblers = nil
|
||||
|
||||
-- the multipart message spool: indexed by a combination of sender+distribution+
|
||||
AceComm.multipart_spool = AceComm.multipart_spool or {}
|
||||
AceComm.multipart_spool = AceComm.multipart_spool or {}
|
||||
|
||||
--- Register for Addon Traffic on a specified prefix
|
||||
-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent)
|
||||
-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent), max 16 characters
|
||||
-- @param method Callback to call on message reception: Function reference, or method name (string) to call on self. Defaults to "OnCommReceived"
|
||||
function AceComm:RegisterComm(prefix, method)
|
||||
if method == nil then
|
||||
method = "OnCommReceived"
|
||||
end
|
||||
|
||||
if #prefix > 16 then -- TODO: 15?
|
||||
error("AceComm:RegisterComm(prefix,method): prefix length is limited to 16 characters")
|
||||
end
|
||||
if C_ChatInfo then
|
||||
C_ChatInfo.RegisterAddonMessagePrefix(prefix)
|
||||
else
|
||||
RegisterAddonMessagePrefix(prefix)
|
||||
end
|
||||
|
||||
return AceComm._RegisterComm(self, prefix, method) -- created by CallbackHandler
|
||||
end
|
||||
|
||||
@@ -75,57 +85,55 @@ function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callb
|
||||
if not( type(prefix)=="string" and
|
||||
type(text)=="string" and
|
||||
type(distribution)=="string" and
|
||||
(target==nil or type(target)=="string") and
|
||||
(prio=="BULK" or prio=="NORMAL" or prio=="ALERT")
|
||||
(target==nil or type(target)=="string" or type(target)=="number") and
|
||||
(prio=="BULK" or prio=="NORMAL" or prio=="ALERT")
|
||||
) then
|
||||
error('Usage: SendCommMessage(addon, "prefix", "text", "distribution"[, "target"[, "prio"[, callbackFn, callbackarg]]])', 2)
|
||||
end
|
||||
|
||||
if strfind(prefix, "[\001-\009]") then
|
||||
if strfind(prefix, "[\001-\003]") then
|
||||
error("SendCommMessage: Characters \\001--\\003 in prefix are reserved for AceComm metadata", 2)
|
||||
elseif not warnedPrefix then
|
||||
-- I have some ideas about future extensions that require more control characters /mikk, 20090808
|
||||
geterrorhandler()("SendCommMessage: Heads-up developers: Characters \\004--\\009 in prefix are reserved for AceComm future extension")
|
||||
warnedPrefix = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local textlen = #text
|
||||
local maxtextlen = 254 - #prefix -- 254 is the max length of prefix + text that can be sent in one message
|
||||
local queueName = prefix..distribution..(target or "")
|
||||
local maxtextlen = 255 -- Yes, the max is 255 even if the dev post said 256. I tested. Char 256+ get silently truncated. /Mikk, 20110327
|
||||
local queueName = prefix
|
||||
|
||||
local ctlCallback = nil
|
||||
if callbackFn then
|
||||
ctlCallback = function(sent)
|
||||
return callbackFn(callbackArg, sent, textlen)
|
||||
ctlCallback = function(sent, sendResult)
|
||||
return callbackFn(callbackArg, sent, textlen, sendResult)
|
||||
end
|
||||
end
|
||||
|
||||
if textlen <= maxtextlen then
|
||||
local forceMultipart
|
||||
if match(text, "^[\001-\009]") then -- 4.1+: see if the first character is a control character
|
||||
-- we need to escape the first character with a \004
|
||||
if textlen+1 > maxtextlen then -- would we go over the size limit?
|
||||
forceMultipart = true -- just make it multipart, no escape problems then
|
||||
else
|
||||
text = "\004" .. text
|
||||
end
|
||||
end
|
||||
|
||||
if not forceMultipart and textlen <= maxtextlen then
|
||||
-- fits all in one message
|
||||
CTL:SendAddonMessage(prio, prefix, text, distribution, target, queueName, ctlCallback, textlen)
|
||||
else
|
||||
maxtextlen = maxtextlen - 1 -- 1 extra byte for part indicator in prefix
|
||||
maxtextlen = maxtextlen - 1 -- 1 extra byte for part indicator in prefix(4.0)/start of message(4.1)
|
||||
|
||||
-- first part
|
||||
local chunk = strsub(text, 1, maxtextlen)
|
||||
CTL:SendAddonMessage(prio, prefix..MSG_MULTI_FIRST, chunk, distribution, target, queueName, ctlCallback, maxtextlen)
|
||||
CTL:SendAddonMessage(prio, prefix, MSG_MULTI_FIRST..chunk, distribution, target, queueName, ctlCallback, maxtextlen)
|
||||
|
||||
-- continuation
|
||||
local pos = 1+maxtextlen
|
||||
local prefix2 = prefix..MSG_MULTI_NEXT
|
||||
|
||||
while pos+maxtextlen <= textlen do
|
||||
chunk = strsub(text, pos, pos+maxtextlen-1)
|
||||
CTL:SendAddonMessage(prio, prefix2, chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1)
|
||||
CTL:SendAddonMessage(prio, prefix, MSG_MULTI_NEXT..chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1)
|
||||
pos = pos + maxtextlen
|
||||
end
|
||||
|
||||
-- final part
|
||||
chunk = strsub(text, pos)
|
||||
CTL:SendAddonMessage(prio, prefix..MSG_MULTI_LAST, chunk, distribution, target, queueName, ctlCallback, textlen)
|
||||
CTL:SendAddonMessage(prio, prefix, MSG_MULTI_LAST..chunk, distribution, target, queueName, ctlCallback, textlen)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -138,17 +146,17 @@ do
|
||||
local compost = setmetatable({}, {__mode = "k"})
|
||||
local function new()
|
||||
local t = next(compost)
|
||||
if t then
|
||||
if t then
|
||||
compost[t]=nil
|
||||
for i=#t,3,-1 do -- faster than pairs loop. don't even nil out 1/2 since they'll be overwritten
|
||||
t[i]=nil
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
return {}
|
||||
end
|
||||
|
||||
|
||||
local function lostdatawarning(prefix,sender,where)
|
||||
DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: lost network data regarding '"..tostring(prefix).."' from '"..tostring(sender).."' (in "..where..")")
|
||||
end
|
||||
@@ -156,14 +164,14 @@ do
|
||||
function AceComm:OnReceiveMultipartFirst(prefix, message, distribution, sender)
|
||||
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
|
||||
local spool = AceComm.multipart_spool
|
||||
|
||||
|
||||
--[[
|
||||
if spool[key] then
|
||||
if spool[key] then
|
||||
lostdatawarning(prefix,sender,"First")
|
||||
-- continue and overwrite
|
||||
end
|
||||
--]]
|
||||
|
||||
|
||||
spool[key] = message -- plain string for now
|
||||
end
|
||||
|
||||
@@ -171,7 +179,7 @@ do
|
||||
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
|
||||
local spool = AceComm.multipart_spool
|
||||
local olddata = spool[key]
|
||||
|
||||
|
||||
if not olddata then
|
||||
--lostdatawarning(prefix,sender,"Next")
|
||||
return
|
||||
@@ -192,14 +200,14 @@ do
|
||||
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
|
||||
local spool = AceComm.multipart_spool
|
||||
local olddata = spool[key]
|
||||
|
||||
|
||||
if not olddata then
|
||||
--lostdatawarning(prefix,sender,"End")
|
||||
return
|
||||
end
|
||||
|
||||
spool[key] = nil
|
||||
|
||||
|
||||
if type(olddata) == "table" then
|
||||
-- if we've received a "next", the spooled data will be a table for rapid & garbage-free tconcat
|
||||
tinsert(olddata, message)
|
||||
@@ -222,47 +230,31 @@ end
|
||||
----------------------------------------
|
||||
|
||||
if not AceComm.callbacks then
|
||||
-- ensure that 'prefix to watch' table is consistent with registered
|
||||
-- callbacks
|
||||
AceComm.__prefixes = {}
|
||||
|
||||
AceComm.callbacks = CallbackHandler:New(AceComm,
|
||||
"_RegisterComm",
|
||||
"UnregisterComm",
|
||||
"UnregisterAllComm")
|
||||
end
|
||||
|
||||
function AceComm.callbacks:OnUsed(target, prefix)
|
||||
AceComm.multipart_origprefixes[prefix..MSG_MULTI_FIRST] = prefix
|
||||
AceComm.multipart_reassemblers[prefix..MSG_MULTI_FIRST] = "OnReceiveMultipartFirst"
|
||||
|
||||
AceComm.multipart_origprefixes[prefix..MSG_MULTI_NEXT] = prefix
|
||||
AceComm.multipart_reassemblers[prefix..MSG_MULTI_NEXT] = "OnReceiveMultipartNext"
|
||||
|
||||
AceComm.multipart_origprefixes[prefix..MSG_MULTI_LAST] = prefix
|
||||
AceComm.multipart_reassemblers[prefix..MSG_MULTI_LAST] = "OnReceiveMultipartLast"
|
||||
end
|
||||
AceComm.callbacks.OnUsed = nil
|
||||
AceComm.callbacks.OnUnused = nil
|
||||
|
||||
function AceComm.callbacks:OnUnused(target, prefix)
|
||||
AceComm.multipart_origprefixes[prefix..MSG_MULTI_FIRST] = nil
|
||||
AceComm.multipart_reassemblers[prefix..MSG_MULTI_FIRST] = nil
|
||||
|
||||
AceComm.multipart_origprefixes[prefix..MSG_MULTI_NEXT] = nil
|
||||
AceComm.multipart_reassemblers[prefix..MSG_MULTI_NEXT] = nil
|
||||
|
||||
AceComm.multipart_origprefixes[prefix..MSG_MULTI_LAST] = nil
|
||||
AceComm.multipart_reassemblers[prefix..MSG_MULTI_LAST] = nil
|
||||
end
|
||||
|
||||
local function OnEvent(this, event, ...)
|
||||
local function OnEvent(self, event, prefix, message, distribution, sender)
|
||||
if event == "CHAT_MSG_ADDON" then
|
||||
local prefix,message,distribution,sender = ...
|
||||
local reassemblername = AceComm.multipart_reassemblers[prefix]
|
||||
if reassemblername then
|
||||
-- multipart: reassemble
|
||||
local aceCommReassemblerFunc = AceComm[reassemblername]
|
||||
local origprefix = AceComm.multipart_origprefixes[prefix]
|
||||
aceCommReassemblerFunc(AceComm, origprefix, message, distribution, sender)
|
||||
sender = Ambiguate(sender, "none")
|
||||
local control, rest = match(message, "^([\001-\009])(.*)")
|
||||
if control then
|
||||
if control==MSG_MULTI_FIRST then
|
||||
AceComm:OnReceiveMultipartFirst(prefix, rest, distribution, sender)
|
||||
elseif control==MSG_MULTI_NEXT then
|
||||
AceComm:OnReceiveMultipartNext(prefix, rest, distribution, sender)
|
||||
elseif control==MSG_MULTI_LAST then
|
||||
AceComm:OnReceiveMultipartLast(prefix, rest, distribution, sender)
|
||||
elseif control==MSG_ESCAPE then
|
||||
AceComm.callbacks:Fire(prefix, rest, distribution, sender)
|
||||
else
|
||||
-- unknown control character, ignore SILENTLY (dont warn unnecessarily about future extensions!)
|
||||
end
|
||||
else
|
||||
-- single part: fire it off immediately and let CallbackHandler decide if it's registered or not
|
||||
AceComm.callbacks:Fire(prefix, message, distribution, sender)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
--
|
||||
-- Manages AddOn chat output to keep player from getting kicked off.
|
||||
--
|
||||
-- ChatThrottleLib:SendChatMessage/:SendAddonMessage functions that accept
|
||||
-- ChatThrottleLib:SendChatMessage/:SendAddonMessage functions that accept
|
||||
-- a Priority ("BULK", "NORMAL", "ALERT") as well as prefix for SendChatMessage.
|
||||
--
|
||||
-- Priorities get an equal share of available bandwidth when fully loaded.
|
||||
@@ -20,8 +20,10 @@
|
||||
--
|
||||
-- Can run as a standalone addon also, but, really, just embed it! :-)
|
||||
--
|
||||
-- LICENSE: ChatThrottleLib is released into the Public Domain
|
||||
--
|
||||
|
||||
local CTL_VERSION = 21
|
||||
local CTL_VERSION = 31
|
||||
|
||||
local _G = _G
|
||||
|
||||
@@ -71,8 +73,8 @@ local math_min = math.min
|
||||
local math_max = math.max
|
||||
local next = next
|
||||
local strlen = string.len
|
||||
local GetFrameRate = GetFrameRate
|
||||
|
||||
local GetFramerate = GetFramerate
|
||||
local unpack,type,pairs,wipe = unpack,type,pairs,table.wipe
|
||||
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
@@ -111,28 +113,41 @@ function Ring:Remove(obj)
|
||||
end
|
||||
end
|
||||
|
||||
-- Note that this is local because there's no upgrade logic for existing ring
|
||||
-- metatables, and this isn't present on rings created in versions older than
|
||||
-- v25.
|
||||
local function Ring_Link(self, other) -- Move and append all contents of another ring to this ring
|
||||
if not self.pos then
|
||||
-- This ring is empty, so just transfer ownership.
|
||||
self.pos = other.pos
|
||||
other.pos = nil
|
||||
elseif other.pos then
|
||||
-- Our tail should point to their head, and their tail to our head.
|
||||
self.pos.prev.next, other.pos.prev.next = other.pos, self.pos
|
||||
-- Our head should point to their tail, and their head to our tail.
|
||||
self.pos.prev, other.pos.prev = other.pos.prev, self.pos.prev
|
||||
other.pos = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- Recycling bin for pipes
|
||||
-- A pipe is a plain integer-indexed queue, which also happens to be a ring member
|
||||
-- Recycling bin for pipes
|
||||
-- A pipe is a plain integer-indexed queue of messages
|
||||
-- Pipes normally live in Rings of pipes (3 rings total, one per priority)
|
||||
|
||||
ChatThrottleLib.PipeBin = nil -- pre-v19, drastically different
|
||||
local PipeBin = setmetatable({}, {__mode="k"})
|
||||
|
||||
local function DelPipe(pipe)
|
||||
for i = #pipe, 1, -1 do
|
||||
pipe[i] = nil
|
||||
end
|
||||
pipe.prev = nil
|
||||
pipe.next = nil
|
||||
|
||||
PipeBin[pipe] = true
|
||||
end
|
||||
|
||||
local function NewPipe()
|
||||
local pipe = next(PipeBin)
|
||||
if pipe then
|
||||
wipe(pipe)
|
||||
PipeBin[pipe] = nil
|
||||
return pipe
|
||||
end
|
||||
@@ -169,7 +184,7 @@ end
|
||||
-- Initialize queues, set up frame for OnUpdate, etc
|
||||
|
||||
|
||||
function ChatThrottleLib:Init()
|
||||
function ChatThrottleLib:Init()
|
||||
|
||||
-- Set up queues
|
||||
if not self.Prio then
|
||||
@@ -179,6 +194,13 @@ function ChatThrottleLib:Init()
|
||||
self.Prio["BULK"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
|
||||
end
|
||||
|
||||
if not self.BlockedQueuesDelay then
|
||||
-- v25: Add blocked queues to rings to handle new client throttles.
|
||||
for _, Prio in pairs(self.Prio) do
|
||||
Prio.Blocked = Ring:New()
|
||||
end
|
||||
end
|
||||
|
||||
-- v4: total send counters per priority
|
||||
for _, Prio in pairs(self.Prio) do
|
||||
Prio.nTotalSent = Prio.nTotalSent or 0
|
||||
@@ -201,6 +223,7 @@ function ChatThrottleLib:Init()
|
||||
self.Frame:SetScript("OnEvent", self.OnEvent) -- v11: Monitor P_E_W so we can throttle hard for a few seconds
|
||||
self.Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
self.OnUpdateDelay = 0
|
||||
self.BlockedQueuesDelay = 0
|
||||
self.LastAvailUpdate = GetTime()
|
||||
self.HardThrottlingBeginTime = GetTime() -- v11: Throttle hard for a few seconds after startup
|
||||
|
||||
@@ -209,14 +232,43 @@ function ChatThrottleLib:Init()
|
||||
-- Use secure hooks as of v16. Old regular hook support yanked out in v21.
|
||||
self.securelyHooked = true
|
||||
--SendChatMessage
|
||||
hooksecurefunc("SendChatMessage", function(...)
|
||||
return ChatThrottleLib.Hook_SendChatMessage(...)
|
||||
end)
|
||||
if _G.C_ChatInfo and _G.C_ChatInfo.SendChatMessage then
|
||||
hooksecurefunc(_G.C_ChatInfo, "SendChatMessage", function(...)
|
||||
return ChatThrottleLib.Hook_SendChatMessage(...)
|
||||
end)
|
||||
else
|
||||
hooksecurefunc("SendChatMessage", function(...)
|
||||
return ChatThrottleLib.Hook_SendChatMessage(...)
|
||||
end)
|
||||
end
|
||||
--SendAddonMessage
|
||||
hooksecurefunc("SendAddonMessage", function(...)
|
||||
hooksecurefunc(_G.C_ChatInfo, "SendAddonMessage", function(...)
|
||||
return ChatThrottleLib.Hook_SendAddonMessage(...)
|
||||
end)
|
||||
end
|
||||
|
||||
-- v26: Hook SendAddonMessageLogged for traffic logging
|
||||
if not self.securelyHookedLogged then
|
||||
self.securelyHookedLogged = true
|
||||
hooksecurefunc(_G.C_ChatInfo, "SendAddonMessageLogged", function(...)
|
||||
return ChatThrottleLib.Hook_SendAddonMessageLogged(...)
|
||||
end)
|
||||
end
|
||||
|
||||
-- v29: Hook BNSendGameData for traffic logging
|
||||
if not self.securelyHookedBNGameData then
|
||||
self.securelyHookedBNGameData = true
|
||||
if _G.C_BattleNet and _G.C_BattleNet.SendGameData then
|
||||
hooksecurefunc(_G.C_BattleNet, "SendGameData", function(...)
|
||||
return ChatThrottleLib.Hook_BNSendGameData(...)
|
||||
end)
|
||||
else
|
||||
hooksecurefunc("BNSendGameData", function(...)
|
||||
return ChatThrottleLib.Hook_BNSendGameData(...)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
self.nBypass = 0
|
||||
end
|
||||
|
||||
@@ -245,6 +297,12 @@ function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destinati
|
||||
self.avail = self.avail - size
|
||||
self.nBypass = self.nBypass + size -- just a statistic
|
||||
end
|
||||
function ChatThrottleLib.Hook_SendAddonMessageLogged(prefix, text, chattype, destination, ...)
|
||||
ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destination, ...)
|
||||
end
|
||||
function ChatThrottleLib.Hook_BNSendGameData(destination, prefix, text)
|
||||
ChatThrottleLib.Hook_SendAddonMessage(prefix, text, "WHISPER", destination)
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -281,27 +339,93 @@ end
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- Despooling logic
|
||||
-- Reminder:
|
||||
-- - We have 3 Priorities, each containing a "Ring" construct ...
|
||||
-- - ... made up of N "Pipe"s (1 for each destination/pipename)
|
||||
-- - and each pipe contains messages
|
||||
|
||||
local SendAddonMessageResult = Enum.SendAddonMessageResult or {
|
||||
Success = 0,
|
||||
AddonMessageThrottle = 3,
|
||||
NotInGroup = 5,
|
||||
ChannelThrottle = 8,
|
||||
GeneralError = 9,
|
||||
}
|
||||
|
||||
local function MapToSendResult(ok, ...)
|
||||
local result
|
||||
|
||||
if not ok then
|
||||
-- The send function itself errored; don't look at anything else.
|
||||
result = SendAddonMessageResult.GeneralError
|
||||
else
|
||||
-- Grab the last return value from the send function and remap
|
||||
-- it from a boolean to an enum code. If there are no results,
|
||||
-- assume success (true).
|
||||
|
||||
result = select(-1, true, ...)
|
||||
|
||||
if result == true then
|
||||
result = SendAddonMessageResult.Success
|
||||
elseif result == false then
|
||||
result = SendAddonMessageResult.GeneralError
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
local function IsThrottledSendResult(result)
|
||||
return result == SendAddonMessageResult.AddonMessageThrottle
|
||||
end
|
||||
|
||||
-- A copy of this function exists in FrameXML, but for clarity it's here too.
|
||||
local function CallErrorHandler(...)
|
||||
return geterrorhandler()(...)
|
||||
end
|
||||
|
||||
local function PerformSend(sendFunction, ...)
|
||||
bMyTraffic = true
|
||||
local sendResult = MapToSendResult(xpcall(sendFunction, CallErrorHandler, ...))
|
||||
bMyTraffic = false
|
||||
return sendResult
|
||||
end
|
||||
|
||||
function ChatThrottleLib:Despool(Prio)
|
||||
local ring = Prio.Ring
|
||||
while ring.pos and Prio.avail > ring.pos[1].nSize do
|
||||
local msg = table_remove(Prio.Ring.pos, 1)
|
||||
if not Prio.Ring.pos[1] then
|
||||
local pipe = Prio.Ring.pos
|
||||
local pipe = ring.pos
|
||||
local msg = pipe[1]
|
||||
local sendResult = PerformSend(msg.f, unpack(msg, 1, msg.n))
|
||||
|
||||
if IsThrottledSendResult(sendResult) then
|
||||
-- Message was throttled; move the pipe into the blocked ring.
|
||||
Prio.Ring:Remove(pipe)
|
||||
Prio.ByName[pipe.name] = nil
|
||||
DelPipe(pipe)
|
||||
Prio.Blocked:Add(pipe)
|
||||
else
|
||||
Prio.Ring.pos = Prio.Ring.pos.next
|
||||
end
|
||||
Prio.avail = Prio.avail - msg.nSize
|
||||
bMyTraffic = true
|
||||
msg.f(unpack(msg, 1, msg.n))
|
||||
bMyTraffic = false
|
||||
Prio.nTotalSent = Prio.nTotalSent + msg.nSize
|
||||
DelMsg(msg)
|
||||
if msg.callbackFn then
|
||||
msg.callbackFn (msg.callbackArg)
|
||||
-- Dequeue message after submission.
|
||||
table_remove(pipe, 1)
|
||||
DelMsg(msg)
|
||||
|
||||
if not pipe[1] then -- did we remove last msg in this pipe?
|
||||
Prio.Ring:Remove(pipe)
|
||||
Prio.ByName[pipe.name] = nil
|
||||
DelPipe(pipe)
|
||||
else
|
||||
ring.pos = ring.pos.next
|
||||
end
|
||||
|
||||
-- Update bandwidth counters on successful sends.
|
||||
local didSend = (sendResult == SendAddonMessageResult.Success)
|
||||
if didSend then
|
||||
Prio.avail = Prio.avail - msg.nSize
|
||||
Prio.nTotalSent = Prio.nTotalSent + msg.nSize
|
||||
end
|
||||
|
||||
-- Notify caller of message submission.
|
||||
if msg.callbackFn then
|
||||
securecallfunction(msg.callbackFn, msg.callbackArg, didSend, sendResult)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -321,6 +445,7 @@ function ChatThrottleLib.OnUpdate(this,delay)
|
||||
local self = ChatThrottleLib
|
||||
|
||||
self.OnUpdateDelay = self.OnUpdateDelay + delay
|
||||
self.BlockedQueuesDelay = self.BlockedQueuesDelay + delay
|
||||
if self.OnUpdateDelay < 0.08 then
|
||||
return
|
||||
end
|
||||
@@ -332,40 +457,60 @@ function ChatThrottleLib.OnUpdate(this,delay)
|
||||
return -- argh. some bastard is spewing stuff past the lib. just bail early to save cpu.
|
||||
end
|
||||
|
||||
-- See how many of our priorities have queued messages (we only have 3, don't worry about the loop)
|
||||
local n = 0
|
||||
for prioname,Prio in pairs(self.Prio) do
|
||||
if Prio.Ring.pos or Prio.avail < 0 then
|
||||
n = n + 1
|
||||
-- Integrate blocked queues back into their rings periodically.
|
||||
if self.BlockedQueuesDelay >= 0.35 then
|
||||
for _, Prio in pairs(self.Prio) do
|
||||
Ring_Link(Prio.Ring, Prio.Blocked)
|
||||
end
|
||||
|
||||
self.BlockedQueuesDelay = 0
|
||||
end
|
||||
|
||||
-- Anything queued still?
|
||||
if n<1 then
|
||||
-- Nope. Move spillover bandwidth to global availability gauge and clear self.bQueueing
|
||||
for prioname, Prio in pairs(self.Prio) do
|
||||
-- See how many of our priorities have queued messages. This is split
|
||||
-- into two counters because priorities that consist only of blocked
|
||||
-- queues must keep our OnUpdate alive, but shouldn't count toward
|
||||
-- bandwidth distribution.
|
||||
local nSendablePrios = 0
|
||||
local nBlockedPrios = 0
|
||||
|
||||
for prioname, Prio in pairs(self.Prio) do
|
||||
if Prio.Ring.pos then
|
||||
nSendablePrios = nSendablePrios + 1
|
||||
elseif Prio.Blocked.pos then
|
||||
nBlockedPrios = nBlockedPrios + 1
|
||||
end
|
||||
|
||||
-- Collect unused bandwidth from priorities with nothing to send.
|
||||
if not Prio.Ring.pos then
|
||||
self.avail = self.avail + Prio.avail
|
||||
Prio.avail = 0
|
||||
end
|
||||
self.bQueueing = false
|
||||
self.Frame:Hide()
|
||||
end
|
||||
|
||||
-- Bandwidth reclamation may take us back over the burst cap.
|
||||
self.avail = math_min(self.avail, self.BURST)
|
||||
|
||||
-- If we can't currently send on any priorities, stop processing early.
|
||||
if nSendablePrios == 0 then
|
||||
-- If we're completely out of data to send, disable queue processing.
|
||||
if nBlockedPrios == 0 then
|
||||
self.bQueueing = false
|
||||
self.Frame:Hide()
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- There's stuff queued. Hand out available bandwidth to priorities as needed and despool their queues
|
||||
local avail = self.avail/n
|
||||
local avail = self.avail / nSendablePrios
|
||||
self.avail = 0
|
||||
|
||||
for prioname, Prio in pairs(self.Prio) do
|
||||
if Prio.Ring.pos or Prio.avail < 0 then
|
||||
if Prio.Ring.pos then
|
||||
Prio.avail = Prio.avail + avail
|
||||
if Prio.Ring.pos and Prio.avail > Prio.Ring.pos[1].nSize then
|
||||
self:Despool(Prio)
|
||||
-- Note: We might not get here if the user-supplied callback function errors out! Take care!
|
||||
end
|
||||
self:Despool(Prio)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -374,7 +519,6 @@ end
|
||||
-----------------------------------------------------------------------
|
||||
-- Spooling logic
|
||||
|
||||
|
||||
function ChatThrottleLib:Enqueue(prioname, pipename, msg)
|
||||
local Prio = self.Prio[prioname]
|
||||
local pipe = Prio.ByName[pipename]
|
||||
@@ -391,8 +535,6 @@ function ChatThrottleLib:Enqueue(prioname, pipename, msg)
|
||||
self.bQueueing = true
|
||||
end
|
||||
|
||||
|
||||
|
||||
function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, language, destination, queueName, callbackFn, callbackArg)
|
||||
if not self or not prio or not prefix or not text or not self.Prio[prio] then
|
||||
error('Usage: ChatThrottleLib:SendChatMessage("{BULK||NORMAL||ALERT}", "prefix", "text"[, "chattype"[, "language"[, "destination"]]]', 2)
|
||||
@@ -411,20 +553,27 @@ function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, languag
|
||||
|
||||
-- Check if there's room in the global available bandwidth gauge to send directly
|
||||
if not self.bQueueing and nSize < self:UpdateAvail() then
|
||||
self.avail = self.avail - nSize
|
||||
bMyTraffic = true
|
||||
_G.SendChatMessage(text, chattype, language, destination)
|
||||
bMyTraffic = false
|
||||
self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
|
||||
if callbackFn then
|
||||
callbackFn (callbackArg)
|
||||
local sendResult = PerformSend(_G.C_ChatInfo.SendChatMessage or _G.SendChatMessage, text, chattype, language, destination)
|
||||
|
||||
if not IsThrottledSendResult(sendResult) then
|
||||
local didSend = (sendResult == SendAddonMessageResult.Success)
|
||||
|
||||
if didSend then
|
||||
self.avail = self.avail - nSize
|
||||
self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
|
||||
end
|
||||
|
||||
if callbackFn then
|
||||
securecallfunction(callbackFn, callbackArg, didSend, sendResult)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- Message needs to be queued
|
||||
local msg = NewMsg()
|
||||
msg.f = _G.SendChatMessage
|
||||
msg.f = _G.C_ChatInfo.SendChatMessage or _G.SendChatMessage
|
||||
msg[1] = text
|
||||
msg[2] = chattype or "SAY"
|
||||
msg[3] = language
|
||||
@@ -434,42 +583,36 @@ function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, languag
|
||||
msg.callbackFn = callbackFn
|
||||
msg.callbackArg = callbackArg
|
||||
|
||||
self:Enqueue(prio, queueName or (prefix..(chattype or "SAY")..(destination or "")), msg)
|
||||
self:Enqueue(prio, queueName or prefix, msg)
|
||||
end
|
||||
|
||||
|
||||
function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg)
|
||||
if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then
|
||||
error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2)
|
||||
end
|
||||
if callbackFn and type(callbackFn)~="function" then
|
||||
error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2)
|
||||
end
|
||||
|
||||
local nSize = prefix:len() + 1 + text:len();
|
||||
|
||||
if nSize>255 then
|
||||
error("ChatThrottleLib:SendAddonMessage(): prefix + message length cannot exceed 254 bytes", 2)
|
||||
end
|
||||
|
||||
nSize = nSize + self.MSG_OVERHEAD;
|
||||
local function SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg)
|
||||
local nSize = #text + self.MSG_OVERHEAD
|
||||
|
||||
-- Check if there's room in the global available bandwidth gauge to send directly
|
||||
if not self.bQueueing and nSize < self:UpdateAvail() then
|
||||
self.avail = self.avail - nSize
|
||||
bMyTraffic = true
|
||||
_G.SendAddonMessage(prefix, text, chattype, target)
|
||||
bMyTraffic = false
|
||||
self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
|
||||
if callbackFn then
|
||||
callbackFn (callbackArg)
|
||||
local sendResult = PerformSend(sendFunction, prefix, text, chattype, target)
|
||||
|
||||
if not IsThrottledSendResult(sendResult) then
|
||||
local didSend = (sendResult == SendAddonMessageResult.Success)
|
||||
|
||||
if didSend then
|
||||
self.avail = self.avail - nSize
|
||||
self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
|
||||
end
|
||||
|
||||
if callbackFn then
|
||||
securecallfunction(callbackFn, callbackArg, didSend, sendResult)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- Message needs to be queued
|
||||
local msg = NewMsg()
|
||||
msg.f = _G.SendAddonMessage
|
||||
msg.f = sendFunction
|
||||
msg[1] = prefix
|
||||
msg[2] = text
|
||||
msg[3] = chattype
|
||||
@@ -479,10 +622,65 @@ function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target,
|
||||
msg.callbackFn = callbackFn
|
||||
msg.callbackArg = callbackArg
|
||||
|
||||
self:Enqueue(prio, queueName or (prefix..chattype..(target or "")), msg)
|
||||
self:Enqueue(prio, queueName or prefix, msg)
|
||||
end
|
||||
|
||||
|
||||
function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg)
|
||||
if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then
|
||||
error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2)
|
||||
elseif callbackFn and type(callbackFn)~="function" then
|
||||
error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2)
|
||||
elseif #text>255 then
|
||||
error("ChatThrottleLib:SendAddonMessage(): message length cannot exceed 255 bytes", 2)
|
||||
end
|
||||
|
||||
local sendFunction = _G.C_ChatInfo.SendAddonMessage
|
||||
SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg)
|
||||
end
|
||||
|
||||
|
||||
function ChatThrottleLib:SendAddonMessageLogged(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg)
|
||||
if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then
|
||||
error('Usage: ChatThrottleLib:SendAddonMessageLogged("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2)
|
||||
elseif callbackFn and type(callbackFn)~="function" then
|
||||
error('ChatThrottleLib:SendAddonMessageLogged(): callbackFn: expected function, got '..type(callbackFn), 2)
|
||||
elseif #text>255 then
|
||||
error("ChatThrottleLib:SendAddonMessageLogged(): message length cannot exceed 255 bytes", 2)
|
||||
end
|
||||
|
||||
local sendFunction = _G.C_ChatInfo.SendAddonMessageLogged
|
||||
SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg)
|
||||
end
|
||||
|
||||
local function BNSendGameDataReordered(prefix, text, _, gameAccountID)
|
||||
local bnSendFunc = _G.C_BattleNet and _G.C_BattleNet.SendGameData or _G.BNSendGameData
|
||||
return bnSendFunc(gameAccountID, prefix, text)
|
||||
end
|
||||
|
||||
function ChatThrottleLib:BNSendGameData(prio, prefix, text, chattype, gameAccountID, queueName, callbackFn, callbackArg)
|
||||
-- Note that this API is intentionally limited to 255 bytes of data
|
||||
-- for reasons of traffic fairness, which is less than the 4078 bytes
|
||||
-- BNSendGameData natively supports. Additionally, a chat type is required
|
||||
-- but must always be set to 'WHISPER' to match what is exposed by the
|
||||
-- receipt event.
|
||||
--
|
||||
-- If splitting messages, callers must also be aware that message
|
||||
-- delivery over BNSendGameData is unordered.
|
||||
|
||||
if not self or not prio or not prefix or not text or not gameAccountID or not chattype or not self.Prio[prio] then
|
||||
error('Usage: ChatThrottleLib:BNSendGameData("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype", gameAccountID)', 2)
|
||||
elseif callbackFn and type(callbackFn)~="function" then
|
||||
error('ChatThrottleLib:BNSendGameData(): callbackFn: expected function, got '..type(callbackFn), 2)
|
||||
elseif #text>255 then
|
||||
error("ChatThrottleLib:BNSendGameData(): message length cannot exceed 255 bytes", 2)
|
||||
elseif chattype ~= "WHISPER" then
|
||||
error("ChatThrottleLib:BNSendGameData(): chat type must be 'WHISPER'", 2)
|
||||
end
|
||||
|
||||
local sendFunction = BNSendGameDataReordered
|
||||
SendAddonMessageInternal(self, sendFunction, prio, prefix, text, chattype, gameAccountID, queueName, callbackFn, callbackArg)
|
||||
end
|
||||
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user