annotate Libs/AceComm-3.0/AceComm-3.0.lua @ 58:0682d738499b v8.0.1.058

- 8.0.1 Update.
author Tercio
date Fri, 20 Jul 2018 19:04:12 -0300
parents dbf04157d63e
children
rev   line source
Tercio@4 1 --- **AceComm-3.0** allows you to send messages of unlimited length over the addon comm channels.
Tercio@4 2 -- It'll automatically split the messages into multiple parts and rebuild them on the receiving end.\\
Tercio@4 3 -- **ChatThrottleLib** is of course being used to avoid being disconnected by the server.
Tercio@4 4 --
Tercio@4 5 -- **AceComm-3.0** can be embeded into your addon, either explicitly by calling AceComm:Embed(MyAddon) or by
Tercio@4 6 -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
Tercio@4 7 -- and can be accessed directly, without having to explicitly call AceComm itself.\\
Tercio@4 8 -- It is recommended to embed AceComm, otherwise you'll have to specify a custom `self` on all calls you
Tercio@4 9 -- make into AceComm.
Tercio@4 10 -- @class file
Tercio@4 11 -- @name AceComm-3.0
Tercio@58 12 -- @release $Id: AceComm-3.0.lua 1174 2018-05-14 17:29:49Z h.leppkes@gmail.com $
Tercio@4 13
Tercio@4 14 --[[ AceComm-3.0
Tercio@4 15
Tercio@4 16 TODO: Time out old data rotting around from dead senders? Not a HUGE deal since the number of possible sender names is somewhat limited.
Tercio@4 17
Tercio@4 18 ]]
Tercio@4 19
Tercio@51 20 local CallbackHandler = LibStub("CallbackHandler-1.0")
Tercio@51 21 local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib")
Tercio@4 22
Tercio@58 23 local MAJOR, MINOR = "AceComm-3.0", 12
Tercio@4 24 local AceComm,oldminor = LibStub:NewLibrary(MAJOR, MINOR)
Tercio@4 25
Tercio@4 26 if not AceComm then return end
Tercio@4 27
Tercio@4 28 -- Lua APIs
Tercio@4 29 local type, next, pairs, tostring = type, next, pairs, tostring
Tercio@4 30 local strsub, strfind = string.sub, string.find
Tercio@4 31 local match = string.match
Tercio@4 32 local tinsert, tconcat = table.insert, table.concat
Tercio@4 33 local error, assert = error, assert
Tercio@4 34
Tercio@4 35 -- WoW APIs
Tercio@4 36 local Ambiguate = Ambiguate
Tercio@4 37
Tercio@4 38 -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
Tercio@4 39 -- List them here for Mikk's FindGlobals script
Tercio@4 40 -- GLOBALS: LibStub, DEFAULT_CHAT_FRAME, geterrorhandler, RegisterAddonMessagePrefix
Tercio@4 41
Tercio@4 42 AceComm.embeds = AceComm.embeds or {}
Tercio@4 43
Tercio@4 44 -- for my sanity and yours, let's give the message type bytes some names
Tercio@4 45 local MSG_MULTI_FIRST = "\001"
Tercio@4 46 local MSG_MULTI_NEXT = "\002"
Tercio@4 47 local MSG_MULTI_LAST = "\003"
Tercio@4 48 local MSG_ESCAPE = "\004"
Tercio@4 49
Tercio@4 50 -- remove old structures (pre WoW 4.0)
Tercio@4 51 AceComm.multipart_origprefixes = nil
Tercio@4 52 AceComm.multipart_reassemblers = nil
Tercio@4 53
Tercio@4 54 -- the multipart message spool: indexed by a combination of sender+distribution+
Tercio@4 55 AceComm.multipart_spool = AceComm.multipart_spool or {}
Tercio@4 56
Tercio@4 57 --- Register for Addon Traffic on a specified prefix
Tercio@4 58 -- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent), max 16 characters
Tercio@4 59 -- @param method Callback to call on message reception: Function reference, or method name (string) to call on self. Defaults to "OnCommReceived"
Tercio@4 60 function AceComm:RegisterComm(prefix, method)
Tercio@4 61 if method == nil then
Tercio@4 62 method = "OnCommReceived"
Tercio@4 63 end
Tercio@4 64
Tercio@4 65 if #prefix > 16 then -- TODO: 15?
Tercio@4 66 error("AceComm:RegisterComm(prefix,method): prefix length is limited to 16 characters")
Tercio@4 67 end
Tercio@58 68 if C_ChatInfo then
Tercio@58 69 C_ChatInfo.RegisterAddonMessagePrefix(prefix)
Tercio@58 70 else
Tercio@58 71 RegisterAddonMessagePrefix(prefix)
Tercio@58 72 end
Tercio@4 73
Tercio@4 74 return AceComm._RegisterComm(self, prefix, method) -- created by CallbackHandler
Tercio@4 75 end
Tercio@4 76
Tercio@4 77 local warnedPrefix=false
Tercio@4 78
Tercio@4 79 --- Send a message over the Addon Channel
Tercio@4 80 -- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent)
Tercio@4 81 -- @param text Data to send, nils (\000) not allowed. Any length.
Tercio@4 82 -- @param distribution Addon channel, e.g. "RAID", "GUILD", etc; see SendAddonMessage API
Tercio@4 83 -- @param target Destination for some distributions; see SendAddonMessage API
Tercio@4 84 -- @param prio OPTIONAL: ChatThrottleLib priority, "BULK", "NORMAL" or "ALERT". Defaults to "NORMAL".
Tercio@4 85 -- @param callbackFn OPTIONAL: callback function to be called as each chunk is sent. receives 3 args: the user supplied arg (see next), the number of bytes sent so far, and the number of bytes total to send.
Tercio@4 86 -- @param callbackArg: OPTIONAL: first arg to the callback function. nil will be passed if not specified.
Tercio@4 87 function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callbackFn, callbackArg)
Tercio@4 88 prio = prio or "NORMAL" -- pasta's reference implementation had different prio for singlepart and multipart, but that's a very bad idea since that can easily lead to out-of-sequence delivery!
Tercio@4 89 if not( type(prefix)=="string" and
Tercio@4 90 type(text)=="string" and
Tercio@4 91 type(distribution)=="string" and
Tercio@58 92 (target==nil or type(target)=="string" or type(target)=="number") and
Tercio@4 93 (prio=="BULK" or prio=="NORMAL" or prio=="ALERT")
Tercio@4 94 ) then
Tercio@4 95 error('Usage: SendCommMessage(addon, "prefix", "text", "distribution"[, "target"[, "prio"[, callbackFn, callbackarg]]])', 2)
Tercio@4 96 end
Tercio@4 97
Tercio@4 98 local textlen = #text
Tercio@4 99 local maxtextlen = 255 -- Yes, the max is 255 even if the dev post said 256. I tested. Char 256+ get silently truncated. /Mikk, 20110327
Tercio@4 100 local queueName = prefix..distribution..(target or "")
Tercio@4 101
Tercio@4 102 local ctlCallback = nil
Tercio@4 103 if callbackFn then
Tercio@4 104 ctlCallback = function(sent)
Tercio@4 105 return callbackFn(callbackArg, sent, textlen)
Tercio@4 106 end
Tercio@4 107 end
Tercio@4 108
Tercio@4 109 local forceMultipart
Tercio@4 110 if match(text, "^[\001-\009]") then -- 4.1+: see if the first character is a control character
Tercio@4 111 -- we need to escape the first character with a \004
Tercio@4 112 if textlen+1 > maxtextlen then -- would we go over the size limit?
Tercio@4 113 forceMultipart = true -- just make it multipart, no escape problems then
Tercio@4 114 else
Tercio@4 115 text = "\004" .. text
Tercio@4 116 end
Tercio@4 117 end
Tercio@4 118
Tercio@4 119 if not forceMultipart and textlen <= maxtextlen then
Tercio@4 120 -- fits all in one message
Tercio@4 121 CTL:SendAddonMessage(prio, prefix, text, distribution, target, queueName, ctlCallback, textlen)
Tercio@4 122 else
Tercio@4 123 maxtextlen = maxtextlen - 1 -- 1 extra byte for part indicator in prefix(4.0)/start of message(4.1)
Tercio@4 124
Tercio@4 125 -- first part
Tercio@4 126 local chunk = strsub(text, 1, maxtextlen)
Tercio@4 127 CTL:SendAddonMessage(prio, prefix, MSG_MULTI_FIRST..chunk, distribution, target, queueName, ctlCallback, maxtextlen)
Tercio@4 128
Tercio@4 129 -- continuation
Tercio@4 130 local pos = 1+maxtextlen
Tercio@4 131
Tercio@4 132 while pos+maxtextlen <= textlen do
Tercio@4 133 chunk = strsub(text, pos, pos+maxtextlen-1)
Tercio@4 134 CTL:SendAddonMessage(prio, prefix, MSG_MULTI_NEXT..chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1)
Tercio@4 135 pos = pos + maxtextlen
Tercio@4 136 end
Tercio@4 137
Tercio@4 138 -- final part
Tercio@4 139 chunk = strsub(text, pos)
Tercio@4 140 CTL:SendAddonMessage(prio, prefix, MSG_MULTI_LAST..chunk, distribution, target, queueName, ctlCallback, textlen)
Tercio@4 141 end
Tercio@4 142 end
Tercio@4 143
Tercio@4 144
Tercio@4 145 ----------------------------------------
Tercio@4 146 -- Message receiving
Tercio@4 147 ----------------------------------------
Tercio@4 148
Tercio@4 149 do
Tercio@4 150 local compost = setmetatable({}, {__mode = "k"})
Tercio@4 151 local function new()
Tercio@4 152 local t = next(compost)
Tercio@4 153 if t then
Tercio@4 154 compost[t]=nil
Tercio@4 155 for i=#t,3,-1 do -- faster than pairs loop. don't even nil out 1/2 since they'll be overwritten
Tercio@4 156 t[i]=nil
Tercio@4 157 end
Tercio@4 158 return t
Tercio@4 159 end
Tercio@4 160
Tercio@4 161 return {}
Tercio@4 162 end
Tercio@4 163
Tercio@4 164 local function lostdatawarning(prefix,sender,where)
Tercio@4 165 DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: lost network data regarding '"..tostring(prefix).."' from '"..tostring(sender).."' (in "..where..")")
Tercio@4 166 end
Tercio@4 167
Tercio@4 168 function AceComm:OnReceiveMultipartFirst(prefix, message, distribution, sender)
Tercio@4 169 local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
Tercio@4 170 local spool = AceComm.multipart_spool
Tercio@4 171
Tercio@4 172 --[[
Tercio@4 173 if spool[key] then
Tercio@4 174 lostdatawarning(prefix,sender,"First")
Tercio@4 175 -- continue and overwrite
Tercio@4 176 end
Tercio@4 177 --]]
Tercio@4 178
Tercio@4 179 spool[key] = message -- plain string for now
Tercio@4 180 end
Tercio@4 181
Tercio@4 182 function AceComm:OnReceiveMultipartNext(prefix, message, distribution, sender)
Tercio@4 183 local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
Tercio@4 184 local spool = AceComm.multipart_spool
Tercio@4 185 local olddata = spool[key]
Tercio@4 186
Tercio@4 187 if not olddata then
Tercio@4 188 --lostdatawarning(prefix,sender,"Next")
Tercio@4 189 return
Tercio@4 190 end
Tercio@4 191
Tercio@4 192 if type(olddata)~="table" then
Tercio@4 193 -- ... but what we have is not a table. So make it one. (Pull a composted one if available)
Tercio@4 194 local t = new()
Tercio@4 195 t[1] = olddata -- add old data as first string
Tercio@4 196 t[2] = message -- and new message as second string
Tercio@4 197 spool[key] = t -- and put the table in the spool instead of the old string
Tercio@4 198 else
Tercio@4 199 tinsert(olddata, message)
Tercio@4 200 end
Tercio@4 201 end
Tercio@4 202
Tercio@4 203 function AceComm:OnReceiveMultipartLast(prefix, message, distribution, sender)
Tercio@4 204 local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
Tercio@4 205 local spool = AceComm.multipart_spool
Tercio@4 206 local olddata = spool[key]
Tercio@4 207
Tercio@4 208 if not olddata then
Tercio@4 209 --lostdatawarning(prefix,sender,"End")
Tercio@4 210 return
Tercio@4 211 end
Tercio@4 212
Tercio@4 213 spool[key] = nil
Tercio@4 214
Tercio@4 215 if type(olddata) == "table" then
Tercio@4 216 -- if we've received a "next", the spooled data will be a table for rapid & garbage-free tconcat
Tercio@4 217 tinsert(olddata, message)
Tercio@4 218 AceComm.callbacks:Fire(prefix, tconcat(olddata, ""), distribution, sender)
Tercio@4 219 compost[olddata] = true
Tercio@4 220 else
Tercio@4 221 -- if we've only received a "first", the spooled data will still only be a string
Tercio@4 222 AceComm.callbacks:Fire(prefix, olddata..message, distribution, sender)
Tercio@4 223 end
Tercio@4 224 end
Tercio@4 225 end
Tercio@4 226
Tercio@4 227
Tercio@4 228
Tercio@4 229
Tercio@4 230
Tercio@4 231
Tercio@4 232 ----------------------------------------
Tercio@4 233 -- Embed CallbackHandler
Tercio@4 234 ----------------------------------------
Tercio@4 235
Tercio@4 236 if not AceComm.callbacks then
Tercio@4 237 AceComm.callbacks = CallbackHandler:New(AceComm,
Tercio@4 238 "_RegisterComm",
Tercio@4 239 "UnregisterComm",
Tercio@4 240 "UnregisterAllComm")
Tercio@4 241 end
Tercio@4 242
Tercio@4 243 AceComm.callbacks.OnUsed = nil
Tercio@4 244 AceComm.callbacks.OnUnused = nil
Tercio@4 245
Tercio@4 246 local function OnEvent(self, event, prefix, message, distribution, sender)
Tercio@4 247 if event == "CHAT_MSG_ADDON" then
Tercio@4 248 sender = Ambiguate(sender, "none")
Tercio@4 249 local control, rest = match(message, "^([\001-\009])(.*)")
Tercio@4 250 if control then
Tercio@4 251 if control==MSG_MULTI_FIRST then
Tercio@4 252 AceComm:OnReceiveMultipartFirst(prefix, rest, distribution, sender)
Tercio@4 253 elseif control==MSG_MULTI_NEXT then
Tercio@4 254 AceComm:OnReceiveMultipartNext(prefix, rest, distribution, sender)
Tercio@4 255 elseif control==MSG_MULTI_LAST then
Tercio@4 256 AceComm:OnReceiveMultipartLast(prefix, rest, distribution, sender)
Tercio@4 257 elseif control==MSG_ESCAPE then
Tercio@4 258 AceComm.callbacks:Fire(prefix, rest, distribution, sender)
Tercio@4 259 else
Tercio@4 260 -- unknown control character, ignore SILENTLY (dont warn unnecessarily about future extensions!)
Tercio@4 261 end
Tercio@4 262 else
Tercio@4 263 -- single part: fire it off immediately and let CallbackHandler decide if it's registered or not
Tercio@4 264 AceComm.callbacks:Fire(prefix, message, distribution, sender)
Tercio@4 265 end
Tercio@4 266 else
Tercio@4 267 assert(false, "Received "..tostring(event).." event?!")
Tercio@4 268 end
Tercio@4 269 end
Tercio@4 270
Tercio@4 271 AceComm.frame = AceComm.frame or CreateFrame("Frame", "AceComm30Frame")
Tercio@4 272 AceComm.frame:SetScript("OnEvent", OnEvent)
Tercio@4 273 AceComm.frame:UnregisterAllEvents()
Tercio@4 274 AceComm.frame:RegisterEvent("CHAT_MSG_ADDON")
Tercio@4 275
Tercio@4 276
Tercio@4 277 ----------------------------------------
Tercio@4 278 -- Base library stuff
Tercio@4 279 ----------------------------------------
Tercio@4 280
Tercio@4 281 local mixins = {
Tercio@4 282 "RegisterComm",
Tercio@4 283 "UnregisterComm",
Tercio@4 284 "UnregisterAllComm",
Tercio@4 285 "SendCommMessage",
Tercio@4 286 }
Tercio@4 287
Tercio@4 288 -- Embeds AceComm-3.0 into the target object making the functions from the mixins list available on target:..
Tercio@4 289 -- @param target target object to embed AceComm-3.0 in
Tercio@4 290 function AceComm:Embed(target)
Tercio@4 291 for k, v in pairs(mixins) do
Tercio@4 292 target[v] = self[v]
Tercio@4 293 end
Tercio@4 294 self.embeds[target] = true
Tercio@4 295 return target
Tercio@4 296 end
Tercio@4 297
Tercio@4 298 function AceComm:OnEmbedDisable(target)
Tercio@4 299 target:UnregisterAllComm()
Tercio@4 300 end
Tercio@4 301
Tercio@4 302 -- Update embeds
Tercio@4 303 for target, v in pairs(AceComm.embeds) do
Tercio@4 304 AceComm:Embed(target)
Tercio@4 305 end