DistributedSmoothNode.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. """DistributedSmoothNode module: contains the DistributedSmoothNode class"""
  2. from panda3d.core import *
  3. from panda3d.direct import *
  4. from .ClockDelta import *
  5. from . import DistributedNode
  6. from . import DistributedSmoothNodeBase
  7. from direct.task.Task import cont
  8. from direct.showbase import DConfig as config
  9. # This number defines our tolerance for out-of-sync telemetry packets.
  10. # If a packet appears to have originated from more than MaxFuture
  11. # seconds in the future, assume we're out of sync with the other
  12. # avatar and suggest a resync for both.
  13. MaxFuture = config.GetFloat("smooth-max-future", 0.2)
  14. # How frequently can we suggest a resynchronize with another client?
  15. MinSuggestResync = config.GetFloat("smooth-min-suggest-resync", 15)
  16. # These flags indicate whether global smoothing and/or prediction is
  17. # allowed or disallowed.
  18. EnableSmoothing = config.GetBool("smooth-enable-smoothing", 1)
  19. EnablePrediction = config.GetBool("smooth-enable-prediction", 1)
  20. # These values represent the amount of time, in seconds, to delay the
  21. # apparent position of other avatars, when non-predictive and
  22. # predictive smoothing is in effect, respectively. This is in
  23. # addition to the automatic delay of the observed average latency from
  24. # each avatar, which is intended to compensate for relative clock
  25. # skew.
  26. Lag = config.GetDouble("smooth-lag", 0.2)
  27. PredictionLag = config.GetDouble("smooth-prediction-lag", 0.0)
  28. GlobalSmoothing = 0
  29. GlobalPrediction = 0
  30. def globalActivateSmoothing(smoothing, prediction):
  31. """ Globally activates or deactivates smoothing and prediction on
  32. all DistributedSmoothNodes currently in existence, or yet to be
  33. generated. """
  34. global GlobalSmoothing, GlobalPrediction
  35. GlobalSmoothing = smoothing
  36. GlobalPrediction = prediction
  37. for obj in base.cr.getAllOfType(DistributedSmoothNode):
  38. obj.activateSmoothing(smoothing, prediction)
  39. # For historical reasons, we temporarily define
  40. # DistributedSmoothNode.activateSmoothing() to be the global function.
  41. # We'll remove this soon, so it won't get confused with the instance
  42. # method, below.
  43. activateSmoothing = globalActivateSmoothing
  44. class DistributedSmoothNode(DistributedNode.DistributedNode,
  45. DistributedSmoothNodeBase.DistributedSmoothNodeBase):
  46. """
  47. This specializes DistributedNode to add functionality to smooth
  48. motion over time, via the SmoothMover C++ object defined in
  49. DIRECT.
  50. """
  51. def __init__(self, cr):
  52. try:
  53. self.DistributedSmoothNode_initialized
  54. except:
  55. self.DistributedSmoothNode_initialized = 1
  56. DistributedNode.DistributedNode.__init__(self, cr)
  57. DistributedSmoothNodeBase.DistributedSmoothNodeBase.__init__(self)
  58. self.smoothStarted = 0
  59. # Set this True to assert that the local process has
  60. # complete authority over the position of this object when
  61. # smoothing is not in effect. When this is True, position
  62. # reports received over the wire will not be applied to
  63. # this node's position, unless those position reports are
  64. # received between startSmooth() and endSmooth().
  65. self.localControl = False
  66. # flag set when we receive a stop message
  67. self.stopped = False
  68. def generate(self):
  69. self.smoother = SmoothMover()
  70. self.smoothStarted = 0
  71. self.lastSuggestResync = 0
  72. self._smoothWrtReparents = False
  73. DistributedNode.DistributedNode.generate(self)
  74. DistributedSmoothNodeBase.DistributedSmoothNodeBase.generate(self)
  75. self.cnode.setRepository(self.cr, 0, 0)
  76. self.activateSmoothing(GlobalSmoothing, GlobalPrediction)
  77. # clear stopped flag for re-generate
  78. self.stopped = False
  79. def disable(self):
  80. DistributedSmoothNodeBase.DistributedSmoothNodeBase.disable(self)
  81. DistributedNode.DistributedNode.disable(self)
  82. del self.smoother
  83. def delete(self):
  84. DistributedSmoothNodeBase.DistributedSmoothNodeBase.delete(self)
  85. DistributedNode.DistributedNode.delete(self)
  86. ### Methods to handle computing and updating of the smoothed
  87. ### position.
  88. def smoothPosition(self):
  89. """
  90. This function updates the position of the node to its computed
  91. smoothed position. This may be overridden by a derived class
  92. to specialize the behavior.
  93. """
  94. self.smoother.computeAndApplySmoothPosHpr(self, self)
  95. def doSmoothTask(self, task):
  96. self.smoothPosition()
  97. return cont
  98. def wantsSmoothing(self):
  99. # Override this function to return 0 if this particular kind
  100. # of smooth node doesn't really want to be smoothed.
  101. return 1
  102. def startSmooth(self):
  103. """
  104. This function starts the task that ensures the node is
  105. positioned correctly every frame. However, while the task is
  106. running, you won't be able to lerp the node or directly
  107. position it.
  108. """
  109. if not self.wantsSmoothing() or self.isDisabled() or self.isLocal():
  110. return
  111. if not self.smoothStarted:
  112. taskName = self.taskName("smooth")
  113. taskMgr.remove(taskName)
  114. self.reloadPosition()
  115. taskMgr.add(self.doSmoothTask, taskName)
  116. self.smoothStarted = 1
  117. def stopSmooth(self):
  118. """
  119. This function stops the task spawned by startSmooth(), and
  120. allows show code to move the node around directly.
  121. """
  122. if self.smoothStarted:
  123. taskName = self.taskName("smooth")
  124. taskMgr.remove(taskName)
  125. self.forceToTruePosition()
  126. self.smoothStarted = 0
  127. def setSmoothWrtReparents(self, flag):
  128. self._smoothWrtReparents = flag
  129. def getSmoothWrtReparents(self):
  130. return self._smoothWrtReparents
  131. def forceToTruePosition(self):
  132. """
  133. This forces the node to reposition itself to its latest known
  134. position. This may result in a pop as the node skips the last
  135. of its lerp points.
  136. """
  137. #printStack()
  138. if (not self.isLocal()) and \
  139. self.smoother.getLatestPosition():
  140. self.smoother.applySmoothPosHpr(self, self)
  141. self.smoother.clearPositions(1)
  142. def reloadPosition(self):
  143. """
  144. This function re-reads the position from the node itself and
  145. clears any old position reports for the node. This should be
  146. used whenever show code bangs on the node position and expects
  147. it to stick.
  148. """
  149. self.smoother.clearPositions(0)
  150. self.smoother.setPosHpr(self.getPos(), self.getHpr())
  151. self.smoother.setPhonyTimestamp()
  152. self.smoother.markPosition()
  153. def _checkResume(self,timestamp):
  154. """
  155. Determine if we were previously stopped and now need to
  156. resume movement by making sure any old stored positions
  157. reflect the node's current position
  158. """
  159. if (self.stopped):
  160. currTime = globalClock.getFrameTime()
  161. now = currTime - self.smoother.getExpectedBroadcastPeriod()
  162. last = self.smoother.getMostRecentTimestamp()
  163. if (now > last):
  164. # only set a new timestamp postion if we still have
  165. # a position being smoothed to (so we don't interrupt
  166. # any current smoothing and only do this if the object
  167. # is actually locally stopped)
  168. if (timestamp == None):
  169. # no timestamp, use current time
  170. local = 0.0
  171. else:
  172. local = globalClockDelta.networkToLocalTime(
  173. timestamp, currTime)
  174. self.smoother.setPhonyTimestamp(local,True)
  175. self.smoother.markPosition()
  176. self.stopped = False
  177. # distributed set pos and hpr functions
  178. # 'send' versions are inherited from DistributedSmoothNodeBase
  179. def setSmStop(self, timestamp=None):
  180. self.setComponentTLive(timestamp)
  181. self.stopped = True
  182. def setSmH(self, h, timestamp=None):
  183. self._checkResume(timestamp)
  184. self.setComponentH(h)
  185. self.setComponentTLive(timestamp)
  186. def setSmZ(self, z, timestamp=None):
  187. self._checkResume(timestamp)
  188. self.setComponentZ(z)
  189. self.setComponentTLive(timestamp)
  190. def setSmXY(self, x, y, timestamp=None):
  191. self._checkResume(timestamp)
  192. self.setComponentX(x)
  193. self.setComponentY(y)
  194. self.setComponentTLive(timestamp)
  195. def setSmXZ(self, x, z, timestamp=None):
  196. self._checkResume(timestamp)
  197. self.setComponentX(x)
  198. self.setComponentZ(z)
  199. self.setComponentTLive(timestamp)
  200. def setSmPos(self, x, y, z, timestamp=None):
  201. self._checkResume(timestamp)
  202. self.setComponentX(x)
  203. self.setComponentY(y)
  204. self.setComponentZ(z)
  205. self.setComponentTLive(timestamp)
  206. def setSmHpr(self, h, p, r, timestamp=None):
  207. self._checkResume(timestamp)
  208. self.setComponentH(h)
  209. self.setComponentP(p)
  210. self.setComponentR(r)
  211. self.setComponentTLive(timestamp)
  212. def setSmXYH(self, x, y, h, timestamp):
  213. self._checkResume(timestamp)
  214. self.setComponentX(x)
  215. self.setComponentY(y)
  216. self.setComponentH(h)
  217. self.setComponentTLive(timestamp)
  218. def setSmXYZH(self, x, y, z, h, timestamp=None):
  219. self._checkResume(timestamp)
  220. self.setComponentX(x)
  221. self.setComponentY(y)
  222. self.setComponentZ(z)
  223. self.setComponentH(h)
  224. self.setComponentTLive(timestamp)
  225. def setSmPosHpr(self, x, y, z, h, p, r, timestamp=None):
  226. self._checkResume(timestamp)
  227. self.setComponentX(x)
  228. self.setComponentY(y)
  229. self.setComponentZ(z)
  230. self.setComponentH(h)
  231. self.setComponentP(p)
  232. self.setComponentR(r)
  233. self.setComponentTLive(timestamp)
  234. def setSmPosHprL(self, l, x, y, z, h, p, r, timestamp=None):
  235. self._checkResume(timestamp)
  236. self.setComponentL(l)
  237. self.setComponentX(x)
  238. self.setComponentY(y)
  239. self.setComponentZ(z)
  240. self.setComponentH(h)
  241. self.setComponentP(p)
  242. self.setComponentR(r)
  243. self.setComponentTLive(timestamp)
  244. ### component set pos and hpr functions ###
  245. ### These are the component functions that are invoked
  246. ### remotely by the above composite functions.
  247. @report(types = ['args'], dConfigParam = 'smoothnode')
  248. def setComponentX(self, x):
  249. self.smoother.setX(x)
  250. @report(types = ['args'], dConfigParam = 'smoothnode')
  251. def setComponentY(self, y):
  252. self.smoother.setY(y)
  253. @report(types = ['args'], dConfigParam = 'smoothnode')
  254. def setComponentZ(self, z):
  255. self.smoother.setZ(z)
  256. @report(types = ['args'], dConfigParam = 'smoothnode')
  257. def setComponentH(self, h):
  258. self.smoother.setH(h)
  259. @report(types = ['args'], dConfigParam = 'smoothnode')
  260. def setComponentP(self, p):
  261. self.smoother.setP(p)
  262. @report(types = ['args'], dConfigParam = 'smoothnode')
  263. def setComponentR(self, r):
  264. self.smoother.setR(r)
  265. @report(types = ['args'], dConfigParam = 'smoothnode')
  266. def setComponentL(self, l):
  267. if (l != self.zoneId):
  268. # only perform set location if location is different
  269. self.setLocation(self.parentId,l)
  270. @report(types = ['args'], dConfigParam = 'smoothnode')
  271. def setComponentT(self, timestamp):
  272. # This is a little bit hacky. If *this* function is called,
  273. # it must have been called directly by the server, for
  274. # instance to update the values previously set for some avatar
  275. # that was already into the zone as we entered. (A live
  276. # update would have gone through the function called
  277. # setComponentTLive, below.)
  278. # Since we know this update came through the server, it may
  279. # reflect very old data. Thus, we can't accurately decode the
  280. # network timestamp (since the network time encoding can only
  281. # represent a time up to about 5 minutes in the past), but we
  282. # don't really need to know the timestamp anyway. We'll just
  283. # arbitrarily place it at right now.
  284. self.smoother.setPhonyTimestamp()
  285. self.smoother.clearPositions(1)
  286. self.smoother.markPosition()
  287. # mark position only takes most recent position sent over the wire
  288. # and applies it to the smoother's sample points, but we still
  289. # need to make sure and apply that position to the actual node
  290. # path
  291. self.forceToTruePosition()
  292. @report(types = ['args'], dConfigParam = 'smoothnode')
  293. def setComponentTLive(self, timestamp):
  294. # This is the variant of setComponentT() that will be called
  295. # whenever we receive a live update directly from the other
  296. # client. This is because the component functions, above,
  297. # call this function explicitly instead of setComponentT().
  298. #print 'setComponentTLive: %s' % timestamp
  299. if timestamp is None:
  300. # if no timestamp, re-use the most recent timestamp to keep things
  301. # from getting out of order
  302. if self.smoother.hasMostRecentTimestamp():
  303. self.smoother.setTimestamp(self.smoother.getMostRecentTimestamp())
  304. else:
  305. # no most-recent timestamp, use current time
  306. self.smoother.setPhonyTimestamp()
  307. self.smoother.markPosition()
  308. else:
  309. now = globalClock.getFrameTime()
  310. local = globalClockDelta.networkToLocalTime(timestamp, now)
  311. realTime = globalClock.getRealTime()
  312. chug = realTime - now
  313. # Sanity check the timestamp from the other avatar. It should
  314. # be just slightly in the past, but it might be off by as much
  315. # as this frame's amount of time forward or back.
  316. howFarFuture = local - now
  317. if howFarFuture - chug >= MaxFuture:
  318. # Too far off; advise the other client of our clock information.
  319. if globalClockDelta.getUncertainty() != None and \
  320. realTime - self.lastSuggestResync >= MinSuggestResync and \
  321. hasattr(self.cr, 'localAvatarDoId'):
  322. self.lastSuggestResync = realTime
  323. timestampB = globalClockDelta.localToNetworkTime(realTime)
  324. serverTime = realTime - globalClockDelta.getDelta()
  325. assert self.notify.info(
  326. "Suggesting resync for %s, with discrepency %s; local time is %s and server time is %s." % (
  327. self.doId, howFarFuture - chug,
  328. realTime, serverTime))
  329. self.d_suggestResync(
  330. self.cr.localAvatarDoId, timestamp,
  331. timestampB, serverTime,
  332. globalClockDelta.getUncertainty())
  333. self.smoother.setTimestamp(local)
  334. self.smoother.markPosition()
  335. if not self.localControl and not self.smoothStarted and \
  336. self.smoother.getLatestPosition():
  337. self.smoother.applySmoothPosHpr(self, self)
  338. # These are all required by the CMU server, which requires get* to
  339. # match set* in more cases than the Disney server does.
  340. def getComponentL(self):
  341. return self.zoneId
  342. def getComponentX(self):
  343. return self.getX()
  344. def getComponentY(self):
  345. return self.getY()
  346. def getComponentZ(self):
  347. return self.getZ()
  348. def getComponentH(self):
  349. return self.getH()
  350. def getComponentP(self):
  351. return self.getP()
  352. def getComponentR(self):
  353. return self.getR()
  354. def getComponentT(self):
  355. return 0
  356. @report(types = ['args'], dConfigParam = 'smoothnode')
  357. def clearSmoothing(self, bogus = None):
  358. # Call this to invalidate all the old position reports
  359. # (e.g. just before popping to a new position).
  360. #printStack()
  361. self.smoother.clearPositions(1)
  362. @report(types = ['args'], dConfigParam = 'smoothnode')
  363. def wrtReparentTo(self, parent):
  364. # We override this NodePath method to force it to
  365. # automatically reset the smoothing position when we call it.
  366. if self.smoothStarted:
  367. if self._smoothWrtReparents:
  368. #print self.getParent(), parent, self.getParent().getPos(parent)
  369. self.smoother.handleWrtReparent(self.getParent(), parent)
  370. NodePath.wrtReparentTo(self, parent)
  371. else:
  372. self.forceToTruePosition()
  373. NodePath.wrtReparentTo(self, parent)
  374. self.reloadPosition()
  375. else:
  376. NodePath.wrtReparentTo(self, parent)
  377. @report(types = ['args'], dConfigParam = 'smoothnode')
  378. def d_setParent(self, parentToken):
  379. # We override this DistributedNode method to force a full position
  380. # update immediately after the distributed setParent is sent.
  381. # See ParentMgr.py for an explanation.
  382. DistributedNode.DistributedNode.d_setParent(self, parentToken)
  383. self.forceToTruePosition()
  384. self.sendCurrentPosition()
  385. ### Monitor clock sync ###
  386. def d_suggestResync(self, avId, timestampA, timestampB,
  387. serverTime, uncertainty):
  388. serverTimeSec = math.floor(serverTime)
  389. serverTimeUSec = (serverTime - serverTimeSec) * 10000.0
  390. self.sendUpdate("suggestResync", [avId, timestampA, timestampB,
  391. serverTimeSec, serverTimeUSec,
  392. uncertainty])
  393. def suggestResync(self, avId, timestampA, timestampB,
  394. serverTimeSec, serverTimeUSec, uncertainty):
  395. """
  396. This message is sent from one client to another when the other
  397. client receives a timestamp from this client that is so far
  398. out of date as to suggest that one or both clients needs to
  399. resynchronize their clock information.
  400. """
  401. serverTime = float(serverTimeSec) + float(serverTimeUSec) / 10000.0
  402. result = self.peerToPeerResync(
  403. avId, timestampA, serverTime, uncertainty)
  404. if result >= 0 and \
  405. globalClockDelta.getUncertainty() != None:
  406. other = self.cr.doId2do.get(avId)
  407. if (not other):
  408. assert self.notify.info(
  409. "Warning: couldn't find the avatar %d" % (avId))
  410. elif hasattr(other, "d_returnResync") and \
  411. hasattr(self.cr, 'localAvatarDoId'):
  412. realTime = globalClock.getRealTime()
  413. serverTime = realTime - globalClockDelta.getDelta()
  414. assert self.notify.info(
  415. "Returning resync for %s; local time is %s and server time is %s." % (
  416. self.doId, realTime, serverTime))
  417. other.d_returnResync(
  418. self.cr.localAvatarDoId, timestampB,
  419. serverTime,
  420. globalClockDelta.getUncertainty())
  421. def d_returnResync(self, avId, timestampB, serverTime, uncertainty):
  422. serverTimeSec = math.floor(serverTime)
  423. serverTimeUSec = (serverTime - serverTimeSec) * 10000.0
  424. self.sendUpdate("returnResync", [
  425. avId, timestampB, serverTimeSec, serverTimeUSec, uncertainty])
  426. def returnResync(self, avId, timestampB, serverTimeSec, serverTimeUSec,
  427. uncertainty):
  428. """
  429. A reply sent by a client whom we recently sent suggestResync
  430. to, this reports the client's new delta information so we can
  431. adjust our clock as well.
  432. """
  433. serverTime = float(serverTimeSec) + float(serverTimeUSec) / 10000.0
  434. self.peerToPeerResync(avId, timestampB, serverTime, uncertainty)
  435. def peerToPeerResync(self, avId, timestamp, serverTime, uncertainty):
  436. gotSync = globalClockDelta.peerToPeerResync(
  437. avId, timestamp, serverTime, uncertainty)
  438. # If we didn't get anything useful from the other client,
  439. # maybe our clock is just completely hosed. Go ask the AI.
  440. if not gotSync:
  441. if self.cr.timeManager != None:
  442. self.cr.timeManager.synchronize("suggested by %d" % (avId))
  443. return gotSync
  444. def activateSmoothing(self, smoothing, prediction):
  445. """
  446. Enables or disables the smoothing of other avatars' motion.
  447. This used to be a global flag, but now it is specific to each
  448. avatar instance. However, see globalActivateSmoothing() in
  449. this module.
  450. If smoothing is off, no kind of smoothing will be performed,
  451. regardless of the setting of prediction.
  452. This is not necessarily predictive smoothing; if predictive
  453. smoothing is off, avatars will be lagged by a certain factor
  454. to achieve smooth motion. Otherwise, if predictive smoothing
  455. is on, avatars will be drawn as nearly as possible in their
  456. current position, by extrapolating from old position reports.
  457. This assumes you have a client repository that knows its
  458. localAvatarDoId -- stored in self.cr.localAvatarDoId
  459. """
  460. if smoothing and EnableSmoothing:
  461. if prediction and EnablePrediction:
  462. # Prediction and smoothing.
  463. self.smoother.setSmoothMode(SmoothMover.SMOn)
  464. self.smoother.setPredictionMode(SmoothMover.PMOn)
  465. self.smoother.setDelay(PredictionLag)
  466. else:
  467. # Smoothing, but no prediction.
  468. self.smoother.setSmoothMode(SmoothMover.SMOn)
  469. self.smoother.setPredictionMode(SmoothMover.PMOff)
  470. self.smoother.setDelay(Lag)
  471. else:
  472. # No smoothing, no prediction.
  473. self.smoother.setSmoothMode(SmoothMover.SMOff)
  474. self.smoother.setPredictionMode(SmoothMover.PMOff)
  475. self.smoother.setDelay(0.0)