diff --git a/direct/src/distributed/CMUClientRepository.py b/direct/src/distributed/CMUClientRepository.py new file mode 100644 index 0000000000..79737a242d --- /dev/null +++ b/direct/src/distributed/CMUClientRepository.py @@ -0,0 +1,207 @@ +"""ClientRepository module: contains the ClientRepository class""" + +from ClientRepositoryBase import * + +class ClientRepository(ClientRepositoryBase): + """ + This is the open-source ClientRepository as provided by CMU. It + communicates with the ServerRepository in this same directory. + + If you are looking for the VR Studio's implementation of the + client repository, look to OTPClientRepository (elsewhere). + """ + notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepository") + + def __init__(self, dcFileNames = None): + ClientRepositoryBase.__init__(self, dcFileNames = dcFileNames) + + # The DOID allocator. The CMU LAN server may choose to + # send us a block of DOIDs. If it chooses to do so, then we + # may create objects, using those DOIDs. + self.DOIDbase = 0 + self.DOIDnext = 0 + self.DOIDlast = 0 + + def handleSetDOIDrange(self, di): + self.DOIDbase = di.getUint32() + self.DOIDlast = self.DOIDbase + di.getUint32() + self.DOIDnext = self.DOIDbase + + def handleRequestGenerates(self, di): + # When new clients join the zone of an object, they need to hear + # about it, so we send out all of our information about objects in + # that particular zone. + + assert self.DOIDnext < self.DOIDlast + zone = di.getUint32() + for obj in self.doId2do.values(): + if obj.zone == zone: + id = obj.doId + if (self.isLocalId(id)): + self.send(obj.dclass.clientFormatGenerate(obj, id, zone, [])) + + def createWithRequired(self, className, zoneId = 0, optionalFields=None): + if self.DOIDnext >= self.DOIDlast: + self.notify.error( + "Cannot allocate a distributed object ID: all IDs used up.") + return None + id = self.DOIDnext + self.DOIDnext = self.DOIDnext + 1 + dclass = self.dclassesByName[className] + classDef = dclass.getClassDef() + if classDef == None: + self.notify.error("Could not create an undefined %s object." % ( + dclass.getName())) + obj = classDef(self) + obj.dclass = dclass + obj.zone = zoneId + obj.doId = id + self.doId2do[id] = obj + obj.generateInit() + obj.generate() + obj.announceGenerate() + datagram = dclass.clientFormatGenerate(obj, id, zoneId, optionalFields) + self.send(datagram) + return obj + + def sendDisableMsg(self, doId): + datagram = PyDatagram() + datagram.addUint16(CLIENT_OBJECT_DISABLE) + datagram.addUint32(doId) + self.send(datagram) + + def sendDeleteMsg(self, doId): + datagram = PyDatagram() + datagram.addUint16(CLIENT_OBJECT_DELETE) + datagram.addUint32(doId) + self.send(datagram) + + def sendRemoveZoneMsg(self, zoneId, visibleZoneList=None): + datagram = PyDatagram() + datagram.addUint16(CLIENT_REMOVE_ZONE) + datagram.addUint32(zoneId) + + # if we have an explicit list of visible zones, add them + if visibleZoneList is not None: + vzl = list(visibleZoneList) + vzl.sort() + assert PythonUtil.uniqueElements(vzl) + for zone in vzl: + datagram.addUint32(zone) + + # send the message + self.send(datagram) + + def sendUpdateZone(self, obj, zoneId): + id = obj.doId + assert self.isLocalId(id) + self.sendDeleteMsg(id, 1) + obj.zone = zoneId + self.send(obj.dclass.clientFormatGenerate(obj, id, zoneId, [])) + + def sendSetZoneMsg(self, zoneId, visibleZoneList=None): + datagram = PyDatagram() + # Add message type + datagram.addUint16(CLIENT_SET_ZONE_CMU) + # Add zone id + datagram.addUint32(zoneId) + + # if we have an explicit list of visible zones, add them + if visibleZoneList is not None: + vzl = list(visibleZoneList) + vzl.sort() + assert PythonUtil.uniqueElements(vzl) + for zone in vzl: + datagram.addUint32(zone) + + # send the message + self.send(datagram) + + def isLocalId(self, id): + return ((id >= self.DOIDbase) and (id < self.DOIDlast)) + + def haveCreateAuthority(self): + return (self.DOIDlast > self.DOIDnext) + + def handleDatagram(self, di): + if self.notify.getDebug(): + print "ClientRepository received datagram:" + di.getDatagram().dumpHex(ostream) + + msgType = self.getMsgType() + + # These are the sort of messages we may expect from the public + # Panda server. + + if msgType == CLIENT_SET_DOID_RANGE: + self.handleSetDOIDrange(di) + elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_RESP: + self.handleGenerateWithRequired(di) + elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER_RESP: + self.handleGenerateWithRequiredOther(di) + elif msgType == CLIENT_OBJECT_UPDATE_FIELD_RESP: + self.handleUpdateField(di) + elif msgType == CLIENT_OBJECT_DELETE_RESP: + self.handleDelete(di) + elif msgType == CLIENT_OBJECT_DISABLE_RESP: + self.handleDisable(di) + elif msgType == CLIENT_REQUEST_GENERATES: + self.handleRequestGenerates(di) + else: + self.handleMessageType(msgType, di) + + # If we're processing a lot of datagrams within one frame, we + # may forget to send heartbeats. Keep them coming! + self.considerHeartbeat() + + def handleGenerateWithRequired(self, di): + # Get the class Id + classId = di.getUint16() + # Get the DO Id + doId = di.getUint32() + # Look up the dclass + dclass = self.dclassesByNumber[classId] + dclass.startGenerate() + # Create a new distributed object, and put it in the dictionary + distObj = self.generateWithRequiredFields(dclass, doId, di) + dclass.stopGenerate() + + def generateWithRequiredFields(self, dclass, doId, di): + if self.doId2do.has_key(doId): + # ...it is in our dictionary. + # Just update it. + distObj = self.doId2do[doId] + assert distObj.dclass == dclass + distObj.generate() + distObj.updateRequiredFields(dclass, di) + # updateRequiredFields calls announceGenerate + elif self.cache.contains(doId): + # ...it is in the cache. + # Pull it out of the cache: + distObj = self.cache.retrieve(doId) + assert distObj.dclass == dclass + # put it in the dictionary: + self.doId2do[doId] = distObj + # and update it. + distObj.generate() + distObj.updateRequiredFields(dclass, di) + # updateRequiredFields calls announceGenerate + else: + # ...it is not in the dictionary or the cache. + # Construct a new one + classDef = dclass.getClassDef() + if classDef == None: + self.notify.error("Could not create an undefined %s object." % ( + dclass.getName())) + distObj = classDef(self) + distObj.dclass = dclass + # Assign it an Id + distObj.doId = doId + # Put the new do in the dictionary + self.doId2do[doId] = distObj + # Update the required fields + distObj.generateInit() # Only called when constructed + distObj.generate() + distObj.updateRequiredFields(dclass, di) + # updateRequiredFields calls announceGenerate + return distObj