mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 02:42:49 -04:00
*** empty log message ***
This commit is contained in:
parent
34e6f32227
commit
75588a1224
148
direct/src/distributed/ClockDelta.py
Normal file
148
direct/src/distributed/ClockDelta.py
Normal 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()
|
334
direct/src/distributed/DistributedSmoothNode.py
Normal file
334
direct/src/distributed/DistributedSmoothNode.py
Normal 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
|
Loading…
x
Reference in New Issue
Block a user