*** empty log message ***

This commit is contained in:
Joe Shochet 2001-11-27 21:52:10 +00:00
parent 34e6f32227
commit 75588a1224
2 changed files with 482 additions and 0 deletions

View File

@ -0,0 +1,148 @@
# ClockDelta provides the ability to use clock synchronization for
# distributed objects
from PandaModules import *
import DirectNotifyGlobal
import DirectObject
import math
# The following two parameters, NetworkTimeBits and
# NetworkTimePrecision, define the number of bits required to store a
# network time, and the number of ticks per second it represents,
# respectively. The tradeoff is the longest period of elapsed time we
# can measure, vs. the precision with which we can measure it.
# 16 and 100 give us precision to 1/100th of a second, with a range of
# +/- 5 minutes in a 16-bit integer. These are eminently tweakable,
# but the parameter types in toon.dc must match the number of bits
# specified here (i.e. int16 if NetworkTimeBits is 16; int32 if
# NetworkTimeBits is 32).
NetworkTimeBits = 16
NetworkTimePrecision = 100.0
# These values are derived from the above.
NetworkTimeMask = (1 << NetworkTimeBits) - 1
NetworkTimeTopBits = 32 - NetworkTimeBits
class ClockDelta(DirectObject.DirectObject):
"""
The ClockDelta object converts between universal ("network") time,
which is used for all network traffic, and local time (e.g. as
returned by getFrameTime() or getRealTime()), which is used for
everything else.
"""
notify = DirectNotifyGlobal.directNotify.newCategory('ClockDelta')
def __init__(self):
self.globalClock = ClockObject.getGlobalClock()
self.delta = 0
self.accept("resetClock", self.__resetClock)
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)
# adjust our timebase by the same amount
self.delta += timeDelta
def resynchronize(self, localTime, networkTime):
"""resynchronize(self, float localTime, int 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.
"""
newDelta = float(localTime) - float(networkTime) / NetworkTimePrecision
change = newDelta - self.delta
self.delta = newDelta
return self.networkToLocalTime(self.localToNetworkTime(change), 0.0)
### Primary interface functions ###
def networkToLocalTime(self, networkTime, now = None):
"""networkToLocalTime(self, int networkTime)
Converts the indicated networkTime to the corresponding
localTime value. The time is assumed to be within +/- 5
minutes of the current local time given in now, or
getRealTime() if now is not specified.
"""
if now == None:
now = self.globalClock.getRealTime()
# First, determine what network time we have for 'now'.
ntime = int(math.floor((now - self.delta) * NetworkTimePrecision + 0.5))
# The signed difference between these is the number of units
# of NetworkTimePrecision by which the network time differs
# from 'now'.
diff = self.__signExtend(networkTime - ntime)
return now + float(diff) / NetworkTimePrecision
def localToNetworkTime(self, localTime):
"""localToNetworkTime(self, float localTime)
Converts the indicated localTime to the corresponding
networkTime value.
"""
ntime = int(math.floor((localTime - self.delta) * NetworkTimePrecision + 0.5))
return self.__signExtend(ntime)
### Convenience functions ###
def getRealNetworkTime(self):
"""getRealNetworkTime(self)
Returns the current getRealTime() expressed as a network time.
"""
return self.localToNetworkTime(self.globalClock.getRealTime())
def getFrameNetworkTime(self):
"""getFrameNetworkTime(self)
Returns the current getFrameTime() expressed as a network time.
"""
return self.localToNetworkTime(self.globalClock.getFrameTime())
def localElapsedTime(self, networkTime):
"""localElapsedTime(self, int networkTime)
Returns the amount of time elapsed (in seconds) on the client
since the server message was sent. Negative values are
clamped to zero.
"""
now = self.globalClock.getFrameTime()
dt = now - self.networkToLocalTime(networkTime, now)
if (dt >= 0.0):
return dt
else:
self.notify.debug('negative clock delta: %.3f' % dt)
return 0.0
### Private functions ###
def __signExtend(self, networkTime):
"""__signExtend(self, int networkTime)
Preserves the lower NetworkTimeBits of the networkTime value,
and extends the sign bit all the way up.
"""
return ((networkTime & NetworkTimeMask) << NetworkTimeTopBits) >> NetworkTimeTopBits
globalClockDelta = ClockDelta()

View File

