Thread Tools Display Modes
05-04-15, 11:22 AM   #1
zaifon
A Defias Bandit
Join Date: May 2015
Posts: 3
Issues regarding UNIT_SPELLCAST_SUCCEEDED

Hey,

Basically this is my first attempt at an addon, it's pretty basic and is supposed to show the amount of Chain Heal bounces for each cast, with multistrikes separated. I've got some previous programming experience, but this is the first time dabbling with Lua. I'll start with explaining the issue and then pasting in the code below.

The basic idea is two event listeners, one on UNIT_SPELLCAST_SUCCEEDED and then one on 'COMBAT_LOG_EVENT'. I think I've managed these ones pretty well, but the succeeded event fires before all the combat log collecting has been done. So I figured that the data gathering is taking too long and with no connection between the two events, they'll work asynchronously. Solution seems to be either to speed up the combat log events or make them fire off one another (somehow).

Here's the code:
Lua Code:
  1. -- Register Frame
  2. function CHC:registerFrame()
  3.     local CHCatcher_EventFrame = CreateFrame("Frame")
  4.     CHC:setEventHandlers(CHCatcher_EventFrame)
  5. end
  6.  
  7. -- Register Events
  8. function CHC:setEventHandlers(Catcher)
  9.     print("EventListener activated")
  10.     Catcher:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
  11.     Catcher:RegisterEvent("COMBAT_LOG_EVENT")
  12.     CHC:handleEvents(Catcher)
  13. end
  14.  
  15. function CHC:handleEvents(Catcher)
  16.     Catcher:SetScript("OnEvent", function(self, event, ...)
  17.         if event == "UNIT_SPELLCAST_SUCCEEDED" then
  18.             CHC:spellCast_Succeeded(...)
  19.         elseif event == "COMBAT_LOG_EVENT" then
  20.             CHC:filterTheCombatLog(...)
  21.         end
  22.     end)
  23. end

With the successful cast function..

Lua Code:
  1. function CHC:spellCast_Succeeded(...)
  2.     local unitID, spell, rank, lineID, spellID = select(1, ...)
  3.  
  4.     -- Only do stuff on chain heal casts that succeed
  5.     if spellId == 1064 or spell == "Chain Heal" then
  6.         print("Finished casting " .. spell)
  7.     end
  8. end

And the combat log filtering.

Lua Code:
  1. function CHC:filterTheCombatLog(...)
  2.     local type = select(2, ...)
  3.  
  4.     -- Check if combat log event is a heal
  5.     if type == "SPELL_HEAL" then
  6.         local spellId, spellName, spellSchool = select(12, ...)
  7.  
  8.         -- If the heal event matches chain heal and all that.
  9.         if spellId == 1064 or spellName == "Chain Heal" then
  10.  
  11.             -- Check if the heal was a multistrike
  12.             -- Save those separately
  13.             if select(19, ...) == true then
  14.                 rawChainHealData.history.multistrikes = rawChainHealData.history.multistrikes + 1
  15.                 print("Multistrike #" .. rawChainHealData.history.multistrikes)
  16.             else
  17.                 rawChainHealData.history.targets = rawChainHealData.history.targets + 1
  18.                 print("Hit #" .. rawChainHealData.history.targets .. " at " .. select(1, ...))
  19.             end
  20.  
  21.             -- Save some of the data for later use.
  22.             local spellData = {
  23.                 timestamp = select(1, ...),
  24.                 target = select(9, ...),
  25.                 source = select(5, ...),
  26.                 amount = select(15, ...),
  27.                 overhealing = select(16, ...),
  28.                 multistrike = select(19, ...)
  29.             }
  30.             table.insert(rawChainHealData.history.data, spellData)
  31.         end
  32.     end
  33. end

Here's some sample output:


And full code can be viewed here (pastebin)

Thanks for your time!
  Reply With Quote
05-04-15, 01:43 PM   #2
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,323
What's happening is all the events are firing at the same time, but as the UI engine only runs on one thread, it can only process one at a time in completely random order. One thing I'm seeing though is all the bounces are using the same timestamp, perhaps you can use that to differentiate the separate casts.
__________________
WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)

Last edited by SDPhantom : 05-04-15 at 01:46 PM.
  Reply With Quote
05-04-15, 02:19 PM   #3
sirann
A Flamescale Wyrmkin
Join Date: Mar 2007
Posts: 142
One thing you may want to do, completely unrelated to the problem you're trying to tackle, is switching to:
local _, _, _, _, _, etc = ...
the code may not look as tidy with all of the empty _ variables, but the cpu impact compared to using select(#, ...) should be pretty noticeable, especially if you're poling for chain heal hits in the combat log
  Reply With Quote
05-04-15, 04:43 PM   #4
zaifon
A Defias Bandit
Join Date: May 2015
Posts: 3
Originally Posted by SDPhantom View Post
What's happening is all the events are firing at the same time, but as the UI engine only runs on one thread, it can only process one at a time in completely random order. One thing I'm seeing though is all the bounces are using the same timestamp, perhaps you can use that to differentiate the separate casts.
The problem (and please correct me if I've understood wrong) is that I'm looking for a heavily event-based functionality. The best way I could explain this would be the following schema:

1. Addon detects a successful Chain Heal.
2. Store each of the chain heal hits inside a table (since I'll otherwise have to react on each hit individually)
3. Display the total amount of hits, within the timeline this would be preferable to happen as soon as possible after the UNIT_SPELLCAST_SUCCEEDED event.

I could do a check for a timestamp from each combat log event, but I wouldn't get the data regarding the previous cast until I've finished the next one (as that particular function only fires once a combat log event has fired).

I attempted to gather a timestamp from the UNIT_SPELLCAST_SUCCEEDED, but I couldn't find a way of getting one without generating one myself, through the time() api, which seems highly unreliable. I'm also not quite sure what I would do with the results.

Please let me know if I've misunderstood you, or perhaps aiming for an impossible solution.

Originally Posted by sirann View Post
One thing you may want to do, completely unrelated to the problem you're trying to tackle, is switching to:
local _, _, _, _, _, etc = ...
the code may not look as tidy with all of the empty _ variables, but the cpu impact compared to using select(#, ...) should be pretty noticeable, especially if you're poling for chain heal hits in the combat log
This is great, I will definitely try this out, see if I can perhaps squeeze a couple more hits from the combat log before the UNIT_SPELLCAST_SUCCEEDED fires.

On a separate note, whilst still on the general topic. I stumbled upon the OnUpdate functionality, and perhaps a separate solution. I'm not quite familiar with resources and how heavy certain functionality will be on the target system, but my briefly planned solution could perhaps be the following.

Remove the UNIT_SPELLCAST_SUCEEDED event-listener, instead provide a OnUpdate function, perhaps tie it in to half of the approximated cast-time of Chain Heal or whatever seems appropriate. Save the chain heals by their timestamp and keep updating the actual frame (the UI component) with the latest set of hits/data.

I'd probably need to decide around garbage collection and more, unless that's taken care of by the engine.

Could this approach perhaps work? Whilst considering it, the only potential issue would be jerkyness of the data displayed on the screen, since it wouldn't be updated after a cast, but from 0 seconds to whatever interval it ends up being.
  Reply With Quote
05-04-15, 06:56 PM   #5
sirann
A Flamescale Wyrmkin
Join Date: Mar 2007
Posts: 142
I think SDPhantom's idea of clumping them by timestamp is best. It would require an OnUpdate function briefly to clear the table, but essentially, you'd listen for the event, record the timestamps, and then add them to a table, set the k to the instance, and the v to the timestamp, then compare all of the v's using a for k, v in pairs or something. Once you "clump" all of your events together by looking at their timestamp, you can add them or do w/e. Then wipe the table, turn off the onupdate, and turn back on your combat log parser.

e. if you're worried about the "latency" of the data, you could set the onupdate interval to run once 0.1s after the combat log event. This would essentially be unnoticable, and if it is, just lower the 0.1 to like .06 or w/e your ping is
  Reply With Quote
05-04-15, 07:56 PM   #6
jeruku
A Cobalt Mageweaver
 
jeruku's Avatar
AddOn Author - Click to view addons
Join Date: Oct 2010
Posts: 223
sirann has a good idea but instead of an OnUpdate you could try a C_Timer.NewTicker. Use a counter checking its size on each tick, if the counter matches the previous value just do what you need there.
__________________
"I have not failed, I simply found 10,000 ways that did not work." - Thomas Edison
  Reply With Quote
05-04-15, 08:20 PM   #7
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,323
From what I see of your code, it's much more complicated than it needs to be for what you describe. Essentially, too much class-style programming is going to end up bogging down the addon with tons of indexing operations. The example I'm going to show is written from scratch.

Lua Code:
  1. local HistoryData={};--     Stores data by index
  2. local HistoryLookup={};--   Links indexed data by timestamp
  3.  
  4. local function OnUpdate(self)-- This will be used to update any shown info
  5.     -- Make whatever calls you need here
  6.  
  7.     self:SetScript("OnUpdate",nil);--   Unregister this handler since we'll no longer need it for now
  8. end
  9.  
  10. local EventFrame=CreateFrame("Frame");
  11. EventFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED");--  This event that isn't affected by filter settings
  12. EventFrame:SetScript("OnEvent",function(self,event,timestamp,subevent,_,sourceguid,sourcename,_,_,destguid,destname,_,_,spellid,_,_,amount,over,absorb,crit,multi)
  13.     if subevent=="SPELL_HEAL" and spellid==1064 and sourceguid==UnitGUID("player") then
  14.         local hops=HistoryLookup[timestamp];--  Pull cast data from lookup table
  15.         if not hops then--  If we didn't get any data, make a new entry
  16.             hops={};--  New hop table
  17.             table.insert(HistoryData,hops);--   Add to data collection
  18.             HistoryLookup[timestamp]=hops;--    Assign a link to the same table to our lookup so we can grab it next time
  19.         end
  20.  
  21.         table.insert(hops,{--   Add hop to hops table
  22.             timestamp=timestamp;
  23.             guid=destguid;
  24.             name=destname;
  25.             amount=amount;
  26.             overheal=over;
  27.             absorbed=absorb;
  28.             critical=crit;
  29.             multistrike=multi;
  30.         });
  31.  
  32.         self:SetScript("OnUpdate",OnUpdate);--  Register our OnUpdate handler to run after all of the events are done firing (unregisters itself when done)
  33.     end
  34. end);

This will end up populating the HistoryData table with instances of every single cast of Chain Heal. Every entry will contain a list of every hop the spell did between targets, including who those targets were, how much they were healed, crits, and multistrikes. I also added a little code to reliably run after the events all fire.



Originally Posted by jeruku View Post
sirann has a good idea but instead of an OnUpdate you could try a C_Timer.NewTicker.
That would run the callback function multiple times per cast (once for every hop recorded).
__________________
WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)
  Reply With Quote
05-04-15, 11:06 PM   #8
elcius
A Cliff Giant
AddOn Author - Click to view addons
Join Date: Sep 2011
Posts: 75
you're over complicating it, just use the combat log event for the cast detection, count hits as they come in, announce and reset the counter on the frame following the cast success.
Code:
local f=CreateFrame("Frame")
local playerGUID = UnitGUID("player");
local n = 0;
local announce = function()
	print(n,'hits.');
	n = 0;
end
f:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
f:SetScript("OnEvent",function(self,e,ts,ce,_,sGUID,sName,_,_,dGUID,dName,_,_,spell)
	if not (spell == 1064 and sGUID == playerGUID) then return end
	if ce == 'SPELL_HEAL' then
		n = n+1;
	elseif ce == 'SPELL_CAST_SUCCESS' then
		C_Timer.After(0.01,announce); -- next frame
	end
end);
  Reply With Quote
05-05-15, 09:28 AM   #9
zaifon
A Defias Bandit
Join Date: May 2015
Posts: 3
Thanks a lot for the answers, unfortunately I don't have enough time as I wished I had to spend on this, so I'll have to get back whenever I've poked and prodded the solutions you guys provided, so again, thanks a lot!

I'll make sure to post again with either a finished product or another cry for help!
  Reply With Quote
05-05-15, 12:27 PM   #10
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,323
Originally Posted by elcius View Post
you're over complicating it, just use the combat log event for the cast detection, count hits as they come in, announce and reset the counter on the frame following the cast success.
Part of the OPs original code was recording history, so I was preserving that functionality in my version. As the rest of the code wasn't given, I can only assume that it's being used elsewhere.



Originally Posted by zaifon View Post
Thanks a lot for the answers, unfortunately I don't have enough time as I wished I had to spend on this, so I'll have to get back whenever I've poked and prodded the solutions you guys provided, so again, thanks a lot!

I'll make sure to post again with either a finished product or another cry for help!
That's ok. We're all here to help if you need us.
__________________
WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)
  Reply With Quote

WoWInterface » Developer Discussions » Lua/XML Help » Issues regarding UNIT_SPELLCAST_SUCCEEDED


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off