Thread Tools Display Modes
07-03-15, 10:16 AM   #1
Dimmulux
A Deviate Faerie Dragon
Join Date: Feb 2014
Posts: 17
Post Coordinating precise timings on different clients

A problem I am currently having with the addon I am writing is coordinating between multiple WoW clients.

Suppose the player is in a raid and wants to notify the rest of the raid that action A needs to be performed precisely T seconds from the time they send the message.

While sometimes sub-second precision may not be particularly important, let us assume it is vital for A (CCing and target switching are where it matters for my addon).

One approach (that taken by DBM) is to simply send the time using SendAddonMessage to the raid channel with a message containing T. Each member of the raid will then receive the number T and perform A in T seconds from the time they receive the message.

The obvious problem with this approach is latency. Depending on the quality of the connections, it would be quite possible for A to be performed 0.4 seconds late, which might be a significant problem.

One way of accounting for latency is to assume that it is constant and use the value provided by API GetNetStats for WorldLatency (I believe it should be world for this purpose, rather than home) of sender and compensate for that when sending message. The receiver then compensates using their WorldLatency to get the time at which they should perform A.

The problem with this approach is that, of course, latency is not constant. If either the sender or the receivers latency varies greatly, the receiver may perform A far too early or far too late. In some cases, performing A too early may be far worse than too late (for example, if A is CCing an enemy out of a cyclone).

Another approach would be for the sender to get the world time and send worldtime + T as the message, where worldtime is stored by a millisecond-precise timer. Unfortunately, GetGameTime() returns time only to minute precision, though polling could probably give the time to second precision. With or without polling, using GetGameTime will not solve the problem as inaccuracy is introduced by the latency of the server response.

The best of these solutions seems to be to compensate using WorldLatency. Perhaps I am missing a better solution or not accurately assessing the options I have considered. How would you solve the problem?

Post describing addon: http://www.wowinterface.com/forums/s...ad.php?t=52373
  Reply With Quote
07-03-15, 12:46 PM   #2
Banknorris
A Chromatic Dragonspawn
 
Banknorris's Avatar
AddOn Author - Click to view addons
Join Date: Oct 2014
Posts: 153
Here is one idea. Lets say client 1 wants to send a message to client 2 to do something in T seconds from now.
1) pick current time t0 with GetTime()
2) waits for next COMBAT_LOG_EVENT_UNFILTERED to trigger
3) when it triggers get the current time t1 with GetTime()
4) send the message of what to do, T1=T-(t1-t0) and the event itself

The client 2 will:
1) check in the table containing the last events the same event contained in the addon message
2) once the event is identified retrieve client 2 time stamp for that event (sender_event[1])
3) waits for next COMBAT_LOG_EVENT_UNFILTERED to trigger and get its time stamp (t2)
4) when it triggers calculate T2=T1-(t2-sender_event[1])
5) T2 is the time you need to wait from now to execute the action required.

Lua Code:
  1. local refresh_time = 1 --this is the refresh time to delete old events.
  2. local time_window = 1  --this is the retroactive time in relation to last event to consider if the event is recent enough to not be discarted
  3. local last_events = {} --table containing the last events
  4. local last_event_time --the time_stamp of the last event
  5. local sender_offset --positive means my events have a bigger time stamp than the sender
  6.  
  7. f = CreateFrame("Frame")
  8. f:SetScript("OnEvent",function(self,event,time_stamp,...)
  9.     last_events[time_stamp] = {...}
  10.     last_event_time = time_stamp
  11. end)
  12. f:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
  13.  
  14. f:SetScript("OnUpdate",function(self,elapsed)
  15.     self.timer = self.timer and self.timer+elapsed or 0
  16.     if self.timer>refresh_time then
  17.         self.timer = 0
  18.         for k,v in pairs(last_events) do
  19.             if k<last_event_time-time_window then
  20.                 last_events[k] = nil
  21.             end
  22.         end
  23.     end
  24. end)
  25.  
  26. local function get_sender_time_offset(sender_event)
  27.     local found = false
  28.     for my_time_stamp,my_event in pairs(last_events) do
  29.         if is_the_same_event(my_event,sender_event) then --caution: sender event contains a time_stamp, my_event does not
  30.             found = true
  31.             break
  32.         end
  33.     end
  34.     if found then
  35.         print("message was send when combat log time stamp was",my_time_stamp)
  36.     end
  37. end
This is not a complete code just a fragment

