ClientRepository.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. """ClientRepository module: contains the ClientRepository class"""
  2. from PandaModules import *
  3. from MsgTypes import *
  4. import Task
  5. import DirectNotifyGlobal
  6. import ClientDistClass
  7. import CRCache
  8. import ConnectionRepository
  9. import PythonUtil
  10. import ParentMgr
  11. import RelatedObjectMgr
  12. import time
  13. class ClientRepository(ConnectionRepository.ConnectionRepository):
  14. notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepository")
  15. def __init__(self, dcFileName):
  16. ConnectionRepository.ConnectionRepository.__init__(self, base.config)
  17. self.number2cdc={}
  18. self.name2cdc={}
  19. self.doId2do={}
  20. self.doId2cdc={}
  21. self.parseDcFile(dcFileName)
  22. self.cache=CRCache.CRCache()
  23. self.serverDelta = 0
  24. self.bootedIndex = None
  25. self.bootedText = None
  26. # create a parentMgr to handle distributed reparents
  27. # this used to be 'token2nodePath'
  28. self.parentMgr = ParentMgr.ParentMgr()
  29. # The RelatedObjectMgr helps distributed objects find each
  30. # other.
  31. self.relatedObjectMgr = RelatedObjectMgr.RelatedObjectMgr(self)
  32. def setServerDelta(self, delta):
  33. """
  34. Indicates the approximate difference in seconds between the
  35. client's clock and the server's clock, in universal time (not
  36. including timezone shifts). This is mainly useful for
  37. reporting synchronization information to the logs; don't
  38. depend on it for any precise timing requirements.
  39. Also see Notify.setServerDelta(), which also accounts for a
  40. timezone shift.
  41. """
  42. self.serverDelta = delta
  43. def getServerDelta(self):
  44. return self.serverDelta
  45. def getServerTimeOfDay(self):
  46. """
  47. Returns the current time of day (seconds elapsed since the
  48. 1972 epoch) according to the server's clock. This is in GMT,
  49. and hence is irrespective of timezones.
  50. The value is computed based on the client's clock and the
  51. known delta from the server's clock, which is not terribly
  52. precisely measured and may drift slightly after startup, but
  53. it should be accurate plus or minus a couple of seconds.
  54. """
  55. return time.time() + self.serverDelta
  56. def parseDcFile(self, dcFileName):
  57. self.dcFile = DCFile()
  58. readResult = self.dcFile.read(dcFileName)
  59. if not readResult:
  60. self.notify.error("Could not read dcfile: %s" % dcFileName.cStr())
  61. self.hashVal = self.dcFile.getHash()
  62. return self.parseDcClasses(self.dcFile)
  63. def parseDcClasses(self, dcFile):
  64. numClasses = dcFile.getNumClasses()
  65. for i in range(0, numClasses):
  66. # Create a clientDistClass from the dcClass
  67. dcClass = dcFile.getClass(i)
  68. clientDistClass = ClientDistClass.ClientDistClass(dcClass)
  69. # List the cdc in the number and name dictionaries
  70. self.number2cdc[dcClass.getNumber()]=clientDistClass
  71. self.name2cdc[dcClass.getName()]=clientDistClass
  72. def handleGenerateWithRequired(self, di):
  73. # Get the class Id
  74. classId = di.getArg(STUint16);
  75. # Get the DO Id
  76. doId = di.getArg(STUint32)
  77. # Look up the cdc
  78. cdc = self.number2cdc[classId]
  79. # Create a new distributed object, and put it in the dictionary
  80. distObj = self.generateWithRequiredFields(cdc, doId, di)
  81. def handleGenerateWithRequiredOther(self, di):
  82. # Get the class Id
  83. classId = di.getArg(STUint16);
  84. # Get the DO Id
  85. doId = di.getArg(STUint32)
  86. # Look up the cdc
  87. cdc = self.number2cdc[classId]
  88. # Create a new distributed object, and put it in the dictionary
  89. distObj = self.generateWithRequiredOtherFields(cdc, doId, di)
  90. def handleQuietZoneGenerateWithRequired(self, di):
  91. # Special handler for quiet zone generates -- we need to filter
  92. # Get the class Id
  93. classId = di.getArg(STUint16);
  94. # Get the DO Id
  95. doId = di.getArg(STUint32)
  96. # Look up the cdc
  97. cdc = self.number2cdc[classId]
  98. # If the class is a neverDisable class (which implies uberzone) we
  99. # should go ahead and generate it even though we are in the quiet zone
  100. if cdc.constructor.neverDisable:
  101. # Create a new distributed object, and put it in the dictionary
  102. distObj = self.generateWithRequiredFields(cdc, doId, di)
  103. def handleQuietZoneGenerateWithRequiredOther(self, di):
  104. # Special handler for quiet zone generates -- we need to filter
  105. # Get the class Id
  106. classId = di.getArg(STUint16);
  107. # Get the DO Id
  108. doId = di.getArg(STUint32)
  109. # Look up the cdc
  110. cdc = self.number2cdc[classId]
  111. # If the class is a neverDisable class (which implies uberzone) we
  112. # should go ahead and generate it even though we are in the quiet zone
  113. if cdc.constructor.neverDisable:
  114. # Create a new distributed object, and put it in the dictionary
  115. distObj = self.generateWithRequiredOtherFields(cdc, doId, di)
  116. return None
  117. def generateWithRequiredFields(self, cdc, doId, di):
  118. # Is it in our dictionary?
  119. if self.doId2do.has_key(doId):
  120. # If so, just update it.
  121. distObj = self.doId2do[doId]
  122. distObj.generate()
  123. distObj.updateRequiredFields(cdc, di)
  124. # updateRequiredFields calls announceGenerate
  125. # Is it in the cache? If so, pull it out, put it in the dictionaries,
  126. # and update it.
  127. elif self.cache.contains(doId):
  128. # If so, pull it out of the cache...
  129. distObj = self.cache.retrieve(doId)
  130. # put it in both dictionaries...
  131. self.doId2do[doId] = distObj
  132. self.doId2cdc[doId] = cdc
  133. # and update it.
  134. distObj.generate()
  135. distObj.updateRequiredFields(cdc, di)
  136. # updateRequiredFields calls announceGenerate
  137. # If it is not in the dictionary or the cache, then...
  138. else:
  139. # Construct a new one
  140. distObj = cdc.constructor(self)
  141. # Assign it an Id
  142. distObj.doId = doId
  143. # Put the new do in both dictionaries
  144. self.doId2do[doId] = distObj
  145. self.doId2cdc[doId] = cdc
  146. # Update the required fields
  147. distObj.generateInit() # Only called when constructed
  148. distObj.generate()
  149. distObj.updateRequiredFields(cdc, di)
  150. # updateRequiredFields calls announceGenerate
  151. return distObj
  152. def generateWithRequiredOtherFields(self, cdc, doId, di):
  153. # Is it in our dictionary?
  154. if self.doId2do.has_key(doId):
  155. # If so, just update it.
  156. distObj = self.doId2do[doId]
  157. distObj.generate()
  158. distObj.updateRequiredOtherFields(cdc, di)
  159. # updateRequiredOtherFields calls announceGenerate
  160. # Is it in the cache? If so, pull it out, put it in the dictionaries,
  161. # and update it.
  162. elif self.cache.contains(doId):
  163. # If so, pull it out of the cache...
  164. distObj = self.cache.retrieve(doId)
  165. # put it in both dictionaries...
  166. self.doId2do[doId] = distObj
  167. self.doId2cdc[doId] = cdc
  168. # and update it.
  169. distObj.generate()
  170. distObj.updateRequiredOtherFields(cdc, di)
  171. # updateRequiredOtherFields calls announceGenerate
  172. # If it is not in the dictionary or the cache, then...
  173. else:
  174. # Construct a new one
  175. if cdc.constructor == None:
  176. self.notify.error("Could not create an undefined %s object." % (cdc.name))
  177. distObj = cdc.constructor(self)
  178. # Assign it an Id
  179. distObj.doId = doId
  180. # Put the new do in both dictionaries
  181. self.doId2do[doId] = distObj
  182. self.doId2cdc[doId] = cdc
  183. # Update the required fields
  184. distObj.generateInit() # Only called when constructed
  185. distObj.generate()
  186. distObj.updateRequiredOtherFields(cdc, di)
  187. # updateRequiredOtherFields calls announceGenerate
  188. return distObj
  189. def handleDisable(self, di):
  190. # Get the DO Id
  191. doId = di.getArg(STUint32)
  192. # disable it.
  193. self.disableDoId(doId)
  194. return None
  195. def disableDoId(self, doId):
  196. # Make sure the object exists
  197. if self.doId2do.has_key(doId):
  198. # Look up the object
  199. distObj = self.doId2do[doId]
  200. # remove the object from both dictionaries
  201. del(self.doId2do[doId])
  202. del(self.doId2cdc[doId])
  203. assert(len(self.doId2do) == len(self.doId2cdc))
  204. # Only cache the object if it is a "cacheable" type
  205. # object; this way we don't clutter up the caches with
  206. # trivial objects that don't benefit from caching.
  207. if distObj.getCacheable():
  208. self.cache.cache(distObj)
  209. else:
  210. distObj.deleteOrDelay()
  211. else:
  212. ClientRepository.notify.warning("Disable failed. DistObj " +
  213. str(doId) +
  214. " is not in dictionary")
  215. def handleDelete(self, di):
  216. # Get the DO Id
  217. doId = di.getArg(STUint32)
  218. self.deleteObject(doId)
  219. def deleteObject(self, doId):
  220. """deleteObject(self, doId)
  221. Removes the object from the client's view of the world. This
  222. should normally not be called except in the case of error
  223. recovery, since the server will normally be responsible for
  224. deleting and disabling objects as they go out of scope.
  225. After this is called, future updates by server on this object
  226. will be ignored (with a warning message). The object will
  227. become valid again the next time the server sends a generate
  228. message for this doId.
  229. This is not a distributed message and does not delete the
  230. object on the server or on any other client.
  231. """
  232. # If it is in the dictionaries, remove it.
  233. if self.doId2do.has_key(doId):
  234. obj = self.doId2do[doId]
  235. # Remove it from the dictionaries
  236. del(self.doId2do[doId])
  237. del(self.doId2cdc[doId])
  238. # Sanity check the dictionaries
  239. assert(len(self.doId2do) == len(self.doId2cdc))
  240. # Disable, announce, and delete the object itself...
  241. # unless delayDelete is on...
  242. obj.deleteOrDelay()
  243. # If it is in the cache, remove it.
  244. elif self.cache.contains(doId):
  245. self.cache.delete(doId)
  246. # Otherwise, ignore it
  247. else:
  248. ClientRepository.notify.warning(
  249. "Asked to delete non-existent DistObj " + str(doId))
  250. def handleUpdateField(self, di):
  251. # Get the DO Id
  252. doId = di.getArg(STUint32)
  253. #print("Updating " + str(doId))
  254. if self.rsDoReport:
  255. self.rsUpdateObjs[doId] = self.rsUpdateObjs.get(doId, 0) + 1
  256. # Find the DO
  257. do = self.doId2do.get(doId)
  258. cdc = self.doId2cdc.get(doId)
  259. if (do != None and cdc != None):
  260. # Let the cdc finish the job
  261. cdc.updateField(do, di)
  262. else:
  263. ClientRepository.notify.warning(
  264. "Asked to update non-existent DistObj " + str(doId))
  265. def handleGoGetLost(self, di):
  266. # The server told us it's about to drop the connection on us.
  267. # Get ready!
  268. if (di.getRemainingSize() > 0):
  269. self.bootedIndex = di.getUint16()
  270. self.bootedText = di.getString()
  271. ClientRepository.notify.warning(
  272. "Server is booting us out (%d): %s" % (self.bootedIndex, self.bootedText))
  273. else:
  274. self.bootedIndex = None
  275. self.bootedText = None
  276. ClientRepository.notify.warning(
  277. "Server is booting us out with no explanation.")
  278. def handleServerHeartbeat(self, di):
  279. # Got a heartbeat message from the server.
  280. if base.config.GetBool('server-heartbeat-info', 1):
  281. ClientRepository.notify.info("Server heartbeat.")
  282. def handleUnexpectedMsgType(self, msgType, di):
  283. if msgType == CLIENT_GO_GET_LOST:
  284. self.handleGoGetLost(di)
  285. elif msgType == CLIENT_HEARTBEAT:
  286. self.handleServerHeartbeat(di)
  287. else:
  288. currentLoginState = self.loginFSM.getCurrentState()
  289. if currentLoginState:
  290. currentLoginStateName = currentLoginState.getName()
  291. else:
  292. currentLoginStateName = "None"
  293. currentGameState = self.gameFSM.getCurrentState()
  294. if currentGameState:
  295. currentGameStateName = currentGameState.getName()
  296. else:
  297. currentGameStateName = "None"
  298. ClientRepository.notify.warning(
  299. "Ignoring unexpected message type: " +
  300. str(msgType) +
  301. " login state: " +
  302. currentLoginStateName +
  303. " game state: " +
  304. currentGameStateName)
  305. def sendSetShardMsg(self, shardId):
  306. datagram = Datagram()
  307. # Add message type
  308. datagram.addUint16(CLIENT_SET_SHARD)
  309. # Add shard id
  310. datagram.addUint32(shardId)
  311. # send the message
  312. self.send(datagram)
  313. def sendSetZoneMsg(self, zoneId, visibleZoneList=None):
  314. datagram = Datagram()
  315. # Add message type
  316. datagram.addUint16(CLIENT_SET_ZONE)
  317. # Add zone id
  318. datagram.addUint32(zoneId)
  319. # if we have an explicit list of visible zones, add them
  320. if visibleZoneList is not None:
  321. vzl = list(visibleZoneList)
  322. vzl.sort()
  323. assert PythonUtil.uniqueElements(vzl)
  324. for zone in vzl:
  325. datagram.addUint32(zone)
  326. # send the message
  327. self.send(datagram)
  328. def sendUpdate(self, do, fieldName, args, sendToId = None):
  329. # Get the DO id
  330. doId = do.doId
  331. # Get the cdc
  332. cdc = self.doId2cdc.get(doId, None)
  333. if cdc:
  334. # Let the cdc finish the job
  335. cdc.sendUpdate(self, do, fieldName, args, sendToId)
  336. def replaceMethod(self, oldMethod, newFunction):
  337. foundIt = 0
  338. import new
  339. # Iterate over the ClientDistClasses
  340. for cdc in self.number2cdc.values():
  341. # Iterate over the ClientDistUpdates
  342. for cdu in cdc.allCDU:
  343. method = cdu.func
  344. # See if this is a match
  345. if (method and (method.im_func == oldMethod)):
  346. # Create a new unbound method out of this new function
  347. newMethod = new.instancemethod(newFunction,
  348. method.im_self,
  349. method.im_class)
  350. # Set the new method on the cdu
  351. cdu.func = newMethod
  352. foundIt = 1
  353. return foundIt
  354. def getAllOfType(self, type):
  355. # Returns a list of all DistributedObjects in the repository
  356. # of a particular type.
  357. result = []
  358. for obj in self.doId2do.values():
  359. if isinstance(obj, type):
  360. result.append(obj)
  361. return result