oUF-wow/oUF

Can you add support for CLEU health updates for faster health bar updates? [classic]

missesjones opened this issue · 2 comments

Background

Just wanted to mention this for starters, get your feedback. I might even have time to implement it and raise a PR, but let's start here and let me know your thoughts.

CLEU seems to arrive a few hundred milliseconds ahead of UNIT_HEALTH. I think this is a long-standing thing, I saw some old forum posts from 2008 talking about this exact issue. I never really paid close attention until now since I've been playing classic, so it's more news to me than it probably is anybody else. Not a huge issue, didn't really affect gameplay that much, but in hindsight it's like whoa.

In my limited testing with /etrace, I was seeing a delay between CLEU and UNIT_HEALTH events anywhere from ~0-500ms! Anybody running Grid2 or Vuhdo will see their bar update sooner than somebody running, say, LunaUnitFrames.

Describe the solution you'd like
Register for COMBAT_LOG_EVENT_UNFILTERED to be able to update health bars faster, similar to Grid2 and Vuhdo.

Making it optional, disabled by default, might be a good idea if you want to preserve existing behavior, and then devs using oUF can offer a way to turn in on perhaps.

Describe alternatives you've considered
Grid2 has an option (disabled by default) for enabling fast health bar updates based on events from CLEU.

Vuhdo does it be default, IIRC.

Grid2 maps the CLEU event into a shape similar to what UNIT_HEALTH provides. However, since CLEU provides a GUID instead of a unit id, they maintain a separate table of roster members keyed by GUID with values of unit id, updated during roster change events. Whenever a CLEU health update comes in, they lookup the unit id to see if it's a known party/raid member, then they proceed as normal.

It appears they register for both CLEU and UNIT_HEALTH, since apparently it might be possible for CLEU to occasionally fire after, but in my very limited testing I didn't observe that.

Haven't looked as closely at Vuhdo, but I'd imagine it's similar there.

Here's a quick and dirty take on it, inspired partly by what Grid2 does. This forces the updated health value from the CLEU event through to the frame.

I'm not familiar enough with oUF, nor super happy with this hack, to want to raise a PR, but perhaps you can run with this and refactor it into a better shape.

diff --git a/libs/oUF/elements/health.lua b/libs/oUF/elements/health.lua
index 951503a..9b361c1 100755
--- a/libs/oUF/elements/health.lua
+++ b/libs/oUF/elements/health.lua
@@ -142,7 +142,7 @@ local function Update(self, event, unit)
 		element:PreUpdate(unit)
 	end
 
-	local cur, max = UnitHealth(unit), UnitHealthMax(unit)
+	local cur, max = self.Health.forcedHealth or UnitHealth(unit), UnitHealthMax(unit)
 	local disconnected = not UnitIsConnected(unit)
 
 	element:SetMinMaxValues(0, max)
diff --git a/libs/oUF/ouf.lua b/libs/oUF/ouf.lua
index d3ba9bd..cc471be 100755
--- a/libs/oUF/ouf.lua
+++ b/libs/oUF/ouf.lua
@@ -853,3 +853,77 @@ if(global) then
 		_G[global] = oUF
 	end
 end
+
+
+
+-- player guid cache based on oUF objects
+local guids = {}
+-- unit to guid cache; helps identify which objects to update
+local units = {}
+local health_cache = {}
+local HealthEvents = {
+	SPELL_DAMAGE = -15,
+	RANGE_DAMAGE = -15,
+	SPELL_PERIODIC_DAMAGE = -15,
+	DAMAGE_SHIELD = -15,
+	DAMAGE_SPLIT = -15,
+	ENVIRONMENTAL_DAMAGE = -13,
+	SWING_DAMAGE = -12,
+	SPELL_PERIODIC_HEAL = 15,
+	SPELL_HEAL = 15
+}
+
+local function regenerateRosterGuids()
+	units = {}
+	guids = {}
+	for i in ipairs(objects) do
+		local object = objects[i]
+		local guid = UnitGUID(object.unit)
+		if UnitExists(object.unit) and guid then
+			units[object.unit] = guid
+			guids[guid] = true
+		end
+	end
+end
+
+local function handleCLEU(...)
+	local timestamp, subevent, _, sourceGUID, sourceName, _, _, destGuid, destName = CombatLogGetCurrentEventInfo()
+	local destGuid = select(8,...)
+	local unit = destName
+	local sign = HealthEvents[select(2,...)]
+	if destGuid and guids[destGuid] and unit and sign then
+		local health
+		if sign > 0 then
+			health = min(UnitHealth(unit) + select(sign,...), UnitHealthMax(unit))
+		elseif sign < 0 then
+			health = max(UnitHealth(unit) - select(-sign,...), 0)
+		end
+		if health~=health_cache[destGuid] then
+			health_cache[destGuid] = health
+			for i in ipairs(objects) do
+				local object = objects[i]
+				if units[object.unit] == destGuid then
+					object.Health.forcedHealth = health
+					object.Health:ForceUpdate()
+					object.Health.forcedHealth = nil
+				end
+			end
+		end
+	end
+end
+
+local function OnEvent_Handler(self, event)
+	if event == 'PLAYER_ENTERING_WORLD' then
+		regenerateRosterGuids()
+	elseif event == 'GROUP_ROSTER_UPDATE' then
+		regenerateRosterGuids()
+	elseif event == 'COMBAT_LOG_EVENT_UNFILTERED' then
+		handleCLEU(CombatLogGetCurrentEventInfo())
+	end
+end
+
+local cleu_frame = CreateFrame('Frame', 'oUF_CLEU_FRAME')
+cleu_frame:RegisterEvent('COMBAT_LOG_EVENT_UNFILTERED')
+cleu_frame:RegisterEvent('GROUP_ROSTER_UPDATE')
+cleu_frame:RegisterEvent('PLAYER_ENTERING_WORLD')
+cleu_frame:SetScript('OnEvent', OnEvent_Handler)
p3lim commented

We don't officially support classic, and this seems like something that could easily be done in a layout.

If you can prove a significant benefit to doing updates with CLEU on retail then we could take a look at it.