Also notice that comparing events in different clients is not so trivial because for example source name can contain the server name in one client and not in another.
__________________
"In this world nothing can be said to be certain, except that fractional reserve banking is a Ponzi scheme and that you won't believe it." - Mandrill

Last edited by Banknorris : 07-03-15 at 04:30 PM.
  Reply With Quote
07-03-15, 03:25 PM   #3
Dimmulux
A Deviate Faerie Dragon
Join Date: Feb 2014
Posts: 17
Thank-you very much for your suggestion, Banknorris.

This seems like it may be the way to go. My main concern with this is that there may be duplicate events (events for which all parameters match) within refresh_time. Choosing the most recent may result in the receiver performing A too soon but the oldest is less likely to be the intended event.

I plan to implement your idea, probably using one of the methods I suggested as a backup (in case of mulitple event matches or latency causing no event matches).
  Reply With Quote
07-06-15, 01:55 PM   #4
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,323
Achieving time synchronization shouldn't be that complicated. All that's needed is a common base to which you compare to. Any CLEU event will give you the server timestamp. What event the timestamp is associated with is irrelevant.

I would suggest going this route as a last ditch effort. If you really want your addon to work and without relying on copies of itself run by other group members, you should find events that fire to indicate what you want to warn about on the local machine. Remember, if a CLEU event fires for you, it's going to fire for everyone participating in the same encounter. It's this principle in which all the boss mods work on. While they do support sending custom timers to other group members, their own timers run independent of other group members by use of boss scripts.

Lua Code:
  1. local TimestampOffset;
  2.  
  3. local EventFrame=CreateFrame("Frame");
  4. EventFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED");
  5. EventFrame:SetScript("OnEvent",function(self,event,timestamp)
  6. --  This is our calculated offset from the system time
  7.     local offset=timestamp-GetTime();
  8.  
  9. --  If we haven't stored an offset before or we have a lower offset due to variance in latency, update it
  10.     if not TimestampOffset or offset<TimestampOffset then
  11.         TimestampOffset=offset;
  12.     end
  13. );
  14.  
  15. function GetServerTimestamp()
  16.     return TimestampOffset+GetTime();-- Calculate from our offset
  17. end
__________________
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 : 07-06-15 at 01:59 PM.
  Reply With Quote
07-06-15, 03:36 PM   #5
Banknorris
A Chromatic Dragonspawn
 
Banknorris's Avatar
AddOn Author - Click to view addons
Join Date: Oct 2014
Posts: 153
Oh I wasn't aware that the timestamp in CLEU was the server time. In this case your method is much more appropriated, reliable and easier.
__________________
"In this world nothing can be said to be certain, except that fractional reserve banking is a Ponzi scheme and that you won't believe it." - Mandrill
  Reply With Quote
07-07-15, 01:27 PM   #6
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,323
I discovered it in my own testing. It appears to be a Unix timestamp which is the number of seconds since epoch. I don't remember if it's timezone corrected or GMT-0 as a Unix timestamp is supposed to be. My theory on it being the server timestamp is based on the lack of a gap in recorded time after experiencing a lag spike.



Edit 1: I reran my tests and it is a native Unix timestamp at GMT-0.
This is confirmed with the following code:
Lua Code:
  1. local EventFrame=CreateFrame("Frame");
  2. EventFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED");
  3. EventFrame:SetScript("OnEvent",function(self,event,timestamp,subevent)
  4.     local h,m,s;
  5.  
  6.     h=math.floor(timestamp%86400/3600);
  7.     m=math.floor(timestamp%3600/60);
  8.     s=timestamp%60;
  9.  
  10.     print(("%d:%02d:%06.3f - %s"):format(h,m,s,subevent);
  11. end);

This should print out something like the following:
20:43:45.343 - SPELL_CAST_SUCCESS


Edit 2: Turns out the test was unnecessary. Digging through the CombatLog code reveals Blizzard sending the timestamp through the second argument of date(), which takes in a Unix timestamp.
__________________
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 : 07-07-15 at 03:20 PM.
  Reply With Quote
07-07-15, 05:50 PM   #7
Dimmulux
A Deviate Faerie Dragon
Join Date: Feb 2014
Posts: 17
Thank-you very much for your help, SDPhantom. I've now implemented your suggestion.
  Reply With Quote

WoWInterface » Developer Discussions » General Authoring Discussion » Coordinating precise timings on different clients


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