ClientRepositoryBase.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. from pandac.PandaModules import *
  2. from MsgTypes import *
  3. from direct.task import Task
  4. from direct.directnotify import DirectNotifyGlobal
  5. import CRCache
  6. from direct.distributed.CRDataCache import CRDataCache
  7. from direct.distributed.ConnectionRepository import ConnectionRepository
  8. from direct.showbase import PythonUtil
  9. import ParentMgr
  10. import RelatedObjectMgr
  11. import time
  12. from ClockDelta import *
  13. from PyDatagram import PyDatagram
  14. from PyDatagramIterator import PyDatagramIterator
  15. import types
  16. class ClientRepositoryBase(ConnectionRepository):
  17. """
  18. This maintains a client-side connection with a Panda server.
  19. This base class exists to collect the common code between
  20. ClientRepository, which is the CMU-provided, open-source version
  21. of the client repository code, and OTPClientRepository, which is
  22. the VR Studio's implementation of the same.
  23. """
  24. notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepositoryBase")
  25. def __init__(self, dcFileNames = None):
  26. self.dcSuffix=""
  27. ConnectionRepository.__init__(self, ConnectionRepository.CM_HTTP, base.config, hasOwnerView=True)
  28. if hasattr(self, 'setVerbose'):
  29. if self.config.GetBool('verbose-clientrepository'):
  30. self.setVerbose(1)
  31. self.context=100000
  32. self.setClientDatagram(1)
  33. self.deferredGenerates = []
  34. self.deferredDoIds = {}
  35. self.lastGenerate = 0
  36. self.setDeferInterval(base.config.GetDouble('deferred-generate-interval', 0.2))
  37. self.noDefer = False # Set this True to temporarily disable deferring.
  38. self.recorder = base.recorder
  39. self.readDCFile(dcFileNames)
  40. self.cache=CRCache.CRCache()
  41. self.doDataCache = CRDataCache()
  42. self.cacheOwner=CRCache.CRCache()
  43. self.serverDelta = 0
  44. self.bootedIndex = None
  45. self.bootedText = None
  46. # create a parentMgr to handle distributed reparents
  47. # this used to be 'token2nodePath'
  48. self.parentMgr = ParentMgr.ParentMgr()
  49. # The RelatedObjectMgr helps distributed objects find each
  50. # other.
  51. self.relatedObjectMgr = RelatedObjectMgr.RelatedObjectMgr(self)
  52. # Keep track of how recently we last sent a heartbeat message.
  53. # We want to keep these coming at heartbeatInterval seconds.
  54. self.heartbeatInterval = base.config.GetDouble('heartbeat-interval', 10)
  55. self.heartbeatStarted = 0
  56. self.lastHeartbeat = 0
  57. self._delayDeletedDOs = {}
  58. self.specialNameNumber = 0
  59. def setDeferInterval(self, deferInterval):
  60. """Specifies the minimum amount of time, in seconds, that must
  61. elapse before generating any two DistributedObjects whose
  62. class type is marked "deferrable". Set this to 0 to indicate
  63. no deferring will occur."""
  64. self.deferInterval = deferInterval
  65. self.setHandleCUpdates(self.deferInterval == 0)
  66. if self.deferredGenerates:
  67. taskMgr.remove('deferredGenerate')
  68. taskMgr.doMethodLater(self.deferInterval, self.__doDeferredGenerate, 'deferredGenerate')
  69. ## def queryObjectAll(self, doID, context=0):
  70. ## """
  71. ## Get a one-time snapshot look at the object.
  72. ## """
  73. ## assert self.notify.debugStateCall(self)
  74. ## # Create a message
  75. ## datagram = PyDatagram()
  76. ## datagram.addServerHeader(
  77. ## doID, localAvatar.getDoId(), 2020)
  78. ## # A context that can be used to index the response if needed
  79. ## datagram.addUint32(context)
  80. ## self.send(datagram)
  81. ## # Make sure the message gets there.
  82. ## self.flush()
  83. # Define uniqueName
  84. def uniqueName(self, desc):
  85. return desc
  86. def specialName(self, label):
  87. name = ("SpecialName %s %s" % (self.specialNameNumber, label))
  88. self.specialNameNumber += 1
  89. return name
  90. def getTables(self, ownerView):
  91. if ownerView:
  92. return self.doId2ownerView, self.cacheOwner
  93. else:
  94. return self.doId2do, self.cache
  95. def _getMsgName(self, msgId):
  96. # we might get a list of message names, use the first one
  97. return makeList(MsgId2Names.get(msgId, 'UNKNOWN MESSAGE: %s' % msgId))[0]
  98. def sendDisconnect(self):
  99. if self.isConnected():
  100. # Tell the game server that we're going:
  101. datagram = PyDatagram()
  102. # Add message type
  103. datagram.addUint16(CLIENT_DISCONNECT)
  104. # Send the message
  105. self.send(datagram)
  106. self.notify.info("Sent disconnect message to server")
  107. self.disconnect()
  108. self.stopHeartbeat()
  109. def allocateContext(self):
  110. self.context+=1
  111. return self.context
  112. def setServerDelta(self, delta):
  113. """
  114. Indicates the approximate difference in seconds between the
  115. client's clock and the server's clock, in universal time (not
  116. including timezone shifts). This is mainly useful for
  117. reporting synchronization information to the logs; don't
  118. depend on it for any precise timing requirements.
  119. Also see Notify.setServerDelta(), which also accounts for a
  120. timezone shift.
  121. """
  122. self.serverDelta = delta
  123. def getServerDelta(self):
  124. return self.serverDelta
  125. def getServerTimeOfDay(self):
  126. """
  127. Returns the current time of day (seconds elapsed since the
  128. 1972 epoch) according to the server's clock. This is in GMT,
  129. and hence is irrespective of timezones.
  130. The value is computed based on the client's clock and the
  131. known delta from the server's clock, which is not terribly
  132. precisely measured and may drift slightly after startup, but
  133. it should be accurate plus or minus a couple of seconds.
  134. """
  135. return time.time() + self.serverDelta
  136. def handleGenerateWithRequired(self, di):
  137. parentId = di.getUint32()
  138. zoneId = di.getUint32()
  139. assert parentId == self.GameGlobalsId or parentId in self.doId2do
  140. # Get the class Id
  141. classId = di.getUint16()
  142. # Get the DO Id
  143. doId = di.getUint32()
  144. # Look up the dclass
  145. dclass = self.dclassesByNumber[classId]
  146. dclass.startGenerate()
  147. # Create a new distributed object, and put it in the dictionary
  148. distObj = self.generateWithRequiredFields(dclass, doId, di, parentId, zoneId)
  149. dclass.stopGenerate()
  150. def handleGenerateWithRequiredOther(self, di):
  151. parentId = di.getUint32()
  152. zoneId = di.getUint32()
  153. # Get the class Id
  154. classId = di.getUint16()
  155. # Get the DO Id
  156. doId = di.getUint32()
  157. dclass = self.dclassesByNumber[classId]
  158. deferrable = getattr(dclass.getClassDef(), 'deferrable', False)
  159. if not self.deferInterval or self.noDefer:
  160. deferrable = False
  161. now = globalClock.getFrameTime()
  162. if self.deferredGenerates or deferrable:
  163. # This object is deferrable, or there are already deferred
  164. # objects in the queue (so all objects have to be held
  165. # up).
  166. if self.deferredGenerates or now - self.lastGenerate < self.deferInterval:
  167. # Queue it for later.
  168. assert(self.notify.debug("deferring generate for %s %s" % (dclass.getName(), doId)))
  169. self.deferredGenerates.append((CLIENT_CREATE_OBJECT_REQUIRED_OTHER, doId))
  170. # Keep a copy of the datagram, and move the di to the copy
  171. dg = Datagram(di.getDatagram())
  172. di = DatagramIterator(dg, di.getCurrentIndex())
  173. self.deferredDoIds[doId] = ((parentId, zoneId, classId, doId, di), deferrable, dg, [])
  174. if len(self.deferredGenerates) == 1:
  175. # We just deferred the first object on the queue;
  176. # start the task to generate it.
  177. taskMgr.remove('deferredGenerate')
  178. taskMgr.doMethodLater(self.deferInterval, self.__doDeferredGenerate, 'deferredGenerate')
  179. else:
  180. # We haven't generated any deferrable objects in a
  181. # while, so it's safe to go ahead and generate this
  182. # one immediately.
  183. self.lastGenerate = now
  184. self.__doGenerate(parentId, zoneId, classId, doId, di)
  185. else:
  186. self.__doGenerate(parentId, zoneId, classId, doId, di)
  187. def __doGenerate(self, parentId, zoneId, classId, doId, di):
  188. # Look up the dclass
  189. assert parentId == self.GameGlobalsId or parentId in self.doId2do
  190. dclass = self.dclassesByNumber[classId]
  191. assert(self.notify.debug("performing generate for %s %s" % (dclass.getName(), doId)))
  192. dclass.startGenerate()
  193. # Create a new distributed object, and put it in the dictionary
  194. distObj = self.generateWithRequiredOtherFields(dclass, doId, di, parentId, zoneId)
  195. dclass.stopGenerate()
  196. def flushGenerates(self):
  197. """ Forces all pending generates to be performed immediately. """
  198. while self.deferredGenerates:
  199. msgType, extra = self.deferredGenerates[0]
  200. del self.deferredGenerates[0]
  201. self.replayDeferredGenerate(msgType, extra)
  202. taskMgr.remove('deferredGenerate')
  203. def replayDeferredGenerate(self, msgType, extra):
  204. """ Override this to do something appropriate with deferred
  205. "generate" messages when they are replayed().
  206. """
  207. if msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER:
  208. # It's a generate message.
  209. doId = extra
  210. if doId in self.deferredDoIds:
  211. args, deferrable, dg, updates = self.deferredDoIds[doId]
  212. del self.deferredDoIds[doId]
  213. self.__doGenerate(*args)
  214. if deferrable:
  215. self.lastGenerate = globalClock.getFrameTime()
  216. for dg, di in updates:
  217. # non-DC updates that need to be played back in-order are
  218. # stored as (msgType, (dg, di))
  219. if type(di) is types.TupleType:
  220. msgType = dg
  221. dg, di = di
  222. self.replayDeferredGenerate(msgType, (dg, di))
  223. else:
  224. # ovUpdated is set to True since its OV
  225. # is assumbed to have occured when the
  226. # deferred update was originally received
  227. self.__doUpdate(doId, di, True)
  228. else:
  229. self.notify.warning("Ignoring deferred message %s" % (msgType))
  230. def __doDeferredGenerate(self, task):
  231. """ This is the task that generates an object on the deferred
  232. queue. """
  233. now = globalClock.getFrameTime()
  234. while self.deferredGenerates:
  235. if now - self.lastGenerate < self.deferInterval:
  236. # Come back later.
  237. return Task.again
  238. # Generate the next deferred object.
  239. msgType, extra = self.deferredGenerates[0]
  240. del self.deferredGenerates[0]
  241. self.replayDeferredGenerate(msgType, extra)
  242. # All objects are generaetd.
  243. return Task.done
  244. def handleGenerateWithRequiredOtherOwner(self, di):
  245. # Get the class Id
  246. classId = di.getUint16()
  247. # Get the DO Id
  248. doId = di.getUint32()
  249. # parentId and zoneId are not relevant here
  250. parentId = di.getUint32()
  251. zoneId = di.getUint32()
  252. # Look up the dclass
  253. dclass = self.dclassesByNumber[classId]
  254. dclass.startGenerate()
  255. # Create a new distributed object, and put it in the dictionary
  256. distObj = self.generateWithRequiredOtherFieldsOwner(dclass, doId, di)
  257. dclass.stopGenerate()
  258. def handleQuietZoneGenerateWithRequired(self, di):
  259. # Special handler for quiet zone generates -- we need to filter
  260. parentId = di.getUint32()
  261. zoneId = di.getUint32()
  262. assert parentId in self.doId2do
  263. # Get the class Id
  264. classId = di.getUint16()
  265. # Get the DO Id
  266. doId = di.getUint32()
  267. # Look up the dclass
  268. dclass = self.dclassesByNumber[classId]
  269. dclass.startGenerate()
  270. distObj = self.generateWithRequiredFields(dclass, doId, di, parentId, zoneId)
  271. dclass.stopGenerate()
  272. def handleQuietZoneGenerateWithRequiredOther(self, di):
  273. # Special handler for quiet zone generates -- we need to filter
  274. parentId = di.getUint32()
  275. zoneId = di.getUint32()
  276. assert parentId in self.doId2do
  277. # Get the class Id
  278. classId = di.getUint16()
  279. # Get the DO Id
  280. doId = di.getUint32()
  281. # Look up the dclass
  282. dclass = self.dclassesByNumber[classId]
  283. dclass.startGenerate()
  284. distObj = self.generateWithRequiredOtherFields(dclass, doId, di, parentId, zoneId)
  285. dclass.stopGenerate()
  286. def generateWithRequiredFields(self, dclass, doId, di, parentId, zoneId):
  287. if self.doId2do.has_key(doId):
  288. # ...it is in our dictionary.
  289. # Just update it.
  290. distObj = self.doId2do[doId]
  291. assert distObj.dclass == dclass
  292. distObj.generate()
  293. distObj.setLocation(parentId, zoneId)
  294. distObj.updateRequiredFields(dclass, di)
  295. # updateRequiredFields calls announceGenerate
  296. elif self.cache.contains(doId):
  297. # ...it is in the cache.
  298. # Pull it out of the cache:
  299. distObj = self.cache.retrieve(doId)
  300. assert distObj.dclass == dclass
  301. # put it in the dictionary:
  302. self.doId2do[doId] = distObj
  303. # and update it.
  304. distObj.generate()
  305. # make sure we don't have a stale location
  306. distObj.parentId = None
  307. distObj.zoneId = None
  308. distObj.setLocation(parentId, zoneId)
  309. distObj.updateRequiredFields(dclass, di)
  310. # updateRequiredFields calls announceGenerate
  311. else:
  312. # ...it is not in the dictionary or the cache.
  313. # Construct a new one
  314. classDef = dclass.getClassDef()
  315. if classDef == None:
  316. self.notify.error("Could not create an undefined %s object." % (dclass.getName()))
  317. distObj = classDef(self)
  318. distObj.dclass = dclass
  319. # Assign it an Id
  320. distObj.doId = doId
  321. # Put the new do in the dictionary
  322. self.doId2do[doId] = distObj
  323. # Update the required fields
  324. distObj.generateInit() # Only called when constructed
  325. distObj._retrieveCachedData()
  326. distObj.generate()
  327. distObj.setLocation(parentId, zoneId)
  328. distObj.updateRequiredFields(dclass, di)
  329. # updateRequiredFields calls announceGenerate
  330. print "New DO:%s, dclass:%s"%(doId, dclass.getName())
  331. return distObj
  332. def generateWithRequiredOtherFields(self, dclass, doId, di,
  333. parentId = None, zoneId = None):
  334. if self.doId2do.has_key(doId):
  335. # ...it is in our dictionary.
  336. # Just update it.
  337. distObj = self.doId2do[doId]
  338. assert distObj.dclass == dclass
  339. distObj.generate()
  340. distObj.setLocation(parentId, zoneId)
  341. distObj.updateRequiredOtherFields(dclass, di)
  342. # updateRequiredOtherFields calls announceGenerate
  343. elif self.cache.contains(doId):
  344. # ...it is in the cache.
  345. # Pull it out of the cache:
  346. distObj = self.cache.retrieve(doId)
  347. assert distObj.dclass == dclass
  348. # put it in the dictionary:
  349. self.doId2do[doId] = distObj
  350. # and update it.
  351. distObj.generate()
  352. # make sure we don't have a stale location
  353. distObj.parentId = None
  354. distObj.zoneId = None
  355. distObj.setLocation(parentId, zoneId)
  356. distObj.updateRequiredOtherFields(dclass, di)
  357. # updateRequiredOtherFields calls announceGenerate
  358. else:
  359. # ...it is not in the dictionary or the cache.
  360. # Construct a new one
  361. classDef = dclass.getClassDef()
  362. if classDef == None:
  363. self.notify.error("Could not create an undefined %s object." % (dclass.getName()))
  364. distObj = classDef(self)
  365. distObj.dclass = dclass
  366. # Assign it an Id
  367. distObj.doId = doId
  368. # Put the new do in the dictionary
  369. self.doId2do[doId] = distObj
  370. # Update the required fields
  371. distObj.generateInit() # Only called when constructed
  372. distObj._retrieveCachedData()
  373. distObj.generate()
  374. distObj.setLocation(parentId, zoneId)
  375. distObj.updateRequiredOtherFields(dclass, di)
  376. # updateRequiredOtherFields calls announceGenerate
  377. return distObj
  378. def generateWithRequiredOtherFieldsOwner(self, dclass, doId, di):
  379. if self.doId2ownerView.has_key(doId):
  380. # ...it is in our dictionary.
  381. # Just update it.
  382. self.notify.error('duplicate owner generate for %s (%s)' % (
  383. doId, dclass.getName()))
  384. distObj = self.doId2ownerView[doId]
  385. assert distObj.dclass == dclass
  386. distObj.generate()
  387. distObj.updateRequiredOtherFields(dclass, di)
  388. # updateRequiredOtherFields calls announceGenerate
  389. elif self.cacheOwner.contains(doId):
  390. # ...it is in the cache.
  391. # Pull it out of the cache:
  392. distObj = self.cacheOwner.retrieve(doId)
  393. assert distObj.dclass == dclass
  394. # put it in the dictionary:
  395. self.doId2ownerView[doId] = distObj
  396. # and update it.
  397. distObj.generate()
  398. distObj.updateRequiredOtherFields(dclass, di)
  399. # updateRequiredOtherFields calls announceGenerate
  400. else:
  401. # ...it is not in the dictionary or the cache.
  402. # Construct a new one
  403. classDef = dclass.getOwnerClassDef()
  404. if classDef == None:
  405. self.notify.error("Could not create an undefined %s object. Have you created an owner view?" % (dclass.getName()))
  406. distObj = classDef(self)
  407. distObj.dclass = dclass
  408. # Assign it an Id
  409. distObj.doId = doId
  410. # Put the new do in the dictionary
  411. self.doId2ownerView[doId] = distObj
  412. # Update the required fields
  413. distObj.generateInit() # Only called when constructed
  414. distObj.generate()
  415. distObj.updateRequiredOtherFields(dclass, di)
  416. # updateRequiredOtherFields calls announceGenerate
  417. return distObj
  418. def handleDisable(self, di, ownerView=False):
  419. # Get the DO Id
  420. doId = di.getUint32()
  421. # disable it.
  422. self.disableDoId(doId, ownerView)
  423. def disableDoId(self, doId, ownerView=False):
  424. table, cache = self.getTables(ownerView)
  425. # Make sure the object exists
  426. if table.has_key(doId):
  427. # Look up the object
  428. distObj = table[doId]
  429. # remove the object from the dictionary
  430. del table[doId]
  431. # Only cache the object if it is a "cacheable" type
  432. # object; this way we don't clutter up the caches with
  433. # trivial objects that don't benefit from caching.
  434. # also don't try to cache an object that is delayDeleted
  435. cached = False
  436. if distObj.getCacheable() and distObj.getDelayDeleteCount() <= 0:
  437. cached = cache.cache(distObj)
  438. if not cached:
  439. distObj.deleteOrDelay()
  440. if distObj.getDelayDeleteCount() <= 0:
  441. # make sure we're not leaking
  442. distObj.detectLeaks()
  443. elif self.deferredDoIds.has_key(doId):
  444. # The object had been deferred. Great; we don't even have
  445. # to generate it now.
  446. del self.deferredDoIds[doId]
  447. i = self.deferredGenerates.index((CLIENT_CREATE_OBJECT_REQUIRED_OTHER, doId))
  448. del self.deferredGenerates[i]
  449. if len(self.deferredGenerates) == 0:
  450. taskMgr.remove('deferredGenerate')
  451. else:
  452. self._logFailedDisable(doId, ownerView)
  453. def _logFailedDisable(self, doId, ownerView):
  454. self.notify.warning(
  455. "Disable failed. DistObj "
  456. + str(doId) +
  457. " is not in dictionary, ownerView=%s" % ownerView)
  458. def handleDelete(self, di):
  459. # overridden by ToontownClientRepository
  460. assert 0
  461. def handleUpdateField(self, di):
  462. """
  463. This method is called when a CLIENT_OBJECT_UPDATE_FIELD
  464. message is received; it decodes the update, unpacks the
  465. arguments, and calls the corresponding method on the indicated
  466. DistributedObject.
  467. In fact, this method is exactly duplicated by the C++ method
  468. cConnectionRepository::handle_update_field(), which was
  469. written to optimize the message loop by handling all of the
  470. CLIENT_OBJECT_UPDATE_FIELD messages in C++. That means that
  471. nowadays, this Python method will probably never be called,
  472. since UPDATE_FIELD messages will not even be passed to the
  473. Python message handlers. But this method remains for
  474. documentation purposes, and also as a "just in case" handler
  475. in case we ever do come across a situation in the future in
  476. which python might handle the UPDATE_FIELD message.
  477. """
  478. # Get the DO Id
  479. doId = di.getUint32()
  480. ovUpdated = self.__doUpdateOwner(doId, di)
  481. if doId in self.deferredDoIds:
  482. # This object hasn't really been generated yet. Sit on
  483. # the update.
  484. args, deferrable, dg0, updates = self.deferredDoIds[doId]
  485. # Keep a copy of the datagram, and move the di to the copy
  486. dg = Datagram(di.getDatagram())
  487. di = DatagramIterator(dg, di.getCurrentIndex())
  488. updates.append((dg, di))
  489. else:
  490. # This object has been fully generated. It's OK to update.
  491. self.__doUpdate(doId, di, ovUpdated)
  492. def __doUpdate(self, doId, di, ovUpdated):
  493. # Find the DO
  494. do = self.doId2do.get(doId)
  495. if do is not None:
  496. # Let the dclass finish the job
  497. do.dclass.receiveUpdate(do, di)
  498. elif not ovUpdated:
  499. # this next bit is looking for avatar handles so that if you get an update
  500. # for an avatar that isn't in your doId2do table but there is a
  501. # avatar handle for that object then it's messages will be forwarded to that
  502. # object. We are currently using that for whisper echoing
  503. # if you need a more general perpose system consider registering proxy objects on
  504. # a dict and adding the avatar handles to that dict when they are created
  505. # then change/remove the old method. I didn't do that because I couldn't think
  506. # of a use for it. -JML
  507. try :
  508. handle = self.identifyAvatar(doId)
  509. if handle:
  510. dclass = self.dclassesByName[handle.dclassName]
  511. dclass.receiveUpdate(handle, di)
  512. else:
  513. self.notify.warning(
  514. "Asked to update non-existent DistObj " + str(doId))
  515. except:
  516. self.notify.warning(
  517. "Asked to update non-existent DistObj " + str(doId) + "and failed to find it")
  518. def __doUpdateOwner(self, doId, di):
  519. ovObj = self.doId2ownerView.get(doId)
  520. if ovObj:
  521. odg = Datagram(di.getDatagram())
  522. odi = DatagramIterator(odg, di.getCurrentIndex())
  523. ovObj.dclass.receiveUpdate(ovObj, odi)
  524. return True
  525. return False
  526. def handleGoGetLost(self, di):
  527. # The server told us it's about to drop the connection on us.
  528. # Get ready!
  529. if (di.getRemainingSize() > 0):
  530. self.bootedIndex = di.getUint16()
  531. self.bootedText = di.getString()
  532. self.notify.warning(
  533. "Server is booting us out (%d): %s" % (self.bootedIndex, self.bootedText))
  534. else:
  535. self.bootedIndex = None
  536. self.bootedText = None
  537. self.notify.warning(
  538. "Server is booting us out with no explanation.")
  539. # disconnect now, don't wait for send/recv to fail
  540. self.stopReaderPollTask()
  541. self.lostConnection()
  542. def handleServerHeartbeat(self, di):
  543. # Got a heartbeat message from the server.
  544. if base.config.GetBool('server-heartbeat-info', 1):
  545. self.notify.info("Server heartbeat.")
  546. def handleSystemMessage(self, di):
  547. # Got a system message from the server.
  548. message = di.getString()
  549. self.notify.info('Message from server: %s' % (message))
  550. return message
  551. def getObjectsOfClass(self, objClass):
  552. """ returns dict of doId:object, containing all objects
  553. that inherit from 'class'. returned dict is safely mutable. """
  554. doDict = {}
  555. for doId, do in self.doId2do.items():
  556. if isinstance(do, objClass):
  557. doDict[doId] = do
  558. return doDict
  559. def getObjectsOfExactClass(self, objClass):
  560. """ returns dict of doId:object, containing all objects that
  561. are exactly of type 'class' (neglecting inheritance). returned
  562. dict is safely mutable. """
  563. doDict = {}
  564. for doId, do in self.doId2do.items():
  565. if do.__class__ == objClass:
  566. doDict[doId] = do
  567. return doDict
  568. def sendSetLocation(self, doId, parentId, zoneId):
  569. datagram = PyDatagram()
  570. datagram.addUint16(CLIENT_OBJECT_LOCATION)
  571. datagram.addUint32(doId)
  572. datagram.addUint32(parentId)
  573. datagram.addUint32(zoneId)
  574. self.send(datagram)
  575. def sendHeartbeat(self):
  576. datagram = PyDatagram()
  577. # Add message type
  578. datagram.addUint16(CLIENT_HEARTBEAT)
  579. # Send it!
  580. self.send(datagram)
  581. self.lastHeartbeat = globalClock.getRealTime()
  582. # This is important enough to consider flushing immediately
  583. # (particularly if we haven't run readerPollTask recently).
  584. self.considerFlush()
  585. def considerHeartbeat(self):
  586. """Send a heartbeat message if we haven't sent one recently."""
  587. if not self.heartbeatStarted:
  588. self.notify.debug("Heartbeats not started; not sending.")
  589. return
  590. elapsed = globalClock.getRealTime() - self.lastHeartbeat
  591. if elapsed < 0 or elapsed > self.heartbeatInterval:
  592. # It's time to send the heartbeat again (or maybe someone
  593. # reset the clock back).
  594. self.notify.info("Sending heartbeat mid-frame.")
  595. self.startHeartbeat()
  596. def stopHeartbeat(self):
  597. taskMgr.remove("heartBeat")
  598. self.heartbeatStarted = 0
  599. def startHeartbeat(self):
  600. self.stopHeartbeat()
  601. self.heartbeatStarted = 1
  602. self.sendHeartbeat()
  603. self.waitForNextHeartBeat()
  604. def sendHeartbeatTask(self, task):
  605. self.sendHeartbeat()
  606. return Task.again
  607. def waitForNextHeartBeat(self):
  608. taskMgr.doMethodLater(self.heartbeatInterval, self.sendHeartbeatTask,
  609. "heartBeat", taskChain = 'net')
  610. def replaceMethod(self, oldMethod, newFunction):
  611. return 0
  612. def getWorld(self, doId):
  613. # Get the world node for this object
  614. obj = self.doId2do[doId]
  615. worldNP = obj.getParent()
  616. while 1:
  617. nextNP = worldNP.getParent()
  618. if nextNP == render:
  619. break
  620. elif worldNP.isEmpty():
  621. return None
  622. return worldNP
  623. def isLive(self):
  624. if base.config.GetBool('force-live', 0):
  625. return True
  626. return not (__dev__ or launcher.isTestServer())
  627. def isLocalId(self, id):
  628. # By default, no ID's are local. See also
  629. # ClientRepository.isLocalId().
  630. return 0
  631. # methods for tracking delaydeletes
  632. def _addDelayDeletedDO(self, do):
  633. # use the id of the object, it's possible to have multiple DelayDeleted instances
  634. # with identical doIds if an object gets deleted then re-generated
  635. key = id(do)
  636. assert key not in self._delayDeletedDOs
  637. self._delayDeletedDOs[key] = do
  638. def _removeDelayDeletedDO(self, do):
  639. key = id(do)
  640. del self._delayDeletedDOs[key]
  641. def printDelayDeletes(self):
  642. print 'DelayDeletes:'
  643. print '============='
  644. for obj in self._delayDeletedDOs.itervalues():
  645. print '%s\t%s (%s)\tdelayDeletes=%s' % (
  646. obj.doId, safeRepr(obj), itype(obj), obj.getDelayDeleteNames())