ClientRepository.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855
  1. """ClientRepository module: contains the ClientRepository class"""
  2. from pandac.PandaModules import *
  3. from MsgTypes import *
  4. from direct.task import Task
  5. from direct.directnotify import DirectNotifyGlobal
  6. import CRCache
  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. class ClientRepository(ConnectionRepository):
  16. """
  17. This maintains a client-side connection with a Panda server.
  18. It currently supports several different versions of the server:
  19. within the VR Studio, we are currently in transition from the
  20. Toontown server to the OTP server; people outside the VR studio
  21. will use the Panda LAN server provided by CMU.
  22. """
  23. notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepository")
  24. def __init__(self):
  25. ConnectionRepository.__init__(self, base.config)
  26. self.context=100000
  27. self.setClientDatagram(1)
  28. self.recorder = base.recorder
  29. if wantOtpServer:
  30. # this is used to imulate the old setzone behavior
  31. # with set locationa and set interest
  32. self.old_setzone_interest_handle = None
  33. self.readDCFile()
  34. self.cache=CRCache.CRCache()
  35. self.serverDelta = 0
  36. self.bootedIndex = None
  37. self.bootedText = None
  38. if 0: # unused:
  39. self.worldScale = render.attachNewNode("worldScale") # for grid zones.
  40. self.worldScale.setScale(base.config.GetFloat('world-scale', 100))
  41. self.priorWorldPos = None
  42. # create a parentMgr to handle distributed reparents
  43. # this used to be 'token2nodePath'
  44. self.parentMgr = ParentMgr.ParentMgr()
  45. # The RelatedObjectMgr helps distributed objects find each
  46. # other.
  47. self.relatedObjectMgr = RelatedObjectMgr.RelatedObjectMgr(self)
  48. # Keep track of how recently we last sent a heartbeat message.
  49. # We want to keep these coming at heartbeatInterval seconds.
  50. self.heartbeatInterval = base.config.GetDouble('heartbeat-interval', 10)
  51. self.heartbeatStarted = 0
  52. self.lastHeartbeat = 0
  53. # By default, the ClientRepository is set up to respond to
  54. # datagrams from the CMU Panda LAN server. You can
  55. # reassign this member to change the response behavior
  56. # according to game context.
  57. self.handler = self.publicServerDatagramHandler
  58. # The DOID allocator. The CMU LAN server may choose to
  59. # send us a block of DOIDs. If it chooses to do so, then we
  60. # may create objects, using those DOIDs. These structures are
  61. # only used in conjunction with the CMU LAN server.
  62. self.DOIDbase = 0
  63. self.DOIDnext = 0
  64. self.DOIDlast = 0
  65. # Define uniqueName
  66. def uniqueName(self, desc):
  67. return desc
  68. def abruptCleanup(self):
  69. """
  70. Call this method to clean up any pending hooks or tasks on
  71. distributed objects, but leave the ClientRepository in a sane
  72. state for creating more distributed objects.
  73. """
  74. self.relatedObjectMgr.abortAllRequests()
  75. def sendDisconnect(self):
  76. if self.isConnected():
  77. # Tell the game server that we're going:
  78. datagram = PyDatagram()
  79. # Add message type
  80. datagram.addUint16(CLIENT_DISCONNECT)
  81. # Send the message
  82. self.send(datagram)
  83. self.notify.info("Sent disconnect message to server")
  84. self.disconnect()
  85. self.stopHeartbeat()
  86. if 0: # Code that became obsolete before it was used:
  87. def setWorldOffset(self, xOffset=0, yOffset=0):
  88. self.worldXOffset=xOffset
  89. self.worldYOffset=yOffset
  90. def getWorldPos(self, nodePath):
  91. pos = nodePath.getPos(self.worldScale)
  92. return (int(round(pos.getX())), int(round(pos.getY())))
  93. def sendWorldPos(self, x, y):
  94. # The server will need to know the world
  95. # offset of our current render node path
  96. # and adjust the x, y accordingly. At one
  97. # point I considered adding the world offset
  98. # here, but that would just use extra bits.
  99. onScreenDebug.add("worldPos", "%-4d, %-4d"%(x, y))
  100. return #*#
  101. datagram = PyDatagram()
  102. # Add message type
  103. datagram.addUint16(CLIENT_SET_WORLD_POS)
  104. # Add x
  105. datagram.addInt16(x)
  106. # Add y
  107. datagram.addSint16(y)
  108. # send the message
  109. self.send(datagram)
  110. def checkWorldPos(self, nodePath):
  111. worldPos = self.getWorldPos(nodePath)
  112. if self.priorWorldPos != worldPos:
  113. self.priorWorldPos = worldPos
  114. self.sendWorldPos(worldPos[0], worldPos[1])
  115. def allocateContext(self):
  116. self.context+=1
  117. return self.context
  118. def setServerDelta(self, delta):
  119. """
  120. Indicates the approximate difference in seconds between the
  121. client's clock and the server's clock, in universal time (not
  122. including timezone shifts). This is mainly useful for
  123. reporting synchronization information to the logs; don't
  124. depend on it for any precise timing requirements.
  125. Also see Notify.setServerDelta(), which also accounts for a
  126. timezone shift.
  127. """
  128. self.serverDelta = delta
  129. def getServerDelta(self):
  130. return self.serverDelta
  131. def getServerTimeOfDay(self):
  132. """
  133. Returns the current time of day (seconds elapsed since the
  134. 1972 epoch) according to the server's clock. This is in GMT,
  135. and hence is irrespective of timezones.
  136. The value is computed based on the client's clock and the
  137. known delta from the server's clock, which is not terribly
  138. precisely measured and may drift slightly after startup, but
  139. it should be accurate plus or minus a couple of seconds.
  140. """
  141. return time.time() + self.serverDelta
  142. def handleGenerateWithRequired(self, di):
  143. if wantOtpServer:
  144. parentId = di.getUint32()
  145. zoneId = di.getUint32()
  146. # Get the class Id
  147. classId = di.getUint16()
  148. # Get the DO Id
  149. doId = di.getUint32()
  150. # Look up the dclass
  151. dclass = self.dclassesByNumber[classId]
  152. dclass.startGenerate()
  153. # Create a new distributed object, and put it in the dictionary
  154. if wantOtpServer:
  155. distObj = self.generateWithRequiredFields(dclass, doId, di, parentId, zoneId)
  156. else:
  157. distObj = self.generateWithRequiredFields(dclass, doId, di)
  158. dclass.stopGenerate()
  159. def handleGenerateWithRequiredOther(self, di):
  160. if wantOtpServer:
  161. parentId = di.getUint32()
  162. zoneId = di.getUint32()
  163. # Get the class Id
  164. classId = di.getUint16()
  165. # Get the DO Id
  166. doId = di.getUint32()
  167. # Look up the dclass
  168. dclass = self.dclassesByNumber[classId]
  169. dclass.startGenerate()
  170. # Create a new distributed object, and put it in the dictionary
  171. if wantOtpServer:
  172. distObj = self.generateWithRequiredOtherFields(dclass, doId, di, parentId, zoneId)
  173. else:
  174. distObj = self.generateWithRequiredOtherFields(dclass, doId, di)
  175. dclass.stopGenerate()
  176. def handleQuietZoneGenerateWithRequired(self, di):
  177. # Special handler for quiet zone generates -- we need to filter
  178. if wantOtpServer:
  179. parentId = di.getUint32()
  180. zoneId = di.getUint32()
  181. # Get the class Id
  182. classId = di.getUint16()
  183. # Get the DO Id
  184. doId = di.getUint32()
  185. # Look up the dclass
  186. dclass = self.dclassesByNumber[classId]
  187. dclass.startGenerate()
  188. # If the class is a neverDisable class (which implies uberzone) we
  189. # should go ahead and generate it even though we are in the quiet zone
  190. if not wantOtpServer:
  191. if dclass.getClassDef().neverDisable:
  192. # Create a new distributed object, and put it in the dictionary
  193. distObj = self.generateWithRequiredFields(dclass, doId, di)
  194. else:
  195. distObj = self.generateWithRequiredFields(dclass, doId, di, parentId, zoneId)
  196. dclass.stopGenerate()
  197. def handleQuietZoneGenerateWithRequiredOther(self, di):
  198. # Special handler for quiet zone generates -- we need to filter
  199. if wantOtpServer:
  200. parentId = di.getUint32()
  201. zoneId = di.getUint32()
  202. # Get the class Id
  203. classId = di.getUint16()
  204. # Get the DO Id
  205. doId = di.getUint32()
  206. # Look up the dclass
  207. dclass = self.dclassesByNumber[classId]
  208. # If the class is a neverDisable class (which implies uberzone) we
  209. # should go ahead and generate it even though we are in the quiet zone
  210. dclass.startGenerate()
  211. if not wantOtpServer:
  212. if dclass.getClassDef().neverDisable:
  213. # Create a new distributed object, and put it in the dictionary
  214. distObj = self.generateWithRequiredOtherFields(dclass, doId, di)
  215. else:
  216. distObj = self.generateWithRequiredOtherFields(dclass, doId, di, parentId, zoneId)
  217. dclass.stopGenerate()
  218. # wantOtpServer: remove the None defaults when we remove this config variable
  219. def generateWithRequiredFields(self, dclass, doId, di, parentId = None, zoneId = None):
  220. if self.doId2do.has_key(doId):
  221. # ...it is in our dictionary.
  222. # Just update it.
  223. distObj = self.doId2do[doId]
  224. assert(distObj.dclass == dclass)
  225. distObj.generate()
  226. if wantOtpServer:
  227. distObj.setLocation(parentId, zoneId)
  228. distObj.updateRequiredFields(dclass, di)
  229. # updateRequiredFields calls announceGenerate
  230. elif self.cache.contains(doId):
  231. # ...it is in the cache.
  232. # Pull it out of the cache:
  233. distObj = self.cache.retrieve(doId)
  234. assert(distObj.dclass == dclass)
  235. # put it in the dictionary:
  236. self.doId2do[doId] = distObj
  237. # and update it.
  238. distObj.generate()
  239. if wantOtpServer:
  240. distObj.setLocation(parentId, zoneId)
  241. distObj.updateRequiredFields(dclass, di)
  242. # updateRequiredFields calls announceGenerate
  243. else:
  244. # ...it is not in the dictionary or the cache.
  245. # Construct a new one
  246. classDef = dclass.getClassDef()
  247. if classDef == None:
  248. self.notify.error("Could not create an undefined %s object." % (dclass.getName()))
  249. distObj = classDef(self)
  250. distObj.dclass = dclass
  251. # Assign it an Id
  252. distObj.doId = doId
  253. # Put the new do in the dictionary
  254. self.doId2do[doId] = distObj
  255. # Update the required fields
  256. distObj.generateInit() # Only called when constructed
  257. distObj.generate()
  258. if wantOtpServer:
  259. distObj.setLocation(parentId, zoneId)
  260. distObj.updateRequiredFields(dclass, di)
  261. # updateRequiredFields calls announceGenerate
  262. if wantOtpServer:
  263. print "New DO:%s, dclass:%s"%(doId, dclass.getName())
  264. return distObj
  265. def generateGlobalObject(self, doId, dcname):
  266. # Look up the dclass
  267. dclass = self.dclassesByName[dcname]
  268. # Create a new distributed object, and put it in the dictionary
  269. #distObj = self.generateWithRequiredFields(dclass, doId, di)
  270. # Construct a new one
  271. classDef = dclass.getClassDef()
  272. if classDef == None:
  273. self.notify.error("Could not create an undefined %s object."%(
  274. dclass.getName()))
  275. distObj = classDef(self)
  276. distObj.dclass = dclass
  277. # Assign it an Id
  278. distObj.doId = doId
  279. # Put the new do in the dictionary
  280. self.doId2do[doId] = distObj
  281. # Update the required fields
  282. distObj.generateInit() # Only called when constructed
  283. distObj.generate()
  284. if wantOtpServer:
  285. # TODO: ROGER: where should we get parentId and zoneId?
  286. parentId = None
  287. zoneId = None
  288. distObj.setLocation(parentId, zoneId)
  289. # updateRequiredFields calls announceGenerate
  290. return distObj
  291. def generateWithRequiredOtherFields(self, dclass, doId, di,
  292. parentId = None, zoneId = None):
  293. if self.doId2do.has_key(doId):
  294. # ...it is in our dictionary.
  295. # Just update it.
  296. distObj = self.doId2do[doId]
  297. assert(distObj.dclass == dclass)
  298. distObj.generate()
  299. if wantOtpServer:
  300. distObj.setLocation(parentId, zoneId)
  301. distObj.updateRequiredOtherFields(dclass, di)
  302. # updateRequiredOtherFields calls announceGenerate
  303. elif self.cache.contains(doId):
  304. # ...it is in the cache.
  305. # Pull it out of the cache:
  306. distObj = self.cache.retrieve(doId)
  307. assert(distObj.dclass == dclass)
  308. # put it in the dictionary:
  309. self.doId2do[doId] = distObj
  310. # and update it.
  311. distObj.generate()
  312. if wantOtpServer:
  313. distObj.setLocation(parentId, zoneId)
  314. distObj.updateRequiredOtherFields(dclass, di)
  315. # updateRequiredOtherFields calls announceGenerate
  316. else:
  317. # ...it is not in the dictionary or the cache.
  318. # Construct a new one
  319. classDef = dclass.getClassDef()
  320. if classDef == None:
  321. self.notify.error("Could not create an undefined %s object." % (dclass.getName()))
  322. distObj = classDef(self)
  323. distObj.dclass = dclass
  324. # Assign it an Id
  325. distObj.doId = doId
  326. # Put the new do in the dictionary
  327. self.doId2do[doId] = distObj
  328. # Update the required fields
  329. distObj.generateInit() # Only called when constructed
  330. distObj.generate()
  331. if wantOtpServer:
  332. distObj.setLocation(parentId, zoneId)
  333. distObj.updateRequiredOtherFields(dclass, di)
  334. # updateRequiredOtherFields calls announceGenerate
  335. return distObj
  336. def handleDisable(self, di):
  337. # Get the DO Id
  338. doId = di.getUint32()
  339. # disable it.
  340. self.disableDoId(doId)
  341. def disableDoId(self, doId):
  342. # Make sure the object exists
  343. if self.doId2do.has_key(doId):
  344. # Look up the object
  345. distObj = self.doId2do[doId]
  346. # remove the object from the dictionary
  347. del(self.doId2do[doId])
  348. # Only cache the object if it is a "cacheable" type
  349. # object; this way we don't clutter up the caches with
  350. # trivial objects that don't benefit from caching.
  351. if distObj.getCacheable():
  352. self.cache.cache(distObj)
  353. else:
  354. distObj.deleteOrDelay()
  355. else:
  356. ClientRepository.notify.warning(
  357. "Disable failed. DistObj "
  358. + str(doId) +
  359. " is not in dictionary")
  360. def handleDelete(self, di):
  361. # Get the DO Id
  362. doId = di.getUint32()
  363. self.deleteObject(doId)
  364. def deleteObject(self, doId):
  365. """
  366. Removes the object from the client's view of the world. This
  367. should normally not be called except in the case of error
  368. recovery, since the server will normally be responsible for
  369. deleting and disabling objects as they go out of scope.
  370. After this is called, future updates by server on this object
  371. will be ignored (with a warning message). The object will
  372. become valid again the next time the server sends a generate
  373. message for this doId.
  374. This is not a distributed message and does not delete the
  375. object on the server or on any other client.
  376. """
  377. if self.doId2do.has_key(doId):
  378. # If it is in the dictionary, remove it.
  379. obj = self.doId2do[doId]
  380. # Remove it from the dictionary
  381. del(self.doId2do[doId])
  382. # Disable, announce, and delete the object itself...
  383. # unless delayDelete is on...
  384. obj.deleteOrDelay()
  385. elif self.cache.contains(doId):
  386. # If it is in the cache, remove it.
  387. self.cache.delete(doId)
  388. else:
  389. # Otherwise, ignore it
  390. ClientRepository.notify.warning(
  391. "Asked to delete non-existent DistObj " + str(doId))
  392. def handleUpdateField(self, di):
  393. """
  394. This method is called when a CLIENT_OBJECT_UPDATE_FIELD
  395. message is received; it decodes the update, unpacks the
  396. arguments, and calls the corresponding method on the indicated
  397. DistributedObject.
  398. In fact, this method is exactly duplicated by the C++ method
  399. cConnectionRepository::handle_update_field(), which was
  400. written to optimize the message loop by handling all of the
  401. CLIENT_OBJECT_UPDATE_FIELD messages in C++. That means that
  402. nowadays, this Python method will probably never be called,
  403. since UPDATE_FIELD messages will not even be passed to the
  404. Python message handlers. But this method remains for
  405. documentation purposes, and also as a "just in case" handler
  406. in case we ever do come across a situation in the future in
  407. which python might handle the UPDATE_FIELD message.
  408. """
  409. # Get the DO Id
  410. doId = di.getUint32()
  411. #print("Updating " + str(doId))
  412. # Find the DO
  413. do = self.doId2do.get(doId)
  414. if do is not None:
  415. # Let the dclass finish the job
  416. do.dclass.receiveUpdate(do, di)
  417. else:
  418. ClientRepository.notify.warning(
  419. "Asked to update non-existent DistObj " + str(doId))
  420. def handleGoGetLost(self, di):
  421. # The server told us it's about to drop the connection on us.
  422. # Get ready!
  423. if (di.getRemainingSize() > 0):
  424. self.bootedIndex = di.getUint16()
  425. self.bootedText = di.getString()
  426. ClientRepository.notify.warning(
  427. "Server is booting us out (%d): %s" % (self.bootedIndex, self.bootedText))
  428. else:
  429. self.bootedIndex = None
  430. self.bootedText = None
  431. ClientRepository.notify.warning(
  432. "Server is booting us out with no explanation.")
  433. def handleServerHeartbeat(self, di):
  434. # Got a heartbeat message from the server.
  435. if base.config.GetBool('server-heartbeat-info', 1):
  436. ClientRepository.notify.info("Server heartbeat.")
  437. def handleSystemMessage(self, di):
  438. # Got a system message from the server.
  439. message = di.getString()
  440. self.notify.info('Message from server: %s' % (message))
  441. return message
  442. def handleSetDOIDrange(self, di):
  443. # This method is only used in conjunction with the CMU LAN
  444. # server.
  445. self.DOIDbase = di.getUint32()
  446. self.DOIDlast = self.DOIDbase + di.getUint32()
  447. self.DOIDnext = self.DOIDbase
  448. def handleRequestGenerates(self, di):
  449. # When new clients join the zone of an object, they need to hear
  450. # about it, so we send out all of our information about objects in
  451. # that particular zone.
  452. # This method is only used in conjunction with the CMU LAN
  453. # server.
  454. assert self.DOIDnext < self.DOIDlast
  455. zone = di.getUint32()
  456. for obj in self.doId2do.values():
  457. if obj.zone == zone:
  458. id = obj.doId
  459. if (self.isLocalId(id)):
  460. self.send(obj.dclass.clientFormatGenerate(obj, id, zone, []))
  461. def handleMessageType(self, msgType, di):
  462. if msgType == CLIENT_GO_GET_LOST:
  463. self.handleGoGetLost(di)
  464. elif msgType == CLIENT_HEARTBEAT:
  465. self.handleServerHeartbeat(di)
  466. elif msgType == CLIENT_SYSTEM_MESSAGE:
  467. self.handleSystemMessage(di)
  468. elif wantOtpServer:
  469. if msgType == CLIENT_CREATE_OBJECT_REQUIRED:
  470. self.handleGenerateWithRequired(di)
  471. elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER:
  472. self.handleGenerateWithRequiredOther(di)
  473. elif msgType == CLIENT_OBJECT_UPDATE_FIELD:
  474. self.handleUpdateField(di)
  475. elif msgType == CLIENT_OBJECT_DISABLE_RESP:
  476. self.handleDisable(di)
  477. elif msgType == CLIENT_OBJECT_DELETE_RESP:
  478. self.handleDelete(di)
  479. elif msgType == CLIENT_CREATE_OBJECT_REQUIRED:
  480. self.handleGenerateWithRequired(di)
  481. elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER:
  482. self.handleGenerateWithRequiredOther(di)
  483. elif msgType == CLIENT_DONE_INTEREST_RESP:
  484. self.handleInterestDoneMessage(di)
  485. elif msgType == CLIENT_QUERY_ONE_FIELD_RESP:
  486. self.handleQueryOneFieldResp(di)
  487. elif msgType == CLIENT_OBJECT_LOCATION:
  488. self.handleObjectLocation(di)
  489. else:
  490. currentLoginState = self.loginFSM.getCurrentState()
  491. if currentLoginState:
  492. currentLoginStateName = currentLoginState.getName()
  493. else:
  494. currentLoginStateName = "None"
  495. currentGameState = self.gameFSM.getCurrentState()
  496. if currentGameState:
  497. currentGameStateName = currentGameState.getName()
  498. else:
  499. currentGameStateName = "None"
  500. ClientRepository.notify.warning(
  501. "Ignoring unexpected message type: " +
  502. str(msgType) +
  503. " login state: " +
  504. currentLoginStateName +
  505. " game state: " +
  506. currentGameStateName)
  507. else:
  508. currentLoginState = self.loginFSM.getCurrentState()
  509. if currentLoginState:
  510. currentLoginStateName = currentLoginState.getName()
  511. else:
  512. currentLoginStateName = "None"
  513. currentGameState = self.gameFSM.getCurrentState()
  514. if currentGameState:
  515. currentGameStateName = currentGameState.getName()
  516. else:
  517. currentGameStateName = "None"
  518. ClientRepository.notify.warning(
  519. "Ignoring unexpected message type: " +
  520. str(msgType) +
  521. " login state: " +
  522. currentLoginStateName +
  523. " game state: " +
  524. currentGameStateName)
  525. def createWithRequired(self, className, zoneId = 0, optionalFields=None):
  526. # This method is only used in conjunction with the CMU LAN
  527. # server.
  528. if self.DOIDnext >= self.DOIDlast:
  529. self.notify.error(
  530. "Cannot allocate a distributed object ID: all IDs used up.")
  531. return None
  532. id = self.DOIDnext
  533. self.DOIDnext = self.DOIDnext + 1
  534. dclass = self.dclassesByName[className]
  535. classDef = dclass.getClassDef()
  536. if classDef == None:
  537. self.notify.error("Could not create an undefined %s object." % (
  538. dclass.getName()))
  539. obj = classDef(self)
  540. obj.dclass = dclass
  541. obj.zone = zoneId
  542. obj.doId = id
  543. self.doId2do[id] = obj
  544. obj.generateInit()
  545. obj.generate()
  546. obj.announceGenerate()
  547. datagram = dclass.clientFormatGenerate(obj, id, zoneId, optionalFields)
  548. self.send(datagram)
  549. return obj
  550. def sendDisableMsg(self, doId):
  551. # This method is only used in conjunction with the CMU LAN
  552. # server.
  553. datagram = PyDatagram()
  554. datagram.addUint16(CLIENT_OBJECT_DISABLE)
  555. datagram.addUint32(doId)
  556. self.send(datagram)
  557. def sendDeleteMsg(self, doId):
  558. # This method is only used in conjunction with the CMU LAN
  559. # server.
  560. datagram = PyDatagram()
  561. datagram.addUint16(CLIENT_OBJECT_DELETE)
  562. datagram.addUint32(doId)
  563. self.send(datagram)
  564. def sendRemoveZoneMsg(self, zoneId, visibleZoneList=None):
  565. # This method is only used in conjunction with the CMU LAN
  566. # server.
  567. datagram = PyDatagram()
  568. datagram.addUint16(CLIENT_REMOVE_ZONE)
  569. datagram.addUint32(zoneId)
  570. # if we have an explicit list of visible zones, add them
  571. if visibleZoneList is not None:
  572. vzl = list(visibleZoneList)
  573. vzl.sort()
  574. assert PythonUtil.uniqueElements(vzl)
  575. for zone in vzl:
  576. datagram.addUint32(zone)
  577. # send the message
  578. self.send(datagram)
  579. def sendSetShardMsg(self, shardId):
  580. datagram = PyDatagram()
  581. # Add message type
  582. datagram.addUint16(CLIENT_SET_SHARD)
  583. # Add shard id
  584. datagram.addUint32(shardId)
  585. # send the message
  586. self.send(datagram)
  587. def getObjectsOfClass(self, objClass):
  588. """ returns dict of doId:object, containing all objects
  589. that inherit from 'class'. returned dict is safely mutable. """
  590. doDict = {}
  591. for doId, do in self.doId2do.items():
  592. if isinstance(do, objClass):
  593. doDict[doId] = do
  594. return doDict
  595. def getObjectsOfExactClass(self, objClass):
  596. """ returns dict of doId:object, containing all objects that
  597. are exactly of type 'class' (neglecting inheritance). returned
  598. dict is safely mutable. """
  599. doDict = {}
  600. for doId, do in self.doId2do.items():
  601. if do.__class__ == objClass:
  602. doDict[doId] = do
  603. return doDict
  604. if wantOtpServer:
  605. def sendEmulateSetZone(self, zoneId, visibleZoneList=None,
  606. parentIdin=None, event=None):
  607. """
  608. This Will Move The avatar and set an interest to that location ..
  609. """
  610. parentId = parentIdin
  611. if parentId is None:
  612. parentId = base.localAvatar.defaultShard
  613. MyAvID = base.localAvatar.doId
  614. # move thwe avatar..
  615. self.sendSetLocation(MyAvID,parentId,zoneId)
  616. # move the interest..
  617. InterestZones = zoneId
  618. if visibleZoneList is not None:
  619. InterestZones = visibleZoneList
  620. if(self.old_setzone_interest_handle == None):
  621. self.old_setzone_interest_handle = self.addInterest(
  622. parentId, InterestZones, "OldSetZone Imulator", event)
  623. else:
  624. self.alterInterest(self.old_setzone_interest_handle,
  625. parentId, InterestZones, "OldSetZone Imulator", event)
  626. def sendEmulateSetZoneOff(self):
  627. MyAvID = base.localAvatar.doId
  628. self.sendSetLocation(MyAvID,0,0)
  629. if self.old_setzone_interest_handle is not None:
  630. self.removeInterest(self.old_setzone_interest_handle)
  631. self.old_setzone_interest_handle = None
  632. def sendSetLocation(self,doId,parentId,zoneId):
  633. datagram = PyDatagram()
  634. datagram.addUint16(CLIENT_OBJECT_LOCATION)
  635. datagram.addUint32(doId)
  636. datagram.addUint32(parentId)
  637. datagram.addUint32(zoneId)
  638. self.send(datagram)
  639. else:
  640. def sendSetZoneMsg(self, zoneId, visibleZoneList=None):
  641. datagram = PyDatagram()
  642. # Add message type
  643. datagram.addUint16(CLIENT_SET_ZONE)
  644. # Add zone id
  645. datagram.addUint32(zoneId)
  646. # if we have an explicit list of visible zones, add them
  647. if visibleZoneList is not None:
  648. vzl = list(visibleZoneList)
  649. vzl.sort()
  650. assert PythonUtil.uniqueElements(vzl)
  651. for zone in vzl:
  652. datagram.addUint32(zone)
  653. # send the message
  654. self.send(datagram)
  655. def handleDatagram(self, di):
  656. if self.notify.getDebug():
  657. print "ClientRepository received datagram:"
  658. di.getDatagram().dumpHex(ostream)
  659. msgType = self.getMsgType()
  660. if not wantOtpServer:
  661. if msgType == CLIENT_DONE_SET_ZONE_RESP:
  662. self.handleSetZoneDone()
  663. if self.handler == None:
  664. self.handleMessageType(msgType, di)
  665. else:
  666. self.handler(msgType, di)
  667. # If we're processing a lot of datagrams within one frame, we
  668. # may forget to send heartbeats. Keep them coming!
  669. self.considerHeartbeat()
  670. def publicServerDatagramHandler(self, msgType, di):
  671. # These are the sort of messages we may expect from the public
  672. # Panda server.
  673. if msgType == CLIENT_SET_DOID_RANGE:
  674. self.handleSetDOIDrange(di)
  675. elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_RESP:
  676. self.handleGenerateWithRequired(di)
  677. elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER_RESP:
  678. self.handleGenerateWithRequiredOther(di)
  679. elif msgType == CLIENT_OBJECT_UPDATE_FIELD_RESP:
  680. self.handleUpdateField(di)
  681. elif msgType == CLIENT_OBJECT_DELETE_RESP:
  682. self.handleDelete(di)
  683. elif msgType == CLIENT_OBJECT_DISABLE_RESP:
  684. self.handleDisable(di)
  685. elif msgType == CLIENT_REQUEST_GENERATES:
  686. self.handleRequestGenerates(di)
  687. else:
  688. self.handleMessageType(msgType, di)
  689. def sendHeartbeat(self):
  690. datagram = PyDatagram()
  691. # Add message type
  692. datagram.addUint16(CLIENT_HEARTBEAT)
  693. # Send it!
  694. self.send(datagram)
  695. self.lastHeartbeat = globalClock.getRealTime()
  696. # This is important enough to consider flushing immediately
  697. # (particularly if we haven't run readerPollTask recently).
  698. self.considerFlush()
  699. def considerHeartbeat(self):
  700. """Send a heartbeat message if we haven't sent one recently."""
  701. if not self.heartbeatStarted:
  702. self.notify.debug("Heartbeats not started; not sending.")
  703. return
  704. elapsed = globalClock.getRealTime() - self.lastHeartbeat
  705. if elapsed < 0 or elapsed > self.heartbeatInterval:
  706. # It's time to send the heartbeat again (or maybe someone
  707. # reset the clock back).
  708. self.notify.info("Sending heartbeat mid-frame.")
  709. self.startHeartbeat()
  710. def stopHeartbeat(self):
  711. taskMgr.remove("heartBeat")
  712. self.heartbeatStarted = 0
  713. def startHeartbeat(self):
  714. self.stopHeartbeat()
  715. self.heartbeatStarted = 1
  716. self.sendHeartbeat()
  717. self.waitForNextHeartBeat()
  718. def sendHeartbeatTask(self, task):
  719. self.sendHeartbeat()
  720. self.waitForNextHeartBeat()
  721. return Task.done
  722. def waitForNextHeartBeat(self):
  723. taskMgr.doMethodLater(self.heartbeatInterval, self.sendHeartbeatTask,
  724. "heartBeat")
  725. def sendUpdateZone(self, obj, zoneId):
  726. # This method is only used in conjunction with the CMU LAN
  727. # server.
  728. id = obj.doId
  729. assert(self.isLocalId(id))
  730. self.sendDeleteMsg(id, 1)
  731. obj.zone = zoneId
  732. self.send(obj.dclass.clientFormatGenerate(obj, id, zoneId, []))
  733. def replaceMethod(self, oldMethod, newFunction):
  734. return 0
  735. def isLocalId(self,id):
  736. return ((id >= self.DOIDbase) and (id < self.DOIDlast))
  737. def haveCreateAuthority(self):
  738. return (self.DOIDlast > self.DOIDnext)
  739. def getWorld(self, doId):
  740. # Get the world node for this object
  741. obj = self.doId2do[doId]
  742. worldNP = obj.getParent()
  743. while 1:
  744. nextNP = worldNP.getParent()
  745. if nextNP == render:
  746. break
  747. elif worldNP.isEmpty():
  748. return None
  749. return worldNP