Make the Actionscript Client able to use the same mechanism as the Java Client to adjust for clock difference between client & server.
This allows us to pass an expiration time from the server, and the client can appropriately turn that into a countdown timer to the appropriately adjusted time. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6733 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -26,6 +26,7 @@ import flash.utils.Timer;
|
||||
|
||||
import com.threerings.util.DelayUtil;
|
||||
import com.threerings.util.Log;
|
||||
import com.threerings.util.Long;
|
||||
import com.threerings.util.Throttle;
|
||||
|
||||
import com.threerings.presents.client.InvocationService_ConfirmListener;
|
||||
@@ -225,6 +226,26 @@ public class Client extends EventDispatcher
|
||||
return _bstrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a server time stamp to a value comparable to client clock readings.
|
||||
*/
|
||||
public function fromServerTime (stamp :Long) :Long
|
||||
{
|
||||
// when we calculated our time delta, we did it such that: C - S = dT, thus to convert
|
||||
// server to client time we do: C = S + dT
|
||||
return Long.fromNumber(stamp.toNumber() + _serverDelta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a client clock reading to a value comparable to a server time stamp.
|
||||
*/
|
||||
public function toServerTime (stamp :Long) :Long
|
||||
{
|
||||
// when we calculated our time delta, we did it such that: C - S = dT, thus to convert
|
||||
// server to client time we do: S = C - dT
|
||||
return Long.fromNumber(stamp.toNumber() - _serverDelta);
|
||||
}
|
||||
|
||||
public function isLoggedOn () :Boolean
|
||||
{
|
||||
return (_clobj != null);
|
||||
@@ -369,6 +390,10 @@ public class Client extends EventDispatcher
|
||||
|
||||
_invdir.init(omgr, _cloid, this);
|
||||
|
||||
// send a few pings to the server to establish the clock offset between this client and
|
||||
// server standard time
|
||||
establishClockDelta(flash.utils.getTimer());
|
||||
|
||||
// log.debug("TimeBaseService: " + requireService(TimeBaseService));
|
||||
}
|
||||
|
||||
@@ -383,8 +408,40 @@ public class Client extends EventDispatcher
|
||||
}
|
||||
|
||||
var now :uint = flash.utils.getTimer();
|
||||
if (now - _comm.getLastWrite() > PingRequest.PING_INTERVAL) {
|
||||
if (_dcalc != null) {
|
||||
// if our current calculator is done, clear it out
|
||||
if (_dcalc.isDone()) {
|
||||
//log.debug("Time offset from server: " + _serverDelta + "ms.");
|
||||
_dcalc = null;
|
||||
} else if (_dcalc.shouldSendPing()) {
|
||||
// otherwise, send another ping
|
||||
var req :PingRequest = new PingRequest();
|
||||
_comm.postMessage(req);
|
||||
_dcalc.sentPing(req);
|
||||
}
|
||||
|
||||
} else if (now - _comm.getLastWrite() > PingRequest.PING_INTERVAL) {
|
||||
_comm.postMessage(new PingRequest());
|
||||
} else if (now - _lastSync > CLOCK_SYNC_INTERVAL) {
|
||||
// resync our clock with the server
|
||||
establishClockDelta(now);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during initialization to initiate a sequence of ping/pong messages which will be used
|
||||
* to determine (with "good enough" accuracy) the difference between the client clock and the
|
||||
* server clock so that we can later interpret server timestamps.
|
||||
*/
|
||||
protected function establishClockDelta (now :Number) :void
|
||||
{
|
||||
if (_comm != null) {
|
||||
// create a new delta calculator and start the process
|
||||
_dcalc = new DeltaCalculator();
|
||||
var req :PingRequest = new PingRequest();
|
||||
_comm.postMessage(req);
|
||||
_dcalc.sentPing(req);
|
||||
_lastSync = now;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,7 +462,6 @@ public class Client extends EventDispatcher
|
||||
{
|
||||
// keep our client object around
|
||||
_clobj = clobj;
|
||||
|
||||
// and start up our tick interval (which will send pings when necessary)
|
||||
if (_tickInterval == null) {
|
||||
_tickInterval = new Timer(5000);
|
||||
@@ -453,7 +509,13 @@ public class Client extends EventDispatcher
|
||||
*/
|
||||
internal function gotPong (pong :PongResponse) :void
|
||||
{
|
||||
// TODO: compute time delta?
|
||||
// if we're not currently calculating our delta, then we can throw away the pong
|
||||
if (_dcalc != null) {
|
||||
// we update the delta after every receipt so as to immediately obtain an estimate of
|
||||
// the clock delta and then refine it as more packets come in
|
||||
_dcalc.gotPong(pong);
|
||||
_serverDelta = _dcalc.getTimeDelta();
|
||||
}
|
||||
}
|
||||
|
||||
internal function setOutgoingMessageThrottle (messagesPerSec :int) :void
|
||||
@@ -536,6 +598,16 @@ public class Client extends EventDispatcher
|
||||
/** Manages invocation services. */
|
||||
protected var _invdir :InvocationDirector = new InvocationDirector();
|
||||
|
||||
/** The difference between the server clock and the client clock (estimated immediately after
|
||||
* logging on). */
|
||||
protected var _serverDelta :Number;
|
||||
|
||||
/** Used when establishing our clock delta between the client and server. */
|
||||
protected var _dcalc :DeltaCalculator;
|
||||
|
||||
/** The last time at which we synced our clock with the server. */
|
||||
protected var _lastSync :Number;
|
||||
|
||||
/** Ticks. */
|
||||
protected var _tickInterval :Timer;
|
||||
|
||||
@@ -547,5 +619,8 @@ public class Client extends EventDispatcher
|
||||
/** Used to temporarily track our server switcher so that we can tell when we're logging off
|
||||
* whether or not we're switching servers or actually ending our session. */
|
||||
protected var _switcher :ServerSwitcher;
|
||||
|
||||
/** How often we recompute our time offset from the server. */
|
||||
protected static const CLOCK_SYNC_INTERVAL :Number = 600 * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
//
|
||||
// $Id: DeltaCalculator.java 6407 2011-01-01 05:02:21Z dhoover $
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.presents.client {
|
||||
|
||||
import flash.utils.getTimer;
|
||||
|
||||
import com.threerings.util.Arrays;
|
||||
import com.threerings.util.Log;
|
||||
|
||||
import com.threerings.presents.net.PingRequest;
|
||||
import com.threerings.presents.net.PongResponse;
|
||||
|
||||
/**
|
||||
* Used to compute the client/server time delta, attempting to account for
|
||||
* the network delay experienced when the server sends its current time to
|
||||
* the client.
|
||||
*/
|
||||
public class DeltaCalculator
|
||||
{
|
||||
private static const log :Log = Log.getLog(DeltaCalculator);
|
||||
|
||||
/**
|
||||
* Constructs a delta calculator which is used to calculate the time
|
||||
* delta between the client and server, accounting reasonably well for
|
||||
* the delay introduced by sending a timestamp over the network from
|
||||
* the server to the client.
|
||||
*/
|
||||
public function DeltaCalculator ()
|
||||
{
|
||||
_deltas = [];
|
||||
for (var ii :int = 0; ii < CLOCK_SYNC_PING_COUNT; ii++) {
|
||||
_deltas.push(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we send another ping?
|
||||
*/
|
||||
public function shouldSendPing () :Boolean
|
||||
{
|
||||
return (_ping == null) && !isDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called when a ping message is sent to the server.
|
||||
*/
|
||||
public function sentPing (ping :PingRequest) :void
|
||||
{
|
||||
_ping = ping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called when the pong response arrives back from the server.
|
||||
*
|
||||
* @return true if we've iterated sufficiently many times to establish
|
||||
* a stable time delta estimate.
|
||||
*/
|
||||
public function gotPong (pong :PongResponse) :Boolean
|
||||
{
|
||||
if (_ping == null) {
|
||||
// an errant pong that is likely being processed late after
|
||||
// a new connection was opened.
|
||||
return false;
|
||||
}
|
||||
// don't freak out if they keep calling gotPong() after we're done
|
||||
if (_iter >= _deltas.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// make a note of when the ping message was sent and when the pong
|
||||
// response was received (both in client time)
|
||||
var send :Number = _ping.getPackStamp();
|
||||
var recv :Number = pong.getUnpackStamp();
|
||||
_ping = null; // clear out the saved sent ping
|
||||
|
||||
// Factor to convert a realtime value (such as the server uses) to a number relative to
|
||||
// when flash started up.
|
||||
var realtimeToTimer :Number = getTimer() - ((new Date()).getTime());
|
||||
|
||||
// make a note of when the pong response was sent (in server time)
|
||||
// and the processing delay incurred on the server
|
||||
var server :Number = pong.getPackStamp().toNumber() + realtimeToTimer;
|
||||
var delay :Number = pong.getProcessDelay();
|
||||
|
||||
// compute the network delay (round-trip time divided by two)
|
||||
var nettime :Number = (recv - send - delay)/2;
|
||||
|
||||
// the time delta is the client time when the pong was received
|
||||
// minus the server's send time (plus network delay): dT = C - S
|
||||
_deltas[_iter] = recv - (server + nettime);
|
||||
|
||||
log.debug("Calculated delta", "delay", delay, "nettime", nettime, "delta", _deltas[_iter],
|
||||
"rtt", (recv-send));
|
||||
|
||||
return (++_iter >= CLOCK_SYNC_PING_COUNT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the best estimate client/server time-delta.
|
||||
*/
|
||||
public function getTimeDelta () :Number
|
||||
{
|
||||
if (_iter == 0) { // no responses yet
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Return a median value as our estimate, rather than an average.
|
||||
// Mdb writes:
|
||||
// -----------
|
||||
// I used the median because that was more likely to result in a
|
||||
// sensible value.
|
||||
//
|
||||
// Assuming there are two kinds of packets, one that goes and comes
|
||||
// back without delay and provides an accurate time value, and one
|
||||
// that gets delayed somewhere on the way there or the way back and
|
||||
// provides an inaccurate time value.
|
||||
//
|
||||
// If no packets are delayed, both algorithms should be fine. If one
|
||||
// packet is delayed the median will select the middle, non-delayed
|
||||
// packet, whereas the average will skew everything a bit because
|
||||
// of the delayed packet. If two packets are delayed, the median
|
||||
// will be more skewed than the average because it will benefit
|
||||
// from the one accurate packet and if all three packets are delayed
|
||||
// both algorithms will be (approximately) equally inaccurate.
|
||||
//
|
||||
// I believe the chances are most likely that zero or one packets
|
||||
// will be delayed, so I chose the median rather than the average.
|
||||
// -----------
|
||||
|
||||
// copy the deltas array so that we don't alter things before
|
||||
// all pongs have arrived
|
||||
var deltasCopy :Array = Arrays.copyOf(_deltas);
|
||||
|
||||
// sort the estimates and return one from the middle
|
||||
deltasCopy.sort(Array.NUMERIC);
|
||||
return deltasCopy[int(deltasCopy.length/2)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this calculator has enough data to compute a time
|
||||
* delta estimate. Stick a fork in it!
|
||||
*/
|
||||
public function isDone () :Boolean
|
||||
{
|
||||
return (_iter >= CLOCK_SYNC_PING_COUNT);
|
||||
}
|
||||
|
||||
/** The number of ping/pong iterations we've made. */
|
||||
protected var _iter :int;
|
||||
|
||||
/** Client/server time delta estimates. */
|
||||
protected var _deltas :Array; // of Number
|
||||
|
||||
/** A reference to the most recently sent ping which we use to obtain
|
||||
* the appropriate send stamp when we get the corresponding receive
|
||||
* stamp. */
|
||||
protected var _ping :PingRequest;
|
||||
|
||||
/** The number of times we PING during clock sync to try to smooth out
|
||||
* network jiggling. */
|
||||
protected static const CLOCK_SYNC_PING_COUNT :int = 3;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user