changeset 105:c6c748a5823b tip

Collaborative list trimming 80% functional Updates for the zone-in problem
author John@Yosemite-PC
date Sun, 06 May 2012 08:33:34 -0400
parents 9aa2dcbbdc87
children
files Admin.lua Comm.lua Lists.lua
diffstat 3 files changed, 131 insertions(+), 51 deletions(-) [+]
line wrap: on
line diff
--- a/Admin.lua	Sun May 06 08:30:15 2012 -0400
+++ b/Admin.lua	Sun May 06 08:33:34 2012 -0400
@@ -15,8 +15,13 @@
 
 adminList = {}
 
+local lastZoneTime = 0
+local function ZoneChanged()
+    lastZoneTime = _G.time()
+end
 local function GuildRosterUpdate()
     local guildInfoText = _G.GetGuildInfoText()
+    if lastZoneTime+5 > _G.time() then return end -- silly workaround - GGIT() returns "" when you zone in
     local newAdminList = {}
     for line in guildInfoText:gmatch("[^\r\n]+") do
         local l,r = line:match("(.*):(.*)") -- could use wow strsplit had I known about it before writing this
@@ -67,60 +72,94 @@
 function RemoteAdminUpdateReceived(sender,remoteAdminStatusTable)
     if not admin then return end
 
-    local rs = remoteAdminStatusTable
-    local events = {} -- record all timestamps seen in this update
+    local rs,base,changes = unpack(remoteAdminStatusTable)
     for i,_ in pairs(adminList) do -- update each admin's entry in your own DB
 
-        -- grab the db copy and the incoming copy for that admin
-        if not db.profile.adminStatus then db.profile.adminStatus = {} end
-        local dbs = db.profile.adminStatus[i] or {base=0, changes={}}
-        local ics = rs[i] or {base=0, changes={}}
+        if i ~= me then
+            -- grab the db copy and the incoming copy for that admin
+            if not db.profile.adminStatus then db.profile.adminStatus = {} end
+            local dbs = db.profile.adminStatus[i] or {base=0, changes={}}
+            local ics = rs[i] or {base=0, changes={}}
 
-        -- figure out which is better and keep that one
-        -- winning criteria:
-        --  * broadcast was actually from that person (ie best
-        --  verification possible)
-        --  * newer base
-        --  * same base, more entries
-        --  * todo: see if date last observed is a better option
+            -- figure out which is better and keep that one
+            -- winning criteria:
+            --  * broadcast was actually from that person (ie best
+            --  verification possible)
+            --  * newer base
+            --  * same base, more entries
+            --  * todo: see if date last observed is a better option
 
-        if i==sender then
-            db.profile.adminStatus[i] = ics
-        elseif ics.base > dbs.base or (ics.base==dbs.base and getn(ics.changes) > getn(dbs.changes)) then
-            db.profile.adminStatus[i] = ics
+            if i==sender then
+                db.profile.adminStatus[i] = ics
+            elseif ics.base > dbs.base or (ics.base==dbs.base and getn(ics.changes) > getn(dbs.changes)) then
+                db.profile.adminStatus[i] = ics
+            end
         end
     end
 
-    local rss = rs[sender]
+    if changes and getn(changes)>0 then
+        IntegrateChangeDiff(base,changes)
+    end
 
-    -- now figure out what I'm missing - and ask for it!
+    -- trim if the replies said it's safe
 
-    -- construct a hash table of all entries that the sender has / should have
-    local entries = {}
-    for i,v in pairs(rs) do
-        if v.changes then 
-            for j,k in pairs(v.changes) do
-                entries[k.time] = true
+    local trimPoint = db.profile.time
+    -- come up with a tentative starting point (max base)
+    for i,v in pairs(db.profile.adminStatus) do
+        if v.base > trimPoint then trimPoint = v.base end
+    end
+
+    local pointer = {}
+    -- fast forward pointers *past* the trimPoint in each list
+    for p,l in pairs(db.profile.adminStatus) do
+        pointer[p] = 1
+        for i,v in ipairs(l.changes) do
+            if v <= trimPoint then
+                pointer[p] = i+1 -- "past" the trim point - might point to nil
+            else
+                break
             end
         end
     end
-    -- now go back and scrub my own keys from that list
+    print("pointers")
+    PrintTable(pointer)
+
     for i,v in ipairs(db.profile.changes) do
-        entries[v.time] = nil
+        if v.time > trimPoint then -- advance to the trim point estimate before doing anything
+            -- into uncharted territory, let's see how far we can push it
+            local continue = true
+            for p,t in pairs(pointer) do
+                local dbpapct = db.profile.adminStatus[p].changes[t] 
+                if dbpapct and dbpapct==v.time then
+                    pointer[p] = pointer[p]+1
+                else
+                    continue=false
+                    break
+                end
+                trimPoint = v.time
+            end
+            if not continue then break end
+        end
     end
-    -- what's left is what I need to ask for
-    local request = {}
-    for i,v in pairs(entries) do
-        if v then table.insert(request,i) end
+    print("pointers")
+    PrintTable(pointer)
+    if trimPoint > db.profile.time then
+
+        -- protect trimming back to beginning of previous month so we're not
+        -- trimming like madmen
+        local ct = _G.date("*t",_G.time())
+        ct.month = ct.month-1
+        if ct.month < 1 then ct.month = 12 end
+        ct.day = 1
+        ct.hour = 1
+        ct.minute = 0
+        ct.sec = 0
+        local ts = _G.time(ct)
+        print("I think I can trim to",trimPoint,ts)
+        TrimLists(ts)
+    else
+        print("no bother trimming",trimPoint)
     end
-    table.sort(request)
-    Comm:RequestSpecificChanges(request,sender)
-
-    for
-        -- specifically leaving this broken. note to self.
-        -- this still isn't good enough. it doesn't communicate an admin's
-        -- present working state. like if they had put in new changes since
-        -- loading up. or learned of some changes to fill in an old gap
 end
 
 function InitializeAdmin()
@@ -153,6 +192,9 @@
     end
 
     event:RegisterEvent("GUILD_ROSTER_UPDATE",GuildRosterUpdate)
+    event:RegisterEvent("ZONE_CHANGED_NEW_AREA",ZoneChanged)
+    event:RegisterEvent("ZONE_CHANGED_INDOORS",ZoneChanged)
+    event:RegisterEvent("ZONE_CHANGED",ZoneChanged)
     _G.GuildRoster() -- will eventually force the event to fire
 
     if me == "Breuemama" then -- debugging only
--- a/Comm.lua	Sun May 06 08:30:15 2012 -0400
+++ b/Comm.lua	Sun May 06 08:33:34 2012 -0400
@@ -111,7 +111,7 @@
 
             local success, o = CreateChangeDiff(remoteBase,t)
             if success and getn(o) > 0 then
-                Send("CU",o)
+                Send("CU",{remoteBase,o})
             end
             if not success then -- push
                 print("Received request at differing timebase",remoteBase,db.profile.time," ... pushing")
@@ -122,7 +122,10 @@
 
     ["CU"] = function(self,packet,sender,isloop) -- blindly trust an admin loot master
         if isloop then return end
-        IntegrateChangeDiff(packet)
+        local base,changes = unpack(packet)
+        if base and base > 0 and changes and getn(changes) > 0 then
+            IntegrateChangeDiff(base,changes)
+        end
     end,
 
     ["RequestCatchup"] = function(self,from) -- todo: integrate from
@@ -153,14 +156,16 @@
         print("SA")
         if isloop then return end
         if admin then
-            SendAdmin("SR",onloadAdminStatus) -- SR ... prevent infinite loop please
+            -- todo: only send back a diff. this is barbaric.
+            SendAdmin("SR",{onloadAdminStatus,db.profile.time,db.profile.changes}) -- SR ... prevent infinite loop please
             RemoteAdminUpdateReceived(sender,packet)
         end
     end,
 
     ["SendAdminStatusTable"] = function(self)
         if admin then
-            SendAdmin("SA",onloadAdminStatus) -- only send onload status, since that's the only data you're guaranteed to have safely safed
+            SendAdmin("SA",{onloadAdminStatus,db.profile.time}) -- only send onload status, since that's the only data you're guaranteed to have safely saved
+            -- todo: see whether the above comment is correct, or the code
         end
     end,
 }
--- a/Lists.lua	Sun May 06 08:30:15 2012 -0400
+++ b/Lists.lua	Sun May 06 08:33:34 2012 -0400
@@ -8,7 +8,6 @@
 
 
 -- TODO: switch all action functions to use identifiers rather than names
--- TODO: collaborative list trimming
 -- TODO: collapse slists into delimited strings for space (premature optimization?)
 -- TODO: organize working state data a little more carefully - hard to keep
 -- track of all the arrays that are floating out there
@@ -850,19 +849,31 @@
         end
     end
 
-    -- apply first half
+    print("before -",#before)
+    --return
+
+    --apply first half
     CreateWorkingStateFromChanges(before)
 
     -- save this state permanently; trim the changes permanently
     LootLists:SaveToDB(db)
     PersonList:SaveToDB(db)
     while db.profile.changes ~= nil and db.profile.changes[1] ~= nil and db.profile.changes[1].time <= time do
-        table.remove(db.profile.changes,1)
+       table.remove(db.profile.changes,1)
     end
+    print("reapplying ",_G.getn(db.profile.changes))
     db.profile.time = lastTime
 
     -- using the trimmed list and the new bases, recreate the working state
-    CreateWorkingStateFromChanges(db.profile.changes)
+    --CreateWorkingStateFromChanges(db.profile.changes)
+
+    for i,v in ipairs(db.profile.changes) do
+        ProcessChange(v)
+    end
+
+    if changelistener then
+       changelistener:dataevent()
+    end
 end
 
 
@@ -943,7 +954,7 @@
     end
 end
 
-function IntegrateChangeDiff(remoteChanges) -- todo: check against remoteBase before committing to insanity
+function IntegrateChangeDiff(remoteBase,remoteChanges)
     local c = remoteChanges
     local old = db.profile.changes
 
@@ -953,9 +964,20 @@
     local cp = 1
 
     local no = getn(old)
+
+    local worthRefresh = false -- only worth refreshing if we take changes from c
+
+    -- if remotebase is older, don't integrate anything prior to db.profile.time. if they're newer, it's ok to merge in their changes
+    if remoteBase < db.profile.time then
+        while c[1] and c[1].time < db.profile.time do
+            table.remove(c,1)
+        end
+    end
+
     local nc = getn(c)
 
-    if no == 0 then
+    if no == 0 and nc > 0 then
+        worthRefresh = true
         db.profile.changes = c
     else
         while op <= no or cp <= nc do -- lists are pre-sorted. insertion merge them
@@ -963,9 +985,17 @@
                 table.insert(new,old[op])
                 op = op + 1
             elseif op > no then
+                worthRefresh=true
                 table.insert(new,c[cp])
                 cp = cp + 1
+            elseif c[cp].time == old[op].time then
+                -- todo: even though they're the same timestamp, still compare
+                -- contents for sanity
+                table.insert(new,old[cp])
+                cp = cp + 1
+                op = op + 1
             elseif c[cp].time < old[op].time then
+                worthRefresh=true
                 table.insert(new,c[cp])
                 cp = cp + 1
             elseif c[cp].time > old[op].time then
@@ -979,8 +1009,10 @@
         db.profile.changes = new
     end
 
-    CreateWorkingStateFromChanges(db.profile.changes)
-    if changeListener then
-        changeListener:DataEvent()
+    if worthRefresh then
+        CreateWorkingStateFromChanges(db.profile.changes)
+        if changelistener then
+            changelistener:dataevent()
+        end
     end
 end