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 |