diff --git a/direct/src/distributed/AstronClientRepository.py b/direct/src/distributed/AstronClientRepository.py new file mode 100644 index 0000000000..a665385201 --- /dev/null +++ b/direct/src/distributed/AstronClientRepository.py @@ -0,0 +1,270 @@ +"""AstronClientRepository module: contains the AstronClientRepository class""" + +from direct.directnotify import DirectNotifyGlobal +from ClientRepositoryBase import ClientRepositoryBase +from MsgTypes import * +from direct.distributed.PyDatagram import PyDatagram +from pandac.PandaModules import STUint16, STUint32 + +class AstronClientRepository(ClientRepositoryBase): + """ + The Astron implementation of a clients repository for + communication with an Astron ClientAgent. + + This repo will emit events for: + * CLIENT_HELLO_RESP + * CLIENT_EJECT ( error_code, reason ) + * CLIENT_OBJECT_LEAVING ( do_id ) + * CLIENT_ADD_INTEREST ( context, interest_id, parent_id, zone_id ) + * CLIENT_ADD_INTEREST_MULTIPLE ( icontext, interest_id, parent_id, [zone_ids] ) + * CLIENT_REMOVE_INTEREST ( context, interest_id ) + * CLIENT_DONE_INTEREST_RESP ( context, interest_id ) + * LOST_CONNECTION () + """ + + notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepository") + + # This is required by DoCollectionManager, even though it's not + # used by this implementation. + GameGlobalsId = 0 + + def __init__(self, *args, **kwargs): + ClientRepositoryBase.__init__(self, *args, **kwargs) + base.finalExitCallbacks.append(self.shutdown) + self.message_handlers = {CLIENT_HELLO_RESP: self.handleHelloResp, + CLIENT_EJECT: self.handleEject, + CLIENT_ENTER_OBJECT_REQUIRED: self.handleEnterObjectRequired, + CLIENT_ENTER_OBJECT_REQUIRED_OWNER: self.handleEnterObjectRequiredOwner, + CLIENT_OBJECT_SET_FIELD: self.handleUpdateField, + CLIENT_OBJECT_SET_FIELDS: self.handleUpdateFields, + CLIENT_OBJECT_LEAVING: self.handleObjectLeaving, + CLIENT_OBJECT_LOCATION: self.handleObjectLocation, + CLIENT_ADD_INTEREST: self.handleAddInterest, + CLIENT_ADD_INTEREST_MULTIPLE: self.handleAddInterestMultiple, + CLIENT_REMOVE_INTEREST: self.handleRemoveInterest, + CLIENT_DONE_INTEREST_RESP: self.handleInterestDoneMessage, + } + + # + # Message Handling + # + + def handleDatagram(self, di): + msgType = self.getMsgType() + # self.handleMessageType(msgType, di) + # + #def handleMessageType(self, msgType, di): + if msgType in self.message_handlers: + self.message_handlers[msgType](di) + else: + self.notify.error("Got unknown message type %d!" % (msgType,)) + + self.considerHeartbeat() + + def handleHelloResp(self, di): + messenger.send("CLIENT_HELLO_RESP", []) + + def handleEject(self, di): + error_code = di.get_uint16() + reason = di.get_string() + messenger.send("CLIENT_EJECT", [error_code, reason]) + + def handleEnterObjectRequired(self, di): + do_id = di.getArg(STUint32) + parent_id = di.getArg(STUint32) + zone_id = di.getArg(STUint32) + dclass_id = di.getArg(STUint16) + dclass = self.dclassesByNumber[dclass_id] + self.generateWithRequiredFields(dclass, do_id, di, parent_id, zone_id) + + def handleEnterObjectRequiredOwner(self, di): + avatar_doId = di.getArg(STUint32) + parentId = di.getArg(STUint32) + zoneId = di.getArg(STUint32) + dclass_id = di.getArg(STUint16) + dclass = self.dclassesByNumber[dclass_id] + self.generateWithRequiredFieldsOwner(dclass, avatar_doId, di) + + def generateWithRequiredFieldsOwner(self, dclass, doId, di): + if doId in self.doId2ownerView: + # ...it is in our dictionary. + # Just update it. + self.notify.error('duplicate owner generate for %s (%s)' % ( + doId, dclass.getName())) + distObj = self.doId2ownerView[doId] + assert distObj.dclass == dclass + distObj.generate() + distObj.updateRequiredFields(dclass, di) + # updateRequiredFields calls announceGenerate + elif self.cacheOwner.contains(doId): + # ...it is in the cache. + # Pull it out of the cache: + distObj = self.cacheOwner.retrieve(doId) + assert distObj.dclass == dclass + # put it in the dictionary: + self.doId2ownerView[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.getOwnerClassDef() + if classDef == None: + self.notify.error("Could not create an undefined %s object. Have you created an owner view?" % (dclass.getName())) + distObj = classDef(self) + distObj.dclass = dclass + # Assign it an Id + distObj.doId = doId + # Put the new do in the dictionary + self.doId2ownerView[doId] = distObj + # Update the required fields + distObj.generateInit() # Only called when constructed + distObj.generate() + distObj.updateRequiredFields(dclass, di) + # updateRequiredFields calls announceGenerate + return distObj + + def handleUpdateFields(self, di): + # Can't test this without the server actually sending it. + self.notify.error("CLIENT_OBJECT_SET_FIELDS not implemented!") + # # Here's some tentative code and notes: + # do_id = di.getUint32() + # field_count = di.getUint16() + # for i in range(0, field_count): + # field_id = di.getUint16() + # field = self.get_dc_file().get_field_by_index(field_id) + # # print(type(field)) + # # print(field) + # # FIXME: Get field type, unpack value, create and send message. + # # value = di.get?() + # # Assemble new message + + def handleObjectLeaving(self, di): + do_id = di.get_uint32() + dist_obj = self.doId2do.get(do_id) + dist_obj.delete() + self.deleteObject(do_id) + messenger.send("CLIENT_OBJECT_LEAVING", [do_id]) + + def handleAddInterest(self, di): + context = di.get_uint32() + interest_id = di.get_uint16() + parent_id = di.get_uint32() + zone_id = di.get_uint32() + messenger.send("CLIENT_ADD_INTEREST", [context, interest_id, parent_id, zone_id]) + + def handleAddInterestMultiple(self, di): + context = di.get_uint32() + interest_id = di.get_uint16() + parent_id = di.get_uint32() + zone_ids = [di.get_uint32() for i in range(0, di.get_uint16())] + messenger.send("CLIENT_ADD_INTEREST_MULTIPLE", [context, interest_id, parent_id, zone_ids]) + + def handleRemoveInterest(self, di): + context = di.get_uint32() + interest_id = di.get_uint16() + messenger.send("CLIENT_REMOVE_INTEREST", [context, interest_id]) + + def deleteObject(self, doId): + """ + implementation copied from ClientRepository.py + + Removes the object from the client's view of the world. This + should normally not be called directly except in the case of + error recovery, since the server will normally be responsible + for deleting and disabling objects as they go out of scope. + + After this is called, future updates by server on this object + will be ignored (with a warning message). The object will + become valid again the next time the server sends a generate + message for this doId. + + This is not a distributed message and does not delete the + object on the server or on any other client. + """ + if doId in self.doId2do: + # If it is in the dictionary, remove it. + obj = self.doId2do[doId] + # Remove it from the dictionary + del self.doId2do[doId] + # Disable, announce, and delete the object itself... + # unless delayDelete is on... + obj.deleteOrDelay() + if self.isLocalId(doId): + self.freeDoId(doId) + elif self.cache.contains(doId): + # If it is in the cache, remove it. + self.cache.delete(doId) + if self.isLocalId(doId): + self.freeDoId(doId) + else: + # Otherwise, ignore it + self.notify.warning( + "Asked to delete non-existent DistObj " + str(doId)) + + # + # Sending messages + # + + def sendUpdate(self, distObj, fieldName, args): + """ Sends a normal update for a single field. """ + dg = distObj.dclass.clientFormatUpdate( + fieldName, distObj.doId, args) + self.send(dg) + + # FIXME: The version string should default to a .prc variable. + def sendHello(self, version_string): + dg = PyDatagram() + dg.add_uint16(CLIENT_HELLO) + dg.add_uint32(self.get_dc_file().get_hash()) + dg.add_string(version_string) + self.send(dg) + + def sendHeartbeat(self): + datagram = PyDatagram() + datagram.addUint16(CLIENT_HEARTBEAT) + self.send(datagram) + + def sendAddInterest(self, context, interest_id, parent_id, zone_id): + dg = PyDatagram() + dg.add_uint16(CLIENT_ADD_INTEREST) + dg.add_uint32(context) + dg.add_uint16(interest_id) + dg.add_uint32(parent_id) + dg.add_uint32(zone_id) + self.send(dg) + + def sendAddInterestMultiple(self, context, interest_id, parent_id, zone_ids): + dg = PyDatagram() + dg.add_uint16(CLIENT_ADD_INTEREST_MULTIPLE) + dg.add_uint32(context) + dg.add_uint16(interest_id) + dg.add_uint32(parent_id) + dg.add_uint16(len(zone_ids)) + for zone_id in zone_ids: + dg.add_uint32(zone_id) + self.send(dg) + + def sendRemoveInterest(self, context, interest_id): + dg = PyDatagram() + dg.add_uint16(CLIENT_REMOVE_INTEREST) + dg.add_uint32(context) + dg.add_uint16(interest_id) + self.send(dg) + + # + # Other stuff + # + + def lostConnection(self): + messenger.send("LOST_CONNECTION") + + def disconnect(self): + """ + This implicitly deletes all objects from the repository. + """ + for do_id in self.doId2do.keys(): + self.deleteObject(do_id) + ClientRepositoryBase.disconnect(self) diff --git a/direct/src/distributed/DistributedNode.py b/direct/src/distributed/DistributedNode.py index 353b001cc2..af4aa10e82 100644 --- a/direct/src/distributed/DistributedNode.py +++ b/direct/src/distributed/DistributedNode.py @@ -17,6 +17,7 @@ class DistributedNode(DistributedObject.DistributedObject, NodePath): self.DistributedNode_initialized = 1 self.gotStringParentToken = 0 DistributedObject.DistributedObject.__init__(self, cr) + NodePath.__init__(self, "DistributedNode") # initialize gridParent self.gridParent = None diff --git a/direct/src/distributed/DistributedObjectBase.py b/direct/src/distributed/DistributedObjectBase.py index ba1d8a21bc..d378141e77 100755 --- a/direct/src/distributed/DistributedObjectBase.py +++ b/direct/src/distributed/DistributedObjectBase.py @@ -95,3 +95,11 @@ class DistributedObjectBase(DirectObject): def hasParentingRules(self): return self.dclass.getFieldByName('setParentingRules') != None + + def delete(self): + """ + Overwrite this to handle cleanup right before this object + gets deleted. + """ + pass + diff --git a/direct/src/distributed/MsgTypes.py b/direct/src/distributed/MsgTypes.py index f839873bcc..b689214d10 100644 --- a/direct/src/distributed/MsgTypes.py +++ b/direct/src/distributed/MsgTypes.py @@ -15,10 +15,12 @@ MsgName2Id = { 'CLIENT_HEARTBEAT': 5, 'CLIENT_OBJECT_SET_FIELD': 120, + 'CLIENT_OBJECT_SET_FIELDS': 121, 'CLIENT_OBJECT_LEAVING': 132, 'CLIENT_OBJECT_LEAVING_OWNER': 161, 'CLIENT_ENTER_OBJECT_REQUIRED': 142, 'CLIENT_ENTER_OBJECT_REQUIRED_OTHER': 143, + 'CLIENT_ENTER_OBJECT_REQUIRED_OWNER': 172, 'CLIENT_ENTER_OBJECT_REQUIRED_OTHER_OWNER': 173, 'CLIENT_DONE_INTEREST_RESP': 204,