WoWInterface

WoWInterface (https://www.wowinterface.com/forums/index.php)
-   Lua/XML Help (https://www.wowinterface.com/forums/forumdisplay.php?f=16)
-   -   Issues regarding UNIT_SPELLCAST_SUCCEEDED (https://www.wowinterface.com/forums/showthread.php?t=52267)

zaifon 05-04-15 11:22 AM

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!

SDPhantom 05-04-15 01:43 PM

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.

sirann 05-04-15 02:19 PM

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

zaifon 05-04-15 04:43 PM

Quote:

Originally Posted by SDPhantom (Post 308596)
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.

Quote:

Originally Posted by sirann (Post 308597)
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.

sirann 05-04-15 06:56 PM

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

jeruku 05-04-15 07:56 PM

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.

SDPhantom 05-04-15 08:20 PM

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.



Quote:

Originally Posted by jeruku (Post 308607)
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).

elcius 05-04-15 11:06 PM

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);


zaifon 05-05-15 09:28 AM

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!

SDPhantom 05-05-15 12:27 PM

Quote:

Originally Posted by elcius (Post 308610)
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.



Quote:

Originally Posted by zaifon (Post 308619)
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.


All times are GMT -6. The time now is 04:41 AM.

vBulletin © 2024, Jelsoft Enterprises Ltd
© 2004 - 2022 MMOUI