changeset 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
files AceGUIWidget-CheckBoxSmallLabel.lua core.lua options.lua text_tabs.lua
diffstat 4 files changed, 130 insertions(+), 42 deletions(-) [+]
line wrap: on
line diff
--- a/AceGUIWidget-CheckBoxSmallLabel.lua	Wed Aug 01 06:51:52 2012 +0000
+++ b/AceGUIWidget-CheckBoxSmallLabel.lua	Sat Aug 04 22:03:05 2012 +0000
@@ -111,6 +111,9 @@
 			self.frame:Disable()
 			self.text:SetTextColor(0.5, 0.5, 0.5)
 			SetDesaturation(self.check, true)
+			if self.desc then
+				self.desc:SetTextColor(0.5, 0.5, 0.5)
+			end
 		else
 			self.frame:Enable()
 			self.text:SetTextColor(1, 1, 1)
@@ -119,6 +122,9 @@
 			else
 				SetDesaturation(self.check, false)
 			end
+			if self.desc then
+				self.desc:SetTextColor(1, 1, 1)
+			end
 		end
 	end,
 
--- 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.
--- a/options.lua	Wed Aug 01 06:51:52 2012 +0000
+++ b/options.lua	Sat Aug 04 22:03:05 2012 +0000
@@ -69,7 +69,7 @@
 local error, assert = addon.error, addon.assert
 
 local function mktoggle (opt, label, width, desc, opt_func)
-	local w = mkbutton("CheckBoxSmallLabel", nil, "", desc)
+	local w = mkbutton("CheckBox", nil, "", desc)
 	w:SetRelativeWidth(width)
 	w:SetType("checkbox")
 	w:SetLabel(label)
@@ -510,7 +510,7 @@
 		grp:SetRelativeWidth(0.60)
 		grp:SetTitle("Output of debugging messages")
 
-		w = AceGUI:Create("CheckBoxSmallLabel")
+		w = AceGUI:Create("CheckBox")
 		w:SetFullWidth(true)
 		w:SetType("checkbox")
 		w:SetLabel("master toggle")
@@ -521,7 +521,7 @@
 		end)
 		grp:AddChild(w)
 		for d,v in pairs(addon.debug) do
-			w = AceGUI:Create("CheckBoxSmallLabel")
+			w = AceGUI:Create("CheckBox")
 			w:SetFullWidth(true)
 			w:SetType("checkbox")
 			w:SetLabel(d)
@@ -549,7 +549,7 @@
 		local simple = AceGUI:Create("SimpleGroup")
 		simple:SetLayout("List")
 		simple:SetRelativeWidth(0.35)
-		w = AceGUI:Create("CheckBoxSmallLabel")
+		w = AceGUI:Create("CheckBox")
 		--w:SetRelativeWidth(0.35)
 		w:SetFullWidth(true)
 		w:SetType("checkbox")
@@ -620,7 +620,7 @@
 			addon:Print("Can do nothing; DBM testing mod wasn't loaded.")
 		end
 	end)
-	w:SetDisabled(addon.bossmod_registered ~= 'DBM')
+	w:SetDisabled(addon.bossmod_registered ~= 'DBM')  -- set by :Activate
 	container:AddChild(w)
 
 	w = mkbutton("GC", [[full GC cycle]])
@@ -824,13 +824,15 @@
 --[[
 PLUGIN is the module table itself.  (This does not actually have to be
   a plugin created with :[Constrained]NewModule, as long as it has a
-  GetName method and other parameters here are used appropriately.)
+  GetName method and
+  - TEXT is passed
+  - if OPTIONS is a table, then OPTIONS.args.name exists
 PARENT is nil to register in the tree list directly.
 TEXT is either the text to display in the tree list, or nil to use the
   moduleName field out of PLUGIN (not :GetName()).
 OPTIONS is either
-  I)  a function to call directly
-  II) an aceconfig-style options table
+  I)  an aceconfig-style options table
+  II) a function to call directly
 
 (I) Will augment options.args.name with coloring/sizing, if it exists.
     (Set options.args.name.name before registering.)  Will create it if not
@@ -851,7 +853,9 @@
 	text = text or plugin.moduleName
 
 	local handler
-	local pdb = self.db:GetNamespace (plugin.moduleName, --[[silent=]]true)
+	local pdb = plugin.moduleName and
+	            self.db:GetNamespace (plugin.moduleName, --[[silent=]]true)
+
 	if type(options) == 'table' then
 		-- AceConfig-style options table
 		aceconfig_list[code] = true
--- a/text_tabs.lua	Wed Aug 01 06:51:52 2012 +0000
+++ b/text_tabs.lua	Sat Aug 04 22:03:05 2012 +0000
@@ -148,7 +148,7 @@
 
 	-- Assumption:  everybody is packed into the first N groups.
 	if raidertable then for name,info in pairs(raidertable) do
-		if info.online ~= 3 then   -- 3 == left the raid
+		if info.online ~= 'no_longer' then   -- 'no_longer' == left the raid
 			if (info.subgroup or (NUM_RAID_GROUPS+1)) <= max_group_number then
 				tins (ingroups, name)
 			else