annotate Lists.lua @ 0:47fac96968e1

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