ClientRepository.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. """ClientRepository module: contains the ClientRepository class"""
  2. from ClientRepositoryBase import *
  3. class ClientRepository(ClientRepositoryBase):
  4. """
  5. This is the open-source ClientRepository as provided by CMU. It
  6. communicates with the ServerRepository in this same directory.
  7. If you are looking for the VR Studio's implementation of the
  8. client repository, look to OTPClientRepository (elsewhere).
  9. """
  10. notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepository")
  11. def __init__(self, dcFileNames = None):
  12. ClientRepositoryBase.__init__(self, dcFileNames = dcFileNames)
  13. # The DOID allocator. The CMU LAN server may choose to
  14. # send us a block of DOIDs. If it chooses to do so, then we
  15. # may create objects, using those DOIDs.
  16. self.DOIDbase = 0
  17. self.DOIDnext = 0
  18. self.DOIDlast = 0
  19. def isLive(self):
  20. return not (__dev__ or launcher.isTestServer())
  21. def handleSetDOIDrange(self, di):
  22. self.DOIDbase = di.getUint32()
  23. self.DOIDlast = self.DOIDbase + di.getUint32()
  24. self.DOIDnext = self.DOIDbase
  25. def handleRequestGenerates(self, di):
  26. # When new clients join the zone of an object, they need to hear
  27. # about it, so we send out all of our information about objects in
  28. # that particular zone.
  29. assert self.DOIDnext < self.DOIDlast
  30. zone = di.getUint32()
  31. for obj in self.doId2do.values():
  32. if obj.zone == zone:
  33. id = obj.doId
  34. if (self.isLocalId(id)):
  35. self.send(obj.dclass.clientFormatGenerate(obj, id, zone, []))
  36. def createWithRequired(self, className, zoneId = 0, optionalFields=None):
  37. if self.DOIDnext >= self.DOIDlast:
  38. self.notify.error(
  39. "Cannot allocate a distributed object ID: all IDs used up.")
  40. return None
  41. id = self.DOIDnext
  42. self.DOIDnext = self.DOIDnext + 1
  43. dclass = self.dclassesByName[className]
  44. classDef = dclass.getClassDef()
  45. if classDef == None:
  46. self.notify.error("Could not create an undefined %s object." % (
  47. dclass.getName()))
  48. obj = classDef(self)
  49. obj.dclass = dclass
  50. obj.zone = zoneId
  51. obj.doId = id
  52. self.doId2do[id] = obj
  53. obj.generateInit()
  54. obj.generate()
  55. obj.announceGenerate()
  56. datagram = dclass.clientFormatGenerate(obj, id, zoneId, optionalFields)
  57. self.send(datagram)
  58. return obj
  59. def sendDisableMsg(self, doId):
  60. datagram = PyDatagram()
  61. datagram.addUint16(CLIENT_OBJECT_DISABLE)
  62. datagram.addUint32(doId)
  63. self.send(datagram)
  64. def sendDeleteMsg(self, doId):
  65. datagram = PyDatagram()
  66. datagram.addUint16(CLIENT_OBJECT_DELETE)
  67. datagram.addUint32(doId)
  68. self.send(datagram)
  69. def sendRemoveZoneMsg(self, zoneId, visibleZoneList=None):
  70. datagram = PyDatagram()
  71. datagram.addUint16(CLIENT_REMOVE_ZONE)
  72. datagram.addUint32(zoneId)
  73. # if we have an explicit list of visible zones, add them
  74. if visibleZoneList is not None:
  75. vzl = list(visibleZoneList)
  76. vzl.sort()
  77. assert PythonUtil.uniqueElements(vzl)
  78. for zone in vzl:
  79. datagram.addUint32(zone)
  80. # send the message
  81. self.send(datagram)
  82. def sendUpdateZone(self, obj, zoneId):
  83. id = obj.doId
  84. assert self.isLocalId(id)
  85. self.sendDeleteMsg(id, 1)
  86. obj.zone = zoneId
  87. self.send(obj.dclass.clientFormatGenerate(obj, id, zoneId, []))
  88. def sendSetZoneMsg(self, zoneId, visibleZoneList=None):
  89. datagram = PyDatagram()
  90. # Add message type
  91. datagram.addUint16(CLIENT_SET_ZONE_CMU)
  92. # Add zone id
  93. datagram.addUint32(zoneId)
  94. # if we have an explicit list of visible zones, add them
  95. if visibleZoneList is not None:
  96. vzl = list(visibleZoneList)
  97. vzl.sort()
  98. assert PythonUtil.uniqueElements(vzl)
  99. for zone in vzl:
  100. datagram.addUint32(zone)
  101. # send the message
  102. self.send(datagram)
  103. def isLocalId(self, id):
  104. return ((id >= self.DOIDbase) and (id < self.DOIDlast))
  105. def haveCreateAuthority(self):
  106. return (self.DOIDlast > self.DOIDnext)
  107. def handleDatagram(self, di):
  108. if self.notify.getDebug():
  109. print "ClientRepository received datagram:"
  110. di.getDatagram().dumpHex(ostream)
  111. msgType = self.getMsgType()
  112. # These are the sort of messages we may expect from the public
  113. # Panda server.
  114. if msgType == CLIENT_SET_DOID_RANGE:
  115. self.handleSetDOIDrange(di)
  116. elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_RESP:
  117. self.handleGenerateWithRequired(di)
  118. elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER_RESP:
  119. self.handleGenerateWithRequiredOther(di)
  120. elif msgType == CLIENT_OBJECT_UPDATE_FIELD_RESP:
  121. self.handleUpdateField(di)
  122. elif msgType == CLIENT_OBJECT_DELETE_RESP:
  123. self.handleDelete(di)
  124. elif msgType == CLIENT_OBJECT_DISABLE_RESP:
  125. self.handleDisable(di)
  126. elif msgType == CLIENT_REQUEST_GENERATES:
  127. self.handleRequestGenerates(di)
  128. else:
  129. self.handleMessageType(msgType, di)
  130. # If we're processing a lot of datagrams within one frame, we
  131. # may forget to send heartbeats. Keep them coming!
  132. self.considerHeartbeat()
  133. def handleGenerateWithRequired(self, di):
  134. # Get the class Id
  135. classId = di.getUint16()
  136. # Get the DO Id
  137. doId = di.getUint32()
  138. # Look up the dclass
  139. dclass = self.dclassesByNumber[classId]
  140. dclass.startGenerate()
  141. # Create a new distributed object, and put it in the dictionary
  142. distObj = self.generateWithRequiredFields(dclass, doId, di)
  143. dclass.stopGenerate()
  144. def generateWithRequiredFields(self, dclass, doId, di):
  145. if self.doId2do.has_key(doId):
  146. # ...it is in our dictionary.
  147. # Just update it.
  148. distObj = self.doId2do[doId]
  149. assert distObj.dclass == dclass
  150. distObj.generate()
  151. distObj.updateRequiredFields(dclass, di)
  152. # updateRequiredFields calls announceGenerate
  153. elif self.cache.contains(doId):
  154. # ...it is in the cache.
  155. # Pull it out of the cache:
  156. distObj = self.cache.retrieve(doId)
  157. assert distObj.dclass == dclass
  158. # put it in the dictionary:
  159. self.doId2do[doId] = distObj
  160. # and update it.
  161. distObj.generate()
  162. distObj.updateRequiredFields(dclass, di)
  163. # updateRequiredFields calls announceGenerate
  164. else:
  165. # ...it is not in the dictionary or the cache.
  166. # Construct a new one
  167. classDef = dclass.getClassDef()
  168. if classDef == None:
  169. self.notify.error("Could not create an undefined %s object." % (
  170. dclass.getName()))
  171. distObj = classDef(self)
  172. distObj.dclass = dclass
  173. # Assign it an Id
  174. distObj.doId = doId
  175. # Put the new do in the dictionary
  176. self.doId2do[doId] = distObj
  177. # Update the required fields
  178. distObj.generateInit() # Only called when constructed
  179. distObj.generate()
  180. distObj.updateRequiredFields(dclass, di)
  181. # updateRequiredFields calls announceGenerate
  182. return distObj