|
@@ -24,6 +24,18 @@ NetworkTimePrecision = 100.0
|
|
|
# These values are derived from the above.
|
|
# These values are derived from the above.
|
|
|
NetworkTimeMask = (1 << NetworkTimeBits) - 1
|
|
NetworkTimeMask = (1 << NetworkTimeBits) - 1
|
|
|
NetworkTimeTopBits = 32 - NetworkTimeBits
|
|
NetworkTimeTopBits = 32 - NetworkTimeBits
|
|
|
|
|
+MaxTimeDelta = (NetworkTimeMask / 2.0) / NetworkTimePrecision
|
|
|
|
|
+
|
|
|
|
|
+# This is the maximum number of seconds by which we expect our clock
|
|
|
|
|
+# (or the server's clock) to drift over an hour.
|
|
|
|
|
+ClockDriftPerHour = 1.0 # Is this generous enough?
|
|
|
|
|
+
|
|
|
|
|
+# And the above, scaled into a per-second value.
|
|
|
|
|
+ClockDriftPerSecond = ClockDriftPerHour / 3600.0
|
|
|
|
|
+
|
|
|
|
|
+# How many seconds to insist on waiting before accepting a second
|
|
|
|
|
+# resync request from another client.
|
|
|
|
|
+P2PResyncDelay = 10.0
|
|
|
|
|
|
|
|
class ClockDelta(DirectObject.DirectObject):
|
|
class ClockDelta(DirectObject.DirectObject):
|
|
|
"""
|
|
"""
|
|
@@ -38,33 +50,171 @@ class ClockDelta(DirectObject.DirectObject):
|
|
|
def __init__(self):
|
|
def __init__(self):
|
|
|
self.globalClock = ClockObject.getGlobalClock()
|
|
self.globalClock = ClockObject.getGlobalClock()
|
|
|
|
|
|
|
|
|
|
+ # self.delta is the relative delta from our clock to the
|
|
|
|
|
+ # server's clock.
|
|
|
self.delta = 0
|
|
self.delta = 0
|
|
|
|
|
+
|
|
|
|
|
+ # self.uncertainty represents the number of seconds plus or
|
|
|
|
|
+ # minus in which we are confident our delta matches the
|
|
|
|
|
+ # server's actual time. The initial value, None, represents
|
|
|
|
|
+ # infinity--we have no idea.
|
|
|
|
|
+ self.uncertainty = None
|
|
|
|
|
+
|
|
|
|
|
+ # self.lastResync is the time at which self.uncertainty
|
|
|
|
|
+ # was measured. It is important to remember because our
|
|
|
|
|
+ # uncertainty increases over time (due to relative clock
|
|
|
|
|
+ # drift).
|
|
|
|
|
+ self.lastResync = 0.0
|
|
|
|
|
+
|
|
|
self.accept("resetClock", self.__resetClock)
|
|
self.accept("resetClock", self.__resetClock)
|
|
|
|
|
|
|
|
|
|
+ def getDelta(self):
|
|
|
|
|
+ return self.delta
|
|
|
|
|
+
|
|
|
|
|
+ def getUncertainty(self):
|
|
|
|
|
+ # Returns our current uncertainty with our clock measurement,
|
|
|
|
|
+ # as a number of seconds plus or minus. Returns None,
|
|
|
|
|
+ # representing infinite uncertainty, if we have never received
|
|
|
|
|
+ # a time measurement.
|
|
|
|
|
+
|
|
|
|
|
+ if self.uncertainty == None:
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ now = self.globalClock.getRealTime()
|
|
|
|
|
+ elapsed = now - self.lastResync
|
|
|
|
|
+ return self.uncertainty + elapsed * ClockDriftPerSecond
|
|
|
|
|
+
|
|
|
|
|
+ def getLastResync(self):
|
|
|
|
|
+ # Returns the local time at which we last resynchronized the
|
|
|
|
|
+ # clock delta.
|
|
|
|
|
+ return self.lastResync
|
|
|
|
|
+
|
|
|
def __resetClock(self, timeDelta):
|
|
def __resetClock(self, timeDelta):
|
|
|
"""
|
|
"""
|
|
|
this is called when the global clock gets adjusted
|
|
this is called when the global clock gets adjusted
|
|
|
timeDelta is equal to the amount of time, in seconds,
|
|
timeDelta is equal to the amount of time, in seconds,
|
|
|
that has been added to the global clock
|
|
that has been added to the global clock
|
|
|
"""
|
|
"""
|
|
|
- self.notify.debug("adjusting timebase by %f seconds" % timeDelta)
|
|
|
|
|
|
|
+ assert(self.notify.debug("adjusting timebase by %f seconds" % timeDelta))
|
|
|
# adjust our timebase by the same amount
|
|
# adjust our timebase by the same amount
|
|
|
self.delta += timeDelta
|
|
self.delta += timeDelta
|
|
|
|
|
|
|
|
- def resynchronize(self, localTime, networkTime):
|
|
|
|
|
- """resynchronize(self, float localTime, uint32 networkTime)
|
|
|
|
|
-
|
|
|
|
|
- Resets the relative delta so that the indicated networkTime
|
|
|
|
|
- and localTime map to the same instant. The return value is
|
|
|
|
|
- the amount by which the clock changes, in seconds.
|
|
|
|
|
|
|
+ def clear(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Throws away any previous synchronization information.
|
|
|
|
|
+ """
|
|
|
|
|
+ self.delta = 0
|
|
|
|
|
+ self.uncertainty = None
|
|
|
|
|
+ self.lastResync = 0.0
|
|
|
|
|
+
|
|
|
|
|
+ def resynchronize(self, localTime, networkTime, newUncertainty,
|
|
|
|
|
+ trustNew = 1):
|
|
|
|
|
+ """resynchronize(self, float localTime, int32 networkTime,
|
|
|
|
|
+ float newUncertainty)
|
|
|
|
|
+
|
|
|
|
|
+ Accepts a new networkTime value, which is understood to
|
|
|
|
|
+ represent the same moment as localTime, plus or minus
|
|
|
|
|
+ uncertainty seconds. Improves our current notion of the time
|
|
|
|
|
+ delta accordingly.
|
|
|
"""
|
|
"""
|
|
|
newDelta = (float(localTime) -
|
|
newDelta = (float(localTime) -
|
|
|
(float(networkTime) / NetworkTimePrecision))
|
|
(float(networkTime) / NetworkTimePrecision))
|
|
|
- change = newDelta - self.delta
|
|
|
|
|
- self.delta = newDelta
|
|
|
|
|
|
|
+ self.newDelta(localTime, newDelta, newUncertainty)
|
|
|
|
|
|
|
|
- return change
|
|
|
|
|
|
|
+ def peerToPeerResync(self, avId, timestamp, serverTime, uncertainty):
|
|
|
|
|
+ """
|
|
|
|
|
+ Accepts an AI time and uncertainty value from another client,
|
|
|
|
|
+ along with a local timestamp value of the message from this
|
|
|
|
|
+ client which prompted the other client to send us its delta
|
|
|
|
|
+ information.
|
|
|
|
|
+
|
|
|
|
|
+ The return value is true if the other client's measurement was
|
|
|
|
|
+ reasonably close to our own, or false if the other client's
|
|
|
|
|
+ time estimate was wildly divergent from our own; the return
|
|
|
|
|
+ value is negative if the test was not even considered (because
|
|
|
|
|
+ it happened too soon after another recent request).
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ now = self.globalClock.getRealTime()
|
|
|
|
|
+ if now - self.lastResync < P2PResyncDelay:
|
|
|
|
|
+ # We can't process this request; it came in on the heels
|
|
|
|
|
+ # of some other request, and our local timestamp may have
|
|
|
|
|
+ # been resynced since then: ergo, the timestamp in this
|
|
|
|
|
+ # request is meaningless.
|
|
|
|
|
+ assert(self.notify.debug("Ignoring request for resync from %s within %.3f s." % (avId, now - self.lastResync)))
|
|
|
|
|
+ return -1
|
|
|
|
|
+
|
|
|
|
|
+ # The timestamp value will be a timestamp that we sent out
|
|
|
|
|
+ # previously, echoed back to us. Therefore we can confidently
|
|
|
|
|
+ # convert it back into our local time, even though we suspect
|
|
|
|
|
+ # our clock delta might be off.
|
|
|
|
|
+ local = self.networkToLocalTime(timestamp, now)
|
|
|
|
|
+ elapsed = now - local
|
|
|
|
|
+ delta = (local + now) / 2.0 - serverTime
|
|
|
|
|
+
|
|
|
|
|
+ gotSync = 0
|
|
|
|
|
+ if elapsed <= 0 or elapsed > P2PResyncDelay:
|
|
|
|
|
+ # The elapsed time must be positive (the local timestamp
|
|
|
|
|
+ # must be in the past), and shouldn't be more than
|
|
|
|
|
+ # P2PResyncDelay. If it does not meet these requirements,
|
|
|
|
|
+ # it must be very old indeed, or someone is playing tricks
|
|
|
|
|
+ # on us.
|
|
|
|
|
+ self.notify.info("Ignoring old request for resync from %s." % (avId))
|
|
|
|
|
+ else:
|
|
|
|
|
+ # Now the other client has told us his delta and uncertainty
|
|
|
|
|
+ # information, which was generated somewhere in the range
|
|
|
|
|
+ # [-elapsed, 0] seconds ago. That means our complete window
|
|
|
|
|
+ # is wider by that amount.
|
|
|
|
|
+ self.notify.info("Got sync +/- %.3f s, elapsed %.3f s, from %s." % (uncertainty, elapsed, avId))
|
|
|
|
|
+ delta -= elapsed / 2.0
|
|
|
|
|
+ uncertainty += elapsed / 2.0
|
|
|
|
|
+
|
|
|
|
|
+ gotSync = self.newDelta(local, delta, uncertainty, trustNew = 0)
|
|
|
|
|
|
|
|
|
|
+ return gotSync
|
|
|
|
|
+
|
|
|
|
|
+ def newDelta(self, localTime, newDelta, newUncertainty,
|
|
|
|
|
+ trustNew = 1):
|
|
|
|
|
+ """
|
|
|
|
|
+ Accepts a new delta and uncertainty pair, understood to
|
|
|
|
|
+ represent time as of localTime. Improves our current notion
|
|
|
|
|
+ of the time delta accordingly. The return value is true if
|
|
|
|
|
+ the new measurement was used, false if it was discarded.
|
|
|
|
|
+ """
|
|
|
|
|
+ oldUncertainty = self.getUncertainty()
|
|
|
|
|
+ if oldUncertainty != None:
|
|
|
|
|
+ assert(self.notify.debug('previous delta at %.3f s, +/- %.3f s.' % (self.delta, oldUncertainty)))
|
|
|
|
|
+ assert(self.notify.debug('new delta at %.3f s, +/- %.3f s.' % (newDelta, newUncertainty)))
|
|
|
|
|
+ # Our previous measurement was self.delta +/- oldUncertainty;
|
|
|
|
|
+ # our new measurement is newDelta +/- newUncertainty. Take
|
|
|
|
|
+ # the intersection of both.
|
|
|
|
|
+
|
|
|
|
|
+ oldLow = self.delta - oldUncertainty
|
|
|
|
|
+ oldHigh = self.delta + oldUncertainty
|
|
|
|
|
+ newLow = newDelta - newUncertainty
|
|
|
|
|
+ newHigh = newDelta + newUncertainty
|
|
|
|
|
+
|
|
|
|
|
+ low = max(oldLow, newLow)
|
|
|
|
|
+ high = min(oldHigh, newHigh)
|
|
|
|
|
+
|
|
|
|
|
+ # If there is no intersection, whoops! Either the old
|
|
|
|
|
+ # measurement or the new measurement is completely wrong.
|
|
|
|
|
+ if low > high:
|
|
|
|
|
+ if not trustNew:
|
|
|
|
|
+ self.notify.info('discarding new delta.')
|
|
|
|
|
+ return 0
|
|
|
|
|
+
|
|
|
|
|
+ self.notify.info('discarding previous delta.')
|
|
|
|
|
+ else:
|
|
|
|
|
+ newDelta = (low + high) / 2.0
|
|
|
|
|
+ newUncertainty = (high - low) / 2.0
|
|
|
|
|
+ assert(self.notify.debug('intersection at %.3f s, +/- %.3f s.' % (newDelta, newUncertainty)))
|
|
|
|
|
+
|
|
|
|
|
+ self.delta = newDelta
|
|
|
|
|
+ self.uncertainty = newUncertainty
|
|
|
|
|
+ self.lastResync = localTime
|
|
|
|
|
+
|
|
|
|
|
+ return 1
|
|
|
|
|
|
|
|
### Primary interface functions ###
|
|
### Primary interface functions ###
|
|
|
|
|
|
|
@@ -147,11 +297,7 @@ class ClockDelta(DirectObject.DirectObject):
|
|
|
now = self.globalClock.getFrameTime()
|
|
now = self.globalClock.getFrameTime()
|
|
|
dt = now - self.networkToLocalTime(networkTime, now, bits=bits)
|
|
dt = now - self.networkToLocalTime(networkTime, now, bits=bits)
|
|
|
|
|
|
|
|
- if (dt >= 0.0):
|
|
|
|
|
- return dt
|
|
|
|
|
- else:
|
|
|
|
|
- self.notify.debug('negative clock delta: %.3f' % dt)
|
|
|
|
|
- return 0.0
|
|
|
|
|
|
|
+ return max(dt, 0.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|