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