diff core.lua @ 102:fe04f5c4114a

- expiring cache callback arg, handle new entries coming in during the same loop at the previous ones ALL being removed TODO: rework other "candidate" caches to use this instead - raider snapshots use string tokens for 'online' field - document callback args - CheckBoxSmallLabel preemptively apply proposed fix for ace3 ticket 304 - now that option toggles are not wedged into tight space, revert to using standard CheckBox widget instead of small-label variant (go back if ticket 304 is not fixed for release)
author Farmbuyer of US-Kilrogg <farmbuyer@gmail.com>
date Sat, 04 Aug 2012 22:03:05 +0000
parents f7162a1cadc7
children dc8a23a47b03
line wrap: on
line diff
--- a/core.lua	Wed Aug 01 06:51:52 2012 +0000
+++ b/core.lua	Sat Aug 04 22:03:05 2012 +0000
@@ -14,11 +14,11 @@
 -    sex        1 = unknown/error, 2 = male, 3 = female
 -    level      can be 0 if player was offline at the time
 -    guild      guild name, or missing if unguilded
--    online     1 = online, 2 = offline, 3 = no longer in raid
+-    online     'online', 'offline', 'no_longer' [no longer in raid group]
     [both of these next two fields use time_t values:]
 -    join       time player joined the raid (or first time we've seen them)
 -    leave      time player left the raid (or time we've left the raid, if
-                'online' is not 3)
+                'online' is not 'no_longer')
 
 Common g_loot entry indices:
 - kind          time/boss/loot
@@ -77,7 +77,7 @@
 ------ Addon member data
 ------ Globals
 ------ Expiring caches
------- Ace3 framework stuff
+------ Ace3 framework stuff (callback 'events', search for LCALLBACK)
 ------ Event handlers
 ------ Slash command handler
 ------ On/off
@@ -101,7 +101,7 @@
 OuroLootSV       = nil   -- possible copy of g_loot
 OuroLootSV_saved = nil   -- table of copies of saved texts, default nil; keys
                          -- are numeric indices of tables, subkeys of those
-						 -- are name/forum/attend/date
+                         -- are name/forum/attend/date
 OuroLootSV_hist  = nil
 OuroLootSV_log   = {}
 
@@ -629,61 +629,72 @@
 cache:test(foo)   -- returns false
 cache:test(bar)   -- returns true
    ....5 seconds pass
-   ....bar also gone, cleanup() called
+   ....bar also gone, cleanup() called:
+function cleanup (expired_entries)
+  for i = 1, #expired_entries do  -- this table is in strict FIFO order
+    print(i, expired_entries[i])
+	-- 1  foo
+	-- 2  bar
+  end
+end
 ]]
 do
 	local caches = {}
 	local cleanup_group = _G.AnimTimerFrame:CreateAnimationGroup()
-	local time = _G.time
+	local time, next = _G.time, _G.next
+	local new, del = flib.new, flib.del
 	cleanup_group:SetLooping("REPEAT")
 	cleanup_group:SetScript("OnLoop", function(cg)
 		addon.dprint('cache',"OnLoop firing")
 		local now = time()
-		local alldone = true
+		local actives = 0
+		local expired = new()
 		-- this is ass-ugly
-		for name,c in pairs(caches) do
+		for name,c in next, caches do
 			local fifo = c.fifo
 			local active = #fifo > 0
+			actives = actives + (active and 1 or 0)
 			while (#fifo > 0) and (now > fifo[1].t) do
-				addon.dprint('cache', name, "cache removing", fifo[1].t, "<", fifo[1].m, ">")
-				tremove(fifo,1)
+				local datum = tremove(fifo,1)
+				addon.dprint('cache', name, "cache removing", datum.t, "<", datum.m, ">")
+				c.hash[datum.m] = nil
+				tinsert(expired,datum.m)
+				del(datum)
 			end
 			if active and #fifo == 0 and c.func then
 				addon.dprint('cache', name, "empty, firing cleanup")
-				c:func()
+				c.func(expired)
 			end
-			alldone = alldone and (#fifo == 0)
+			wipe(expired)
 		end
-		if alldone then
+		del(expired)
+		if actives == 0 then
 			addon.dprint('cache',"OnLoop FINISHING animation group")
 			cleanup_group:Finish()
-			_G.collectgarbage()
 		else
 			addon.dprint('cache',"OnLoop done, not yet finished")
 		end
 	end)
 
 	local function _add (cache, x)
-		local datum = { t=time()+cache.ttl, m=x }
+		assert(type(x)~='number')
+		local datum = new()
+		datum.m = x
+		datum.t = time() + cache.ttl
 		cache.hash[x] = datum
 		tinsert (cache.fifo, datum)
 		if not cleanup_group:IsPlaying() then
 			addon.dprint('cache', cache.name, "with entry", datum.t, "<", datum.m, "> STARTING animation group")
-			cache.cleanup:SetDuration(1)  -- hmmm
 			cleanup_group:Play()
 		end
 	end
 	local function _test (cache, x)
-		-- FIXME This can return false positives, if called after the onloop
-		-- fifo has been removed but before the GC has removed the weak entry.
-		-- What to do, what to do...  try forcing a GC during alldone.
 		return cache.hash[x] ~= nil
 	end
 
 	function create_new_cache (name, ttl, on_alldone)
 		-- setting OnFinished for cleanup fires at the end of each inner loop,
 		-- with no 'requested' argument to distinguish cases.  thus, on_alldone.
-		-- FWIW, on_alldone is passed this table as its sole argument:
 		local c = {
 			ttl = ttl,
 			name = name,
@@ -691,10 +702,13 @@
 			test = _test,
 			cleanup = cleanup_group:CreateAnimation("Animation"),
 			func = on_alldone,
+			-- Testing merging these two (_add's 'x' must not be numeric)
 			fifo = {},
-			hash = _G.setmetatable({}, {__mode='kv'}),
+			--hash = {},
 		}
-		c.cleanup:SetOrder(1)
+		c.hash = c.fifo
+		c.cleanup:SetOrder(1)  -- [1,100] range within parent animation
+		c.cleanup:SetDuration(0.8)  -- hmmm
 		caches[name] = c
 		return c
 	end
@@ -1052,34 +1066,91 @@
 	end
 end
 
--- We don't want to trigger plugins and another addons as soon as something
--- interesting happens, because a nontrivial amount of work happens "quickly"
--- after the interesting event:  cleanups/fixups, improvs from network, etc.
--- So firing a callback is delayed ever so briefly by human standards.
---
--- Can't *quite* use the expiring caches for this, but that's okay.
+--[[ LCALLBACK
+Standard ace3-style callback registration and dispatching.  All player names
+are simple (uncolored) strings.  The "uniqueID" always refers to the unique
+tag string stored as 'unique' in loot entries and used as keys in history.
+Item IDs are always of numeric type.
+
+'Activate', enabled_p, rebroadcast_p, threshold
+    The two boolean predicates are self-explanatory.  The threshold is an
+    ITEM_QUALITY_* constant integer.
+
+'Deactivate'
+    After all system events have been unregistered.
+
+'Reset'
+    Clicking "Clear Loot", after all data manipulation is finished.
+
+'NewBoss', boss
+    Boss event triggered by a local bossmod (DBM, etc) or a remote OL tracker.
+    Argument is a g_loot table entry of kind=='boss'.
+
+'NewBossEntry', boss
+    New row in primary EOI table of kind=='boss'.  Includes all 'NewBoss'
+    occasions, plus manual boss additions, testing, etc.  Arg same as NewBoss.
+
+'NewLootEntry', loot
+'DelLootEntry', loot
+    New or removed row in primary EOI table of kind=='loot'.  Argument is a
+    g_loot table entry of kind=='loot'.
+
+'NewEOIEntry', entry
+'DelEOIEntry', entry
+    New or removed row in primary EOI table, of any kind.  Argument is the
+    g_loot entry, already inserted into or removed from g_loot.
+
+'NewHistory', player_name, uniqueID
+'DelHistory', player_name, uniqueID
+    New or removed entry in player history.  Name argument self-explanatory.
+    ID is the corresponding loot, already inserted into or removed from the
+    history structures.
+
+'Reassign', uniqueID, itemID, loot, from_player_name, to_player_name
+    Loot reassigned from one player to another.  Loot represented by the
+    unique & item IDs, and the g_loot entry of kind=='loot'.  The player
+    names are self-explanatory.
+
+'MarkAs', uniqueID, itemID, loot, old_disposition, new_disposition
+    The "Mark as <x>" action (as if from the item right-click menu, possibly
+    from a remote tracker) has finished.  ID & loot arguments are as in
+    'Reassign'.  The old/new dispositions are those of the g_loot index
+    "disposition" (described at the top of core.lua), with the added possible
+    value of "normal" meaning exactly that.
+]]
 do
-	local mtnewindex = function() error("This table is read-only", 3) end
+	-- We don't want to trigger plugins or other addons as soon as something
+	-- interesting happens, because a nontrivial amount of work happens "soon"
+	-- after the interesting event:  cleanups/fixups, improvs from network,
+	-- etc.  So firing a callback is delayed ever so briefly by human scales.
+	--
+	-- For data safety, we replace any table arguments with read-only proxies
+	-- before passing them to the callbacks.  The goal is to prevent accidents,
+	-- not fraud.
+	local unpack, setmetatable = _G.unpack, _G.setmetatable
+	local mtnewindex = function() --[[local]]error("This table is read-only", 3) end
 	local function make_readonly (t)
-		return _G.setmetatable({}, {
+		return setmetatable({}, {
 			__newindex = mtnewindex,
 			__index = t,
 			__metatable = false,
+			__tostring = getmetatable(t) and getmetatable(t).__tostring,
 		})
 	end
 
-	local unpack = _G.unpack
-	local function F (t)
-		addon.callbacks:Fire (unpack(t))
-		flib.del(t)
-	end
+	local queue = create_new_cache ('callbacks', 1.2, function (allcbs)
+		for _,c in ipairs(allcbs) do
+			addon.callbacks:Fire (unpack(c))
+			flib.del(c)
+		end
+	end)
 	function addon:Fire (...)
 		self.dprint('callback', ...)
 		local capture = flib.new(...)
 		for k,v in ipairs(capture) do if type(v) == 'table' then
 			capture[k] = make_readonly(v)
 		end end
-		self:ScheduleTimer (F, 1.2, capture)
+		queue:add(capture)
 	end
 end
 
@@ -1139,7 +1210,7 @@
 	      IsInInstance, UnitIsConnected, UnitClass, UnitRace, UnitSex,
 		  		UnitLevel, UnitInRaid, UnitIsVisible, GetGuildInfo, GetRaidRosterInfo
 	local time, difftime = time, difftime
-	local R_ACTIVE, R_OFFLINE, R_LEFT = 1, 2, 3
+	local R_ACTIVE, R_OFFLINE, R_LEFT = 'online', 'offline', 'no_longer'
 
 	local lastevent, now = 0, 0
 	local redo_count = 0
@@ -2140,6 +2211,8 @@
 		end
 		wipe(candidates)
 	end
+	-- Ten seconds is a long time, but occasionally DBM takes for-EVAH to
+	-- decide that a fight is over.
 	local recent_boss = create_new_cache ('boss', 10, fixup_durations)
 
 	-- Similar to _do_loot, but duration+ parms only present when locally generated.