Merge pull request #2 from TheCheapestPixels/master
AstronClientRepository
This commit is contained in:
commit
0a352d3d51
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue