better clock synchronization, including p2p sync

This commit is contained in:
David Rose 2003-07-30 09:48:21 +00:00
parent 7b17ba518e
commit aec3aa147c
3 changed files with 207 additions and 26 deletions

View File

@ -59,7 +59,7 @@ class ClientRepository(ConnectionRepository.ConnectionRepository):
precisely measured and may drift slightly after startup, but
it should be accurate plus or minus a couple of seconds.
"""
return time.time() + self.cr.getServerDelta()
return time.time() + self.serverDelta
def parseDcFile(self, dcFileName):
self.dcFile = DCFile()

View File

@ -24,6 +24,18 @@ NetworkTimePrecision = 100.0
# These values are derived from the above.
NetworkTimeMask = (1 << NetworkTimeBits) - 1
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):
"""
@ -38,33 +50,171 @@ class ClockDelta(DirectObject.DirectObject):
def __init__(self):
self.globalClock = ClockObject.getGlobalClock()
# self.delta is the relative delta from our clock to the
# server's clock.
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)
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):
"""
this is called when the global clock gets adjusted
timeDelta is equal to the amount of time, in seconds,
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
self.delta += timeDelta
def resynchronize(self, localTime, networkTime):
"""resynchronize(self, float localTime, uint32 networkTime)
def clear(self):
"""
Throws away any previous synchronization information.
"""
self.delta = 0
self.uncertainty = None
self.lastResync = 0.0
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 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) -
(float(networkTime) / NetworkTimePrecision))
change = newDelta - self.delta
self.newDelta(localTime, newDelta, newUncertainty)
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 change
return 1
### Primary interface functions ###
@ -147,11 +297,7 @@ class ClockDelta(DirectObject.DirectObject):
now = self.globalClock.getFrameTime()
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)

View File

@ -283,17 +283,20 @@ class DistributedSmoothNode(DistributedNode.DistributedNode):
now = globalClock.getFrameTime()
local = globalClockDelta.networkToLocalTime(timestamp, now)
chug = globalClock.getRealTime() - now
real = globalClock.getRealTime()
chug = real - now
# Sanity check the timestamp from the other avatar. It should
# be just slightly in the past, but it might be off by as much
# as this frame's amount of time forward or back.
howFarFuture = local - now
if howFarFuture - chug >= MaxFuture:
# Too far off; resync both of us.
if self.cr.timeManager != None:
self.cr.timeManager.synchronize("Packets from %d off by %.1f s" % (self.doId, howFarFuture))
self.d_suggestResync(self.cr.localToonDoId)
# Too far off; advise the other client of our clock information.
if globalClockDelta.getUncertainty() != None:
self.d_suggestResync(self.cr.localToonDoId, timestamp,
globalClockDelta.getRealNetworkTime(),
real - globalClockDelta.getDelta(),
globalClockDelta.getUncertainty())
self.smoother.setTimestamp(local)
self.smoother.markPosition()
@ -334,16 +337,48 @@ class DistributedSmoothNode(DistributedNode.DistributedNode):
### Monitor clock sync ###
def d_suggestResync(self, avId):
self.sendUpdate("suggestResync", [avId])
def d_suggestResync(self, avId, timestampA, timestampB, serverTime, uncertainty):
self.sendUpdate("suggestResync", [avId, timestampA, timestampB, serverTime, uncertainty])
def suggestResync(self, avId):
def suggestResync(self, avId, timestampA, timestampB, serverTime, uncertainty):
"""suggestResync(self, avId)
This message is sent from one client to another when the other
client receives a timestamp from this client that is so far
out of date as to suggest that one or both clients needs to
resynchronize with the AI.
resynchronize their clock information.
"""
result = \
self.peerToPeerResync(avId, timestampA, serverTime, uncertainty)
if result >= 0 and \
globalClockDelta.getUncertainty() != None:
other = self.cr.doId2do.get(avId)
if other and hasattr(other, "d_returnResync"):
real = globalClock.getRealTime()
other.d_returnResync(self.cr.localToonDoId, timestampB,
real - globalClockDelta.getDelta(),
globalClockDelta.getUncertainty())
def d_returnResync(self, avId, timestampB, serverTime, uncertainty):
self.sendUpdate("returnResync", [avId, timestampB, serverTime, uncertainty])
def returnResync(self, avId, timestampB, serverTime, uncertainty):
"""returnResync(self, avId)
A reply sent by a client whom we recently sent suggestResync
to, this reports the client's new delta information so we can
adjust our clock as well.
"""
self.peerToPeerResync(avId, timestampB, serverTime, uncertainty)
def peerToPeerResync(self, avId, timestamp, serverTime, uncertainty):
gotSync = globalClockDelta.peerToPeerResync(avId, timestamp, serverTime, uncertainty)
# If we didn't get anything useful from the other client,
# maybe our clock is just completely hosed. Go ask the AI.
if not gotSync:
if self.cr.timeManager != None:
self.cr.timeManager.synchronize("suggested by %d" % (avId))
return gotSync