DistributedSmoothNode.py 22 KB

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