@ -0,0 +1,334 @@
"""DistributedSmoothNode module: contains the DistributedSmoothNode class"""
from PandaModules import *
from ClockDelta import *
import DistributedNode
import Task
globalClock = ClockObject.getGlobalClock()
# This number defines our tolerance for out-of-sync telemetry packets.
# If a packet appears to have originated from more than MaxFuture
# seconds in the future, assume we're out of sync with the other
# avatar and suggest a resync for both.
MaxFuture = base.config.GetFloat("smooth-max-future", 0.1)
# These flags indicate whether global smoothing and/or prediction is
# allowed or disallowed.
EnableSmoothing = base.config.GetBool("smooth-enable-smoothing", 1)
EnablePrediction = base.config.GetBool("smooth-enable-prediction", 1)
# These values represent the amount of time, in seconds, to delay the
# apparent position of other avatars, when non-predictive and
# predictive smoothing is in effect, respectively.
Lag = base.config.GetDouble("smooth-lag", 0.2)
PredictionLag = base.config.GetDouble("smooth-prediction-lag", 0.0)
def activateSmoothing(smoothing, prediction):
"""
Enables or disables the smoothing of other avatars' motion.
This is a global flag that controls the behavior of all
SmoothMovers in the world. If smoothing is off, no kind of
smoothing will be performed, regardless of the setting of
prediction.
This is not necessarily predictive smoothing; if predictive
smoothing is off, avatars will be lagged by a certain factor
to achieve smooth motion. Otherwise, if predictive smoothing
is on, avatars will be drawn as nearly as possible in their
current position, by extrapolating from old position reports.
This assumes you have a client repository that knows its
localToonDoId -- stored in self.cr.localToonDoId
"""
if smoothing and EnableSmoothing:
if prediction and EnablePrediction:
# Prediction and smoothing.
SmoothMover.setSmoothMode(SmoothMover.SMOn)
SmoothMover.setPredictionMode(SmoothMover.PMOn)
SmoothMover.setDelay(PredictionLag)
else:
# Smoothing, but no prediction.
SmoothMover.setSmoothMode(SmoothMover.SMOn)
SmoothMover.setPredictionMode(SmoothMover.PMOff)
SmoothMover.setDelay(Lag)
else:
# No smoothing, no prediction.
SmoothMover.setSmoothMode(SmoothMover.SMOff)
SmoothMover.setPredictionMode(SmoothMover.PMOff)
SmoothMover.setDelay(0.0)
class DistributedSmoothNode(DistributedNode.DistributedNode):
"""DistributedSmoothNode class:
This specializes DistributedNode to add functionality to smooth
motion over time, via the SmoothMover C++ object defined in
DIRECT.
"""
def __init__(self, cr):
try:
self.DistributedSmoothNode_initialized
except:
self.DistributedSmoothNode_initialized = 1
DistributedNode.DistributedNode.__init__(self, cr)
self.smoother = SmoothMover()
self.smoothStarted = 0
return None
### Methods to handle computing and updating of the smoothed
### position.
def smoothPosition(self):
"""smoothPosition(self)
This function updates the position of the node to its computed
smoothed position. This may be overridden by a derived class
to specialize the behavior.
"""
if self.smoother.computeSmoothPosition():
self.setMat(self.smoother.getSmoothMat())
def doSmoothTask(self, task):
self.smoothPosition()
return Task.cont
def startSmooth(self):
"""startSmooth(self)
This function starts the task that ensures the node is
positioned correctly every frame. However, while the task is
running, you won't be able to lerp the node or directly
position it.
"""
if self.isLocal():
# If we've just finished banging on localToon, reload the
# drive interface's concept of our position.
base.drive.node().setPos(self.getPos())
base.drive.node().setHpr(self.getHpr())
elif not self.smoothStarted:
taskName = self.taskName("smooth")
taskMgr.removeTasksNamed(taskName)
self.reloadPosition()
taskMgr.spawnMethodNamed(self.doSmoothTask, taskName)
self.smoothStarted = 1
return
def stopSmooth(self):
"""startSmooth(self)
This function stops the task spawned by startSmooth(), and
allows show code to move the node around directly.
"""
if self.smoothStarted:
taskName = self.taskName("smooth")
taskMgr.removeTasksNamed(taskName)
self.forceToTruePosition()
self.smoothStarted = 0
return
def forceToTruePosition(self):
"""forceToTruePosition(self)
This forces the node to reposition itself to its latest known
position. This may result in a pop as the node skips the last
of its lerp points.
"""
if (not self.isLocal()) and \
self.smoother.getLatestPosition():
self.setMat(self.smoother.getSmoothMat())
self.smoother.clearPositions(1)
def reloadPosition(self):
"""reloadPosition(self)
This function re-reads the position from the node itself and
clears any old position reports for the node. This should be
used whenever show code bangs on the node position and expects
it to stick.
"""
self.smoother.clearPositions(0)
self.smoother.setMat(self.getMat())
self.smoother.setTimestamp()
self.smoother.markPosition()
### distributed set pos and hpr functions ###
### These functions send the distributed update to set the
### appropriate values on the remote side. These are
### composite fields, with all the likely combinations
### defined; each function maps (via the dc file) to one or
### more component operations on the remote client.
def d_setSmStop(self):
self.sendUpdate("setSmStop", [globalClockDelta.getFrameNetworkTime()])
def setSmStop(self, timestamp):
self.setComponentTLive(timestamp)
def d_setSmH(self, h):
self.sendUpdate("setSmH", [h, globalClockDelta.getFrameNetworkTime()])
def setSmH(self, h, timestamp):
self.setComponentH(h)
self.setComponentTLive(timestamp)
def d_setSmXY(self, x, y):
self.sendUpdate("setSmXY", [x, y, globalClockDelta.getFrameNetworkTime()])
def setSmXY(self, x, y, timestamp):
self.setComponentX(x)
self.setComponentY(y)
self.setComponentTLive(timestamp)
def d_setSmXZ(self, x, z):
self.sendUpdate("setSmXZ", [x, z, globalClockDelta.getFrameNetworkTime()])
def setSmXZ(self, x, z, timestamp):
self.setComponentX(x)
self.setComponentZ(z)
self.setComponentTLive(timestamp)
def d_setSmPos(self, x, y, z):
self.sendUpdate("setSmPos", [x, y, z, globalClockDelta.getFrameNetworkTime()])
def setSmPos(self, x, y, z, timestamp):
self.setComponentX(x)
self.setComponentY(y)
self.setComponentZ(z)
self.setComponentTLive(timestamp)
def d_setSmHpr(self, h, p, r):
self.sendUpdate("setSmHpr", [h, p, r, globalClockDelta.getFrameNetworkTime()])
def setSmHpr(self, h, p, r, timestamp):
self.setComponentH(h)
self.setComponentP(p)
self.setComponentR(r)
self.setComponentTLive(timestamp)
def d_setSmXYH(self, x, y, h):
self.sendUpdate("setSmXYH", [x, y, h, globalClockDelta.getFrameNetworkTime()])
def setSmXYH(self, x, y, h, timestamp):
self.setComponentX(x)
self.setComponentY(y)
self.setComponentH(h)
self.setComponentTLive(timestamp)
def d_setSmXYZH(self, x, y, z, h):
self.sendUpdate("setSmXYZH", [x, y, z, h, globalClockDelta.getFrameNetworkTime()])
def setSmXYZH(self, x, y, z, h, timestamp):
self.setComponentX(x)
self.setComponentY(y)
self.setComponentZ(z)
self.setComponentH(h)
self.setComponentTLive(timestamp)
def d_setSmPosHpr(self, x, y, z, h, p, r):
self.sendUpdate("setSmPosHpr", [x, y, z, h, p, r, globalClockDelta.getFrameNetworkTime()])
def setSmPosHpr(self, x, y, z, h, p, r, timestamp):
self.setComponentX(x)
self.setComponentY(y)
self.setComponentZ(z)
self.setComponentH(h)
self.setComponentP(p)
self.setComponentR(r)
self.setComponentTLive(timestamp)
return
### component set pos and hpr functions ###
### These are the component functions that are invoked
### remotely by the above composite functions.
def setComponentX(self, x):
self.smoother.setX(x)
def setComponentY(self, y):
self.smoother.setY(y)
def setComponentZ(self, z):
self.smoother.setZ(z)
def setComponentH(self, h):
self.smoother.setH(h)
def setComponentP(self, p):
self.smoother.setP(p)
def setComponentR(self, r):
self.smoother.setR(r)
def setComponentT(self, timestamp):
# This is a little bit hacky. If *this* function is called,
# it must have been called directly by the server, for
# instance to update the values previously set for some avatar
# that was already into the zone as we entered. (A live
# update would have gone through the function called
# setComponentTLive, below.)
# Since we know this update came through the server, it may
# reflect very old data. Thus, we can't accurately decode the
# network timestamp (since the network time encoding can only
# represent a time up to about 5 minutes in the past), but we
# don't really need to know the timestamp anyway. We'll just
# arbitrarily place it at right now.
now = globalClock.getFrameTime()
self.smoother.setTimestamp(now)
self.smoother.clearPositions(1)
self.smoother.markPosition()
def setComponentTLive(self, timestamp):
# This is the variant of setComponentT() that will be called
# whenever we receive a live update directly from the other
# client. This is because the component functions, above,
# call this function explicitly instead of setComponentT().
now = globalClock.getFrameTime()
local = globalClockDelta.networkToLocalTime(timestamp, now)
chug = globalClock.getRealTime() - 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)
self.smoother.setTimestamp(local)
self.smoother.markPosition()
def b_clearSmoothing(self):
self.d_clearSmoothing()
self.clearSmoothing()
def d_clearSmoothing(self):
self.sendUpdate("clearSmoothing", [0])
def clearSmoothing(self, bogus = None):
# Call this to invalidate all the old position reports
# (e.g. just before popping to a new position).
self.smoother.clearPositions(1)
def wrtReparentTo(self, parent):
# We override this NodePath method to force it to
# automatically reset the smoothing position when we call it.
if self.smoothStarted:
self.forceToTruePosition()
NodePath.wrtReparentTo(self, parent)
self.reloadPosition()
else:
NodePath.wrtReparentTo(self, parent)
def isLocal(self):
# Local toon will override this to return true
return 0