Mercurial > wow > breuesk
diff Lists.lua @ 0:47fac96968e1
First commit
author | John@Yosemite-PC |
---|---|
date | Fri, 02 Mar 2012 00:15:09 -0500 |
parents | |
children | 21c58930f74e |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lists.lua Fri Mar 02 00:15:09 2012 -0500 @@ -0,0 +1,408 @@ +-- lists consist of three things +-- 1) a base state - agreed on by one or more list holders +-- 2) change sets - incremental list changes (can be rolled forwards or +-- backwards) +-- 3) working state - not saved because it can be so easily calculated +-- +-- A separate user list is held - lists index into this + + +-- TODO: rename player + + + +bsk.lists = {} +bsk.players = {} + +local RaidList = {} +local ReserveList = {} +local activeList = 0 -- temporary + +local tinsert = table.insert +local sformat = string.format +local getn = table.getn + +function bsk:tcopy(to, from) + for k,v in pairs(from) do + if(type(v)=="table") then + to[k] = {} + bsk:tcopy(to[k], v); + else + to[k] = v; + end + end +end +local shallowCopy = function(t) + local u = { } + for k, v in pairs(t) do u[k] = v end + return setmetatable(u, getmetatable(t)) +end + +function bsk:PrintTable(table, depth) + depth = depth or "" + if not table then return end + for i,v in pairs(table) do + if( type(v) == "string" ) then + self:Print(depth .. i .. " - " .. v) + elseif( type(v) == "number" ) then + self:Print(depth .. i .. " - " .. tostring(v)) + elseif( type(v) == "table" ) then + self:Print(depth .. i .." - ") + self:PrintTable(v,depth.." ") + elseif( type(v) == "boolean" ) then + self:Print(depth .. i .. " - " .. tostring(v)) + else + self:Print(depth .. i .. " - not sure how to print type: " .. type(v) ) + end + end +end + +-- Debugging {{{ +function bsk:PrintLists() + bsk:PrintTable(bsk.lists) +end +function bsk:PrintChanges() + bsk:PrintTable(bsk.db.profile.changes) +end +function bsk:PrintPlayers() + bsk:PrintTable(bsk.players) +end +--}}} + +function bsk:CreateWorkingStateFromChanges() + local playerBase = self.db.profile.players + local listBase = self.db.profile.listBase + local changes = self.db.profile.changes + + -- copy the base to the working state + wipe(bsk.lists) + wipe(bsk.players) + bsk:tcopy(bsk.lists,listBase) + bsk:tcopy(bsk.players,playerBase) + + -- now just go through the changes list applying each + for i,v in pairs(changes) do + bsk:ProcessChange(v) + end +end + +function bsk:CreateChange(change) + -- sanity + assert(change) + assert(change.action) + assert(change.arg) + + bsk:StartChange(change) + bsk:CommitChange(change) +end + +function bsk:StartChange(change) + local changes = self.db.profile.changes + change.time = time() + local n = getn(changes) + if n > 0 then + if changes[n].time >= change.time then + change.time = changes[n].time + 1 + end + end +end + +function bsk:CommitChange(change) + local changes = self.db.profile.changes + tinsert(changes,change) + -- TODO: broadcast change +end + + +-- timestamp logic: +-- use time() for comparisons - local clients use date() to make it pretty. only +-- dowisde - we can't have a server timestamp. Which kind of sucks, but it turns +-- out you can change timezones when you enter an instance server, so you really +-- never know what time it is. +-- There's unfortunately no hard-and-proven method for determining the true time +-- difference between local time and server time. You can't just query the two +-- and compare them because your server timezone can change (!) if you go into +-- an instance server with a different timezone. This is apparently a big +-- problem on Oceanic realms. +-- +-- Timestamp handling (brainstorming how to deal with drift): +-- (not an issue) if someone sends you time in the future, update your offset so you won't +-- send out events in the "past" to that person +-- (not an issue - using local UTC now) on change-zone-event: check if you've changed timezones - might need update +-- each time you add a change, check the tail of the change list; if this is +-- less than that, you have a problem. Print a message. if this is equal, then +-- that's ok, just bump it by 1 second. This could happen in the case of, say, +-- spam-clicking the undo button or adding names to the list. The recipients +-- should be ok with this since they'll follow the same algorithm. The only +-- real chance for a problem is if two people click within the 1 second window? +-- if someone sends you a past event, +-- it's ok if it's newer than anything in the changes list +-- otherwise ... causality has been violated. +-- Whenever an admin signon event happens, have the admins each perform a +-- timestamp check. Issue warnings for anyone with a clock that's more than +-- X seconds out of sync with the others. Seriously, why isn't NTP a standard +-- setting on all operating systems ... + +function bsk:ProcessChange(change) + if change.action == "AddPlayer" then + bsk:DoAddPlayer(change) + elseif change.action == "CreateList" then + bsk:DoCreateList(change) + elseif change.action == "AddPlayerToList" then + bsk:DoAddPlayerToList(change) + elseif change.action == "SuicidePlayer" then + bsk:DoSuicidePlayer(change) + else + bsk:Print("Unknown message encountered") + bsk:PrintTable(change) + assert(false) + end +end + + +-- +-- The actual actions for changes start here +-- +-- Each action occurs as a pair of functions. The bsk:Action() function is from +-- a list admin's point of view. Each will check for admin status, then create a +-- change bundle, call the handler for that change (ie the DoAction func), and +-- then record/transmist the bundle. These are simple and repetitive functions. +-- +-- The bsk:DoAction() function is tasked with executing the bundle and is what +-- non-admins and admins alike will call to transform their working state via a +-- change packet. Each Do() function will accept *only* a change packet, and +-- it's assumed that the change has been vetted elsewhere. These are very blunt +-- routines. +-- +-- Note that "undo" has no special voodoo to it. It's basically a change that +-- reverses the prior change on the stack. + +-- Players list +function bsk:DoAddPlayer(change) + assert(change) + assert(change.arg.guid) + local arg = change.arg + -- require admin + local players = bsk.players + local name = arg.name + local guid = arg.guid + assert(players[guid]==nil) + players[guid] = name + players.time=change.time + return true +end + +function bsk:AddPlayer(name) + local players = bsk.players + local guid = UnitGUID(name) + -- TODO: check guid to be sure it's a player + if not guid then + self:Print(sformat("Could not add player %s - they must be in range or group",name)) + return + end + if players[guid] and players[guid] ~= name then + self:Print(sformat("Namechange detected for %s - new is %s, please rename the existing entry", players[guid], name)) + return + end + if players[guid] ~= nil then + self:Print(sformat("%s is already in the players list; disregarding", name)) + return + end + local change = {action="AddPlayer",arg={name=name,guid=guid}} + if bsk:DoAddPlayer(change) then + bsk:CreateChange(change) + end +end + +function bsk:CreateFakeLists() + -- testing only +end + +function bsk:DoCreateList(change) + -- TODO: this segment will probably be useful as bsk:SearchForListByName + local lists = bsk.lists + for i,v in pairs(lists) do + if v.name == change.arg.name then + self:Print(sformat("List %s already exists",v.name)) + return false + end + end + tinsert(lists,{name=change.arg.name,time=change.time}) + return true +end + +function bsk:CreateList(name) + -- require admin + local change={action="CreateList",arg={name=name}} + bsk:StartChange(change) + self:Print("Creating ... " .. name) + if bsk:DoCreateList(change) then + bsk:CommitChange(change) + end +end + +function bsk:DoAddPlayerToList(change) + local listIndex = change.arg.listIndex + local slist = change.arg.slist + local list = bsk.lists[listIndex] + + if #slist == 1 then -- end of list insertion - just one person + tinsert(list,slist[1]) + list.time = change.time + else + self:Print("Adding to middle of list is not yet supported") + return false + end + return true +end + +function bsk:AddPlayerToList(name,list) + -- require admin + local listIndex = bsk:GetListIndex(list) + local slist = {name} -- TODO: support adding to elsewhere besides the end + local change = {action="AddPlayerToList",arg={name=name,listIndex=listIndex,slist=slist}} + bsk:StartChange(change) + if bsk:DoAddPlayerToList(change) then + bsk:CommitChange(change) + end +end + +function bsk:DoRemovePlayer(change) + + -- return true +end + +function bsk:RemovePlayer(name) + -- from both players and lists +end + +function bsk:GetSuicideList(name,list) + --self:Print("Calculating changeset for "..name.." from list -") + --self:PrintTable(list) + local t = {} + local ret = {} + local pushing = false + for i = 1, #list do + if list[i] == name then + pushing = true + end + if pushing and (RaidList[list[i]] or ReserveList[list[i]]) then + tinsert(ret,list[i]) + end + end + return ret +end + +function bsk:GetActiveList() + return bsk.lists[1] -- todo! +end + +function bsk:DoSuicidePlayer(change) + local listIndex = change.arg.listIndex + local list = bsk.lists[listIndex] + local slist = shallowCopy(change.arg.list) + -- the goal here is to rotate the suicide list by 1 + -- then we can just mash it on top of the intersection between the original + -- list and the working copy + local stemp = shallowCopy(change.arg.list) + local temp = table.remove(stemp,1) -- pop + tinsert(stemp,temp) -- push_back + --bsk:Print(sformat("Before suicide of %s on list %s",slist[1],list.name)) + --bsk:PrintTable(list) + for i = 1, #list do + if list[i] == slist[1] then + table.remove(slist,1) + list[i] = stemp[1] + table.remove(stemp,1) + end + end + list.time=change.time + --bsk:Print("After") + --bsk:PrintTable(list) + return true +end + +function bsk:SuicidePlayer(name,list) + -- require admin + local l=bsk:GetActiveList() + bsk:PopulateRaidList() + local slist=bsk:GetSuicideList(name,l) + local listIndex = bsk:GetListIndex(list) + local change = {action="SuicidePlayer",arg={names=names,list=slist,listIndex=listIndex}} + bsk:StartChange(change) + if bsk:DoSuicidePlayer(change) then + bsk:CommitChange(change) + end +end + +-- The following code is from Xinhuan (wowace forum member) +-- Pre-create the unitID strings we will use +local pID = {} +local rID = {} +for i = 1, 4 do + pID[i] = format("party%d", i) +end +for i = 1, 40 do + rID[i] = format("raid%d", i) +end +function bsk:PopulateRaidList() + local inParty = GetNumPartyMembers() + local inRaid = GetNumRaidMembers() + + wipe(RaidList) + if inRaid > 0 then + for i = 1, inRaid do + RaidList[UnitName(rID[i])]=true + end + elseif inParty > 0 then + for i = 1, inParty do + RaidList[UnitName(pID[i])]=true + end + -- Now add yourself as the last party member + RaidList[UnitName("player")]=true + else + -- You're alone + RaidList[UnitName("player")]=true + end +end + +-- undo rules! +-- only the most recent event can be undone +-- ^^^ on a given list? +-- algorithm is easy, given "Suicide A B C" +-- just find A,B,C in the list and replace in order from the s message +-- while undo is allowed *per-list*, certain events in the stream will +-- prevent proper undo, such as add/delete player or add/delete list + + + + +-- reserves +function bsk:AddReserve(name) + ReserveList[name]=true + -- TODO: communicate to others. don't store this in any way. +end + +function bsk:RemoveReserve(name) + ReserveList[name]=false + -- TODO: communicate to others. don't store this in any way. +end + + + + + + + + + +-- Support functions + +function bsk:GetListIndex(name) + for i,v in pairs(bsk.lists) do + if v.name == name then + return i + end + end + assert(false) +end