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