comparison Lists.lua @ 0:47fac96968e1

First commit
author John@Yosemite-PC
date Fri, 02 Mar 2012 00:15:09 -0500
parents
children 21c58930f74e
comparison
equal deleted inserted replaced
-1:000000000000 0:47fac96968e1
1 -- lists consist of three things
2 -- 1) a base state - agreed on by one or more list holders
3 -- 2) change sets - incremental list changes (can be rolled forwards or
4 -- backwards)
5 -- 3) working state - not saved because it can be so easily calculated
6 --
7 -- A separate user list is held - lists index into this
8
9
10 -- TODO: rename player
11
12
13
14 bsk.lists = {}
15 bsk.players = {}
16
17 local RaidList = {}
18 local ReserveList = {}
19 local activeList = 0 -- temporary
20
21 local tinsert = table.insert
22 local sformat = string.format
23 local getn = table.getn
24
25 function bsk:tcopy(to, from)
26 for k,v in pairs(from) do
27 if(type(v)=="table") then
28 to[k] = {}
29 bsk:tcopy(to[k], v);
30 else
31 to[k] = v;
32 end
33 end
34 end
35 local shallowCopy = function(t)
36 local u = { }
37 for k, v in pairs(t) do u[k] = v end
38 return setmetatable(u, getmetatable(t))
39 end
40
41 function bsk:PrintTable(table, depth)
42 depth = depth or ""
43 if not table then return end
44 for i,v in pairs(table) do
45 if( type(v) == "string" ) then
46 self:Print(depth .. i .. " - " .. v)
47 elseif( type(v) == "number" ) then
48 self:Print(depth .. i .. " - " .. tostring(v))
49 elseif( type(v) == "table" ) then
50 self:Print(depth .. i .." - ")
51 self:PrintTable(v,depth.." ")
52 elseif( type(v) == "boolean" ) then
53 self:Print(depth .. i .. " - " .. tostring(v))
54 else
55 self:Print(depth .. i .. " - not sure how to print type: " .. type(v) )
56 end
57 end
58 end
59
60 -- Debugging {{{
61 function bsk:PrintLists()
62 bsk:PrintTable(bsk.lists)
63 end
64 function bsk:PrintChanges()
65 bsk:PrintTable(bsk.db.profile.changes)
66 end
67 function bsk:PrintPlayers()
68 bsk:PrintTable(bsk.players)
69 end
70 --}}}
71
72 function bsk:CreateWorkingStateFromChanges()
73 local playerBase = self.db.profile.players
74 local listBase = self.db.profile.listBase
75 local changes = self.db.profile.changes
76
77 -- copy the base to the working state
78 wipe(bsk.lists)
79 wipe(bsk.players)
80 bsk:tcopy(bsk.lists,listBase)
81 bsk:tcopy(bsk.players,playerBase)
82
83 -- now just go through the changes list applying each
84 for i,v in pairs(changes) do
85 bsk:ProcessChange(v)
86 end
87 end
88
89 function bsk:CreateChange(change)
90 -- sanity
91 assert(change)
92 assert(change.action)
93 assert(change.arg)
94
95 bsk:StartChange(change)
96 bsk:CommitChange(change)
97 end
98
99 function bsk:StartChange(change)
100 local changes = self.db.profile.changes
101 change.time = time()
102 local n = getn(changes)
103 if n > 0 then
104 if changes[n].time >= change.time then
105 change.time = changes[n].time + 1
106 end
107 end
108 end
109
110 function bsk:CommitChange(change)
111 local changes = self.db.profile.changes
112 tinsert(changes,change)
113 -- TODO: broadcast change
114 end
115
116
117 -- timestamp logic:
118 -- use time() for comparisons - local clients use date() to make it pretty. only
119 -- dowisde - we can't have a server timestamp. Which kind of sucks, but it turns
120 -- out you can change timezones when you enter an instance server, so you really
121 -- never know what time it is.
122 -- There's unfortunately no hard-and-proven method for determining the true time
123 -- difference between local time and server time. You can't just query the two
124 -- and compare them because your server timezone can change (!) if you go into
125 -- an instance server with a different timezone. This is apparently a big
126 -- problem on Oceanic realms.
127 --
128 -- Timestamp handling (brainstorming how to deal with drift):
129 -- (not an issue) if someone sends you time in the future, update your offset so you won't
130 -- send out events in the "past" to that person
131 -- (not an issue - using local UTC now) on change-zone-event: check if you've changed timezones - might need update
132 -- each time you add a change, check the tail of the change list; if this is
133 -- less than that, you have a problem. Print a message. if this is equal, then
134 -- that's ok, just bump it by 1 second. This could happen in the case of, say,
135 -- spam-clicking the undo button or adding names to the list. The recipients
136 -- should be ok with this since they'll follow the same algorithm. The only
137 -- real chance for a problem is if two people click within the 1 second window?
138 -- if someone sends you a past event,
139 -- it's ok if it's newer than anything in the changes list
140 -- otherwise ... causality has been violated.
141 -- Whenever an admin signon event happens, have the admins each perform a
142 -- timestamp check. Issue warnings for anyone with a clock that's more than
143 -- X seconds out of sync with the others. Seriously, why isn't NTP a standard
144 -- setting on all operating systems ...
145
146 function bsk:ProcessChange(change)
147 if change.action == "AddPlayer" then
148 bsk:DoAddPlayer(change)
149 elseif change.action == "CreateList" then
150 bsk:DoCreateList(change)
151 elseif change.action == "AddPlayerToList" then
152 bsk:DoAddPlayerToList(change)
153 elseif change.action == "SuicidePlayer" then
154 bsk:DoSuicidePlayer(change)
155 else
156 bsk:Print("Unknown message encountered")
157 bsk:PrintTable(change)
158 assert(false)
159 end
160 end
161
162
163 --
164 -- The actual actions for changes start here
165 --
166 -- Each action occurs as a pair of functions. The bsk:Action() function is from
167 -- a list admin's point of view. Each will check for admin status, then create a
168 -- change bundle, call the handler for that change (ie the DoAction func), and
169 -- then record/transmist the bundle. These are simple and repetitive functions.
170 --
171 -- The bsk:DoAction() function is tasked with executing the bundle and is what
172 -- non-admins and admins alike will call to transform their working state via a
173 -- change packet. Each Do() function will accept *only* a change packet, and
174 -- it's assumed that the change has been vetted elsewhere. These are very blunt
175 -- routines.
176 --
177 -- Note that "undo" has no special voodoo to it. It's basically a change that
178 -- reverses the prior change on the stack.
179
180 -- Players list
181 function bsk:DoAddPlayer(change)
182 assert(change)
183 assert(change.arg.guid)
184 local arg = change.arg
185 -- require admin
186 local players = bsk.players
187 local name = arg.name
188 local guid = arg.guid
189 assert(players[guid]==nil)
190 players[guid] = name
191 players.time=change.time
192 return true
193 end
194
195 function bsk:AddPlayer(name)
196 local players = bsk.players
197 local guid = UnitGUID(name)
198 -- TODO: check guid to be sure it's a player
199 if not guid then
200 self:Print(sformat("Could not add player %s - they must be in range or group",name))
201 return
202 end
203 if players[guid] and players[guid] ~= name then
204 self:Print(sformat("Namechange detected for %s - new is %s, please rename the existing entry", players[guid], name))
205 return
206 end
207 if players[guid] ~= nil then
208 self:Print(sformat("%s is already in the players list; disregarding", name))
209 return
210 end
211 local change = {action="AddPlayer",arg={name=name,guid=guid}}
212 if bsk:DoAddPlayer(change) then
213 bsk:CreateChange(change)
214 end
215 end
216
217 function bsk:CreateFakeLists()
218 -- testing only
219 end
220
221 function bsk:DoCreateList(change)
222 -- TODO: this segment will probably be useful as bsk:SearchForListByName
223 local lists = bsk.lists
224 for i,v in pairs(lists) do
225 if v.name == change.arg.name then
226 self:Print(sformat("List %s already exists",v.name))
227 return false
228 end
229 end
230 tinsert(lists,{name=change.arg.name,time=change.time})
231 return true
232 end
233
234 function bsk:CreateList(name)
235 -- require admin
236 local change={action="CreateList",arg={name=name}}
237 bsk:StartChange(change)
238 self:Print("Creating ... " .. name)
239 if bsk:DoCreateList(change) then
240 bsk:CommitChange(change)
241 end
242 end
243
244 function bsk:DoAddPlayerToList(change)
245 local listIndex = change.arg.listIndex
246 local slist = change.arg.slist
247 local list = bsk.lists[listIndex]
248
249 if #slist == 1 then -- end of list insertion - just one person
250 tinsert(list,slist[1])
251 list.time = change.time
252 else
253 self:Print("Adding to middle of list is not yet supported")
254 return false
255 end
256 return true
257 end
258
259 function bsk:AddPlayerToList(name,list)
260 -- require admin
261 local listIndex = bsk:GetListIndex(list)
262 local slist = {name} -- TODO: support adding to elsewhere besides the end
263 local change = {action="AddPlayerToList",arg={name=name,listIndex=listIndex,slist=slist}}
264 bsk:StartChange(change)
265 if bsk:DoAddPlayerToList(change) then
266 bsk:CommitChange(change)
267 end
268 end
269
270 function bsk:DoRemovePlayer(change)
271
272 -- return true
273 end
274
275 function bsk:RemovePlayer(name)
276 -- from both players and lists
277 end
278
279 function bsk:GetSuicideList(name,list)
280 --self:Print("Calculating changeset for "..name.." from list -")
281 --self:PrintTable(list)
282 local t = {}
283 local ret = {}
284 local pushing = false
285 for i = 1, #list do
286 if list[i] == name then
287 pushing = true
288 end
289 if pushing and (RaidList[list[i]] or ReserveList[list[i]]) then
290 tinsert(ret,list[i])
291 end
292 end
293 return ret
294 end
295
296 function bsk:GetActiveList()
297 return bsk.lists[1] -- todo!
298 end
299
300 function bsk:DoSuicidePlayer(change)
301 local listIndex = change.arg.listIndex
302 local list = bsk.lists[listIndex]
303 local slist = shallowCopy(change.arg.list)
304 -- the goal here is to rotate the suicide list by 1
305 -- then we can just mash it on top of the intersection between the original
306 -- list and the working copy
307 local stemp = shallowCopy(change.arg.list)
308 local temp = table.remove(stemp,1) -- pop
309 tinsert(stemp,temp) -- push_back
310 --bsk:Print(sformat("Before suicide of %s on list %s",slist[1],list.name))
311 --bsk:PrintTable(list)
312 for i = 1, #list do
313 if list[i] == slist[1] then
314 table.remove(slist,1)
315 list[i] = stemp[1]
316 table.remove(stemp,1)
317 end
318 end
319 list.time=change.time
320 --bsk:Print("After")
321 --bsk:PrintTable(list)
322 return true
323 end
324
325 function bsk:SuicidePlayer(name,list)
326 -- require admin
327 local l=bsk:GetActiveList()
328 bsk:PopulateRaidList()
329 local slist=bsk:GetSuicideList(name,l)
330 local listIndex = bsk:GetListIndex(list)
331 local change = {action="SuicidePlayer",arg={names=names,list=slist,listIndex=listIndex}}
332 bsk:StartChange(change)
333 if bsk:DoSuicidePlayer(change) then
334 bsk:CommitChange(change)
335 end
336 end
337
338 -- The following code is from Xinhuan (wowace forum member)
339 -- Pre-create the unitID strings we will use
340 local pID = {}
341 local rID = {}
342 for i = 1, 4 do
343 pID[i] = format("party%d", i)
344 end
345 for i = 1, 40 do
346 rID[i] = format("raid%d", i)
347 end
348 function bsk:PopulateRaidList()
349 local inParty = GetNumPartyMembers()
350 local inRaid = GetNumRaidMembers()
351
352 wipe(RaidList)
353 if inRaid > 0 then
354 for i = 1, inRaid do
355 RaidList[UnitName(rID[i])]=true
356 end
357 elseif inParty > 0 then
358 for i = 1, inParty do
359 RaidList[UnitName(pID[i])]=true
360 end
361 -- Now add yourself as the last party member
362 RaidList[UnitName("player")]=true
363 else
364 -- You're alone
365 RaidList[UnitName("player")]=true
366 end
367 end
368
369 -- undo rules!
370 -- only the most recent event can be undone
371 -- ^^^ on a given list?
372 -- algorithm is easy, given "Suicide A B C"
373 -- just find A,B,C in the list and replace in order from the s message
374 -- while undo is allowed *per-list*, certain events in the stream will
375 -- prevent proper undo, such as add/delete player or add/delete list
376
377
378
379
380 -- reserves
381 function bsk:AddReserve(name)
382 ReserveList[name]=true
383 -- TODO: communicate to others. don't store this in any way.
384 end
385
386 function bsk:RemoveReserve(name)
387 ReserveList[name]=false
388 -- TODO: communicate to others. don't store this in any way.
389 end
390
391
392
393
394
395
396
397
398
399 -- Support functions
400
401 function bsk:GetListIndex(name)
402 for i,v in pairs(bsk.lists) do
403 if v.name == name then
404 return i
405 end
406 end
407 assert(false)
408 end