ClientRepository.py 36 KB

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