separate out ClientRepositoryBase, ClientRepository, and OTPClientRepository

This commit is contained in:
David Rose 2006-02-28 22:21:32 +00:00
parent 92291308eb
commit e89d02c9c5
5 changed files with 707 additions and 707 deletions

View File

@ -1,628 +1,82 @@
"""ClientRepository module: contains the ClientRepository class""" """ClientRepository module: contains the ClientRepository class"""
from pandac.PandaModules import * from ClientRepositoryBase import *
from MsgTypes import *
from direct.task import Task
from direct.directnotify import DirectNotifyGlobal
import CRCache
from direct.distributed.ConnectionRepository import ConnectionRepository
from direct.showbase import PythonUtil
import ParentMgr
import RelatedObjectMgr
import time
from ClockDelta import *
from PyDatagram import PyDatagram
from PyDatagramIterator import PyDatagramIterator
class ClientRepository(ConnectionRepository): class ClientRepository(ClientRepositoryBase):
""" """
This maintains a client-side connection with a Panda server. This is the open-source ClientRepository as provided by CMU. It
It currently supports several different versions of the server: communicates with the ServerRepository in this same directory.
within the VR Studio, we are currently in transition from the
Toontown server to the OTP server; people outside the VR studio If you are looking for the VR Studio's implementation of the
will use the Panda LAN server provided by CMU. client repository, look to OTPClientRepository (elsewhere).
""" """
notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepository") notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepository")
def __init__(self, dcFileNames = None): def __init__(self, dcFileNames = None):
self.dcSuffix="" ClientRepositoryBase.__init__(self, dcFileNames = dcFileNames)
ConnectionRepository.__init__(self, base.config, hasOwnerView=True)
self.context=100000
self.setClientDatagram(1)
self.recorder = base.recorder
self.readDCFile(dcFileNames)
self.cache=CRCache.CRCache()
self.cacheOwner=CRCache.CRCache()
self.serverDelta = 0
self.bootedIndex = None
self.bootedText = None
if 0: # unused:
self.worldScale = render.attachNewNode("worldScale") # for grid zones.
self.worldScale.setScale(base.config.GetFloat('world-scale', 100))
self.priorWorldPos = None
# create a parentMgr to handle distributed reparents
# this used to be 'token2nodePath'
self.parentMgr = ParentMgr.ParentMgr()
# The RelatedObjectMgr helps distributed objects find each
# other.
self.relatedObjectMgr = RelatedObjectMgr.RelatedObjectMgr(self)
# Keep track of how recently we last sent a heartbeat message.
# We want to keep these coming at heartbeatInterval seconds.
self.heartbeatInterval = base.config.GetDouble('heartbeat-interval', 10)
self.heartbeatStarted = 0
self.lastHeartbeat = 0
# By default, the ClientRepository is set up to respond to
# datagrams from the CMU Panda LAN server. You can
# reassign this member to change the response behavior
# according to game context.
self.handler = self.publicServerDatagramHandler
# The DOID allocator. The CMU LAN server may choose to # The DOID allocator. The CMU LAN server may choose to
# send us a block of DOIDs. If it chooses to do so, then we # send us a block of DOIDs. If it chooses to do so, then we
# may create objects, using those DOIDs. These structures are # may create objects, using those DOIDs.
# only used in conjunction with the CMU LAN server.
self.DOIDbase = 0 self.DOIDbase = 0
self.DOIDnext = 0 self.DOIDnext = 0
self.DOIDlast = 0 self.DOIDlast = 0
## def queryObjectAll(self, doID, context=0):
## """
## Get a one-time snapshot look at the object.
## """
## assert self.notify.debugStateCall(self)
## # Create a message
## datagram = PyDatagram()
## datagram.addServerHeader(
## doID, localAvatar.getDoId(), 2020)
## # A context that can be used to index the response if needed
## datagram.addUint32(context)
## self.send(datagram)
## # Make sure the message gets there.
## self.flush()
# Define uniqueName
def uniqueName(self, desc):
return desc
def getTables(self, ownerView):
if ownerView:
return self.doId2ownerView, self.cacheOwner
else:
return self.doId2do, self.cache
def sendDisconnect(self):
if self.isConnected():
# Tell the game server that we're going:
datagram = PyDatagram()
# Add message type
datagram.addUint16(CLIENT_DISCONNECT)
# Send the message
self.send(datagram)
self.notify.info("Sent disconnect message to server")
self.disconnect()
self.stopHeartbeat()
if 0: # Code that became obsolete before it was used:
def setWorldOffset(self, xOffset=0, yOffset=0):
self.worldXOffset=xOffset
self.worldYOffset=yOffset
def getWorldPos(self, nodePath):
pos = nodePath.getPos(self.worldScale)
return (int(round(pos.getX())), int(round(pos.getY())))
def sendWorldPos(self, x, y):
# The server will need to know the world
# offset of our current render node path
# and adjust the x, y accordingly. At one
# point I considered adding the world offset
# here, but that would just use extra bits.
onScreenDebug.add("worldPos", "%-4d, %-4d"%(x, y))
return #*#
datagram = PyDatagram()
# Add message type
datagram.addUint16(CLIENT_SET_WORLD_POS)
# Add x
datagram.addInt16(x)
# Add y
datagram.addSint16(y)
# send the message
self.send(datagram)
def checkWorldPos(self, nodePath):
worldPos = self.getWorldPos(nodePath)
if self.priorWorldPos != worldPos:
self.priorWorldPos = worldPos
self.sendWorldPos(worldPos[0], worldPos[1])
def allocateContext(self):
self.context+=1
return self.context
def setServerDelta(self, delta):
"""
Indicates the approximate difference in seconds between the
client's clock and the server's clock, in universal time (not
including timezone shifts). This is mainly useful for
reporting synchronization information to the logs; don't
depend on it for any precise timing requirements.
Also see Notify.setServerDelta(), which also accounts for a
timezone shift.
"""
self.serverDelta = delta
def getServerDelta(self):
return self.serverDelta
def getServerTimeOfDay(self):
"""
Returns the current time of day (seconds elapsed since the
1972 epoch) according to the server's clock. This is in GMT,
and hence is irrespective of timezones.
The value is computed based on the client's clock and the
known delta from the server's clock, which is not terribly
precisely measured and may drift slightly after startup, but
it should be accurate plus or minus a couple of seconds.
"""
return time.time() + self.serverDelta
def handleGenerateWithRequired(self, di):
parentId = di.getUint32()
zoneId = di.getUint32()
assert parentId == self.GameGlobalsId or parentId in self.doId2do
# 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, parentId, zoneId)
dclass.stopGenerate()
def handleGenerateWithRequiredOther(self, di):
parentId = di.getUint32()
zoneId = di.getUint32()
assert parentId == self.GameGlobalsId or parentId in self.doId2do
# 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.generateWithRequiredOtherFields(dclass, doId, di, parentId, zoneId)
dclass.stopGenerate()
def handleGenerateWithRequiredOtherOwner(self, di):
# Get the class Id
classId = di.getUint16()
# Get the DO Id
doId = di.getUint32()
# parentId and zoneId are not relevant here
parentId = di.getUint32()
zoneId = 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.generateWithRequiredOtherFieldsOwner(dclass, doId, di)
dclass.stopGenerate()
def handleQuietZoneGenerateWithRequired(self, di):
# Special handler for quiet zone generates -- we need to filter
parentId = di.getUint32()
zoneId = di.getUint32()
assert parentId in self.doId2do
# Get the class Id
classId = di.getUint16()
# Get the DO Id
doId = di.getUint32()
# Look up the dclass
dclass = self.dclassesByNumber[classId]
dclass.startGenerate()
distObj = self.generateWithRequiredFields(dclass, doId, di, parentId, zoneId)
dclass.stopGenerate()
def handleQuietZoneGenerateWithRequiredOther(self, di):
# Special handler for quiet zone generates -- we need to filter
parentId = di.getUint32()
zoneId = di.getUint32()
assert parentId in self.doId2do
# Get the class Id
classId = di.getUint16()
# Get the DO Id
doId = di.getUint32()
# Look up the dclass
dclass = self.dclassesByNumber[classId]
dclass.startGenerate()
distObj = self.generateWithRequiredOtherFields(dclass, doId, di, parentId, zoneId)
dclass.stopGenerate()
def generateWithRequiredFields(self, dclass, doId, di, parentId, zoneId):
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.setLocation(parentId, zoneId)
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.setLocation(parentId, zoneId)
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.setLocation(parentId, zoneId)
distObj.updateRequiredFields(dclass, di)
# updateRequiredFields calls announceGenerate
print "New DO:%s, dclass:%s"%(doId, dclass.getName())
return distObj
## def generateGlobalObject(self, doId, dcname):
## # Look up the dclass
## dclass = self.dclassesByName[dcname]
## # Create a new distributed object, and put it in the dictionary
## #distObj = self.generateWithRequiredFields(dclass, doId, di)
## # 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()
## # TODO: ROGER: where should we get parentId and zoneId?
## parentId = None
## zoneId = None
## distObj.setLocation(parentId, zoneId)
## # updateRequiredFields calls announceGenerate
## return distObj
def generateWithRequiredOtherFields(self, dclass, doId, di,
parentId = None, zoneId = None):
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.setLocation(parentId, zoneId)
distObj.updateRequiredOtherFields(dclass, di)
# updateRequiredOtherFields 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.setLocation(parentId, zoneId)
distObj.updateRequiredOtherFields(dclass, di)
# updateRequiredOtherFields 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.setLocation(parentId, zoneId)
distObj.updateRequiredOtherFields(dclass, di)
# updateRequiredOtherFields calls announceGenerate
return distObj
def generateWithRequiredOtherFieldsOwner(self, dclass, doId, di):
if self.doId2ownerView.has_key(doId):
# ...it is in our dictionary.
# Just update it.
distObj = self.doId2ownerView[doId]
assert distObj.dclass == dclass
distObj.generate()
distObj.updateRequiredOtherFields(dclass, di)
# updateRequiredOtherFields 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.updateRequiredOtherFields(dclass, di)
# updateRequiredOtherFields 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.updateRequiredOtherFields(dclass, di)
# updateRequiredOtherFields calls announceGenerate
return distObj
def handleDisable(self, di, ownerView=False):
# Get the DO Id
doId = di.getUint32()
# disable it.
self.disableDoId(doId, ownerView)
def disableDoId(self, doId, ownerView=False):
table, cache = self.getTables(ownerView)
# Make sure the object exists
if table.has_key(doId):
# Look up the object
distObj = table[doId]
# remove the object from the dictionary
del table[doId]
# Only cache the object if it is a "cacheable" type
# object; this way we don't clutter up the caches with
# trivial objects that don't benefit from caching.
if distObj.getCacheable():
cache.cache(distObj)
else:
distObj.deleteOrDelay()
else:
self._logFailedDisable(doId, ownerView)
def _logFailedDisable(self, doId, ownerView):
ClientRepository.notify.warning(
"Disable failed. DistObj "
+ str(doId) +
" is not in dictionary, ownerView=%s" % ownerView)
def handleDelete(self, di):
# overridden by ToontownClientRepository
assert 0
def handleUpdateField(self, di):
"""
This method is called when a CLIENT_OBJECT_UPDATE_FIELD
message is received; it decodes the update, unpacks the
arguments, and calls the corresponding method on the indicated
DistributedObject.
In fact, this method is exactly duplicated by the C++ method
cConnectionRepository::handle_update_field(), which was
written to optimize the message loop by handling all of the
CLIENT_OBJECT_UPDATE_FIELD messages in C++. That means that
nowadays, this Python method will probably never be called,
since UPDATE_FIELD messages will not even be passed to the
Python message handlers. But this method remains for
documentation purposes, and also as a "just in case" handler
in case we ever do come across a situation in the future in
which python might handle the UPDATE_FIELD message.
"""
# Get the DO Id
doId = di.getUint32()
#print("Updating " + str(doId))
# Find the DO
do = self.doId2do.get(doId)
if do is not None:
# Let the dclass finish the job
do.dclass.receiveUpdate(do, di)
else:
ClientRepository.notify.warning(
"Asked to update non-existent DistObj " + str(doId))
def handleGoGetLost(self, di):
# The server told us it's about to drop the connection on us.
# Get ready!
if (di.getRemainingSize() > 0):
self.bootedIndex = di.getUint16()
self.bootedText = di.getString()
ClientRepository.notify.warning(
"Server is booting us out (%d): %s" % (self.bootedIndex, self.bootedText))
else:
self.bootedIndex = None
self.bootedText = None
ClientRepository.notify.warning(
"Server is booting us out with no explanation.")
def handleServerHeartbeat(self, di):
# Got a heartbeat message from the server.
if base.config.GetBool('server-heartbeat-info', 1):
ClientRepository.notify.info("Server heartbeat.")
def handleSystemMessage(self, di):
# Got a system message from the server.
message = di.getString()
self.notify.info('Message from server: %s' % (message))
return message
def handleSetDOIDrange(self, di): def handleSetDOIDrange(self, di):
# This method is only used in conjunction with the CMU LAN
# server.
self.DOIDbase = di.getUint32() self.DOIDbase = di.getUint32()
self.DOIDlast = self.DOIDbase + di.getUint32() self.DOIDlast = self.DOIDbase + di.getUint32()
self.DOIDnext = self.DOIDbase self.DOIDnext = self.DOIDbase
## TODO: This should probably be move to a derived class for CMU def handleRequestGenerates(self, di):
## def handleRequestGenerates(self, di): # When new clients join the zone of an object, they need to hear
## # 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
## # about it, so we send out all of our information about objects in # that particular zone.
## # that particular zone.
## assert self.DOIDnext < self.DOIDlast
## # This method is only used in conjunction with the CMU LAN zone = di.getUint32()
## # server. for obj in self.doId2do.values():
## if obj.zone == zone:
## assert self.DOIDnext < self.DOIDlast id = obj.doId
## zone = di.getUint32() if (self.isLocalId(id)):
## for obj in self.doId2do.values(): self.send(obj.dclass.clientFormatGenerate(obj, id, zone, []))
## if obj.zone == zone:
## id = obj.doId
## if (self.isLocalId(id)):
## self.send(obj.dclass.clientFormatGenerate(obj, id, zone, []))
def handleMessageType(self, msgType, di):
if msgType == CLIENT_GO_GET_LOST:
self.handleGoGetLost(di)
elif msgType == CLIENT_HEARTBEAT:
self.handleServerHeartbeat(di)
elif msgType == CLIENT_SYSTEM_MESSAGE:
self.handleSystemMessage(di)
elif msgType == CLIENT_CREATE_OBJECT_REQUIRED:
self.handleGenerateWithRequired(di)
elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER:
self.handleGenerateWithRequiredOther(di)
elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER_OWNER:
self.handleGenerateWithRequiredOtherOwner(di)
elif msgType == CLIENT_OBJECT_UPDATE_FIELD:
self.handleUpdateField(di)
elif msgType == CLIENT_OBJECT_DISABLE:
self.handleDisable(di)
elif msgType == CLIENT_OBJECT_DISABLE_OWNER:
self.handleDisable(di, ownerView=True)
elif msgType == CLIENT_OBJECT_DELETE_RESP:
self.handleDelete(di)
elif msgType == CLIENT_DONE_INTEREST_RESP:
self.handleInterestDoneMessage(di)
elif msgType == CLIENT_GET_STATE_RESP:
# TODO: is this message obsolete?
pass
#Roger wants to remove this elif msgType == CLIENT_QUERY_ONE_FIELD_RESP:
#Roger wants to remove this self.handleQueryOneFieldResp(di)
elif msgType == CLIENT_OBJECT_LOCATION:
self.handleObjectLocation(di)
else:
currentLoginState = self.loginFSM.getCurrentState()
if currentLoginState:
currentLoginStateName = currentLoginState.getName()
else:
currentLoginStateName = "None"
currentGameState = self.gameFSM.getCurrentState()
if currentGameState:
currentGameStateName = currentGameState.getName()
else:
currentGameStateName = "None"
ClientRepository.notify.warning(
"Ignoring unexpected message type: " +
str(msgType) +
" login state: " +
currentLoginStateName +
" game state: " +
currentGameStateName)
## TODO: This should probably be move to a derived class for CMU def createWithRequired(self, className, zoneId = 0, optionalFields=None):
## def createWithRequired(self, className, zoneId = 0, optionalFields=None): if self.DOIDnext >= self.DOIDlast:
## # This method is only used in conjunction with the CMU LAN self.notify.error(
## # server. "Cannot allocate a distributed object ID: all IDs used up.")
## return None
## if self.DOIDnext >= self.DOIDlast: id = self.DOIDnext
## self.notify.error( self.DOIDnext = self.DOIDnext + 1
## "Cannot allocate a distributed object ID: all IDs used up.") dclass = self.dclassesByName[className]
## return None classDef = dclass.getClassDef()
## id = self.DOIDnext if classDef == None:
## self.DOIDnext = self.DOIDnext + 1 self.notify.error("Could not create an undefined %s object." % (
## dclass = self.dclassesByName[className] dclass.getName()))
## classDef = dclass.getClassDef() obj = classDef(self)
## if classDef == None: obj.dclass = dclass
## self.notify.error("Could not create an undefined %s object." % ( obj.zone = zoneId
## dclass.getName())) obj.doId = id
## obj = classDef(self) self.doId2do[id] = obj
## obj.dclass = dclass obj.generateInit()
## obj.zone = zoneId obj.generate()
## obj.doId = id obj.announceGenerate()
## self.doId2do[id] = obj datagram = dclass.clientFormatGenerate(obj, id, zoneId, optionalFields)
## obj.generateInit() self.send(datagram)
## obj.generate() return obj
## obj.announceGenerate()
## datagram = dclass.clientFormatGenerate(obj, id, zoneId, optionalFields)
## self.send(datagram)
## return obj
def sendDisableMsg(self, doId): def sendDisableMsg(self, doId):
# This method is only used in conjunction with the CMU LAN
# server.
datagram = PyDatagram() datagram = PyDatagram()
datagram.addUint16(CLIENT_OBJECT_DISABLE) datagram.addUint16(CLIENT_OBJECT_DISABLE)
datagram.addUint32(doId) datagram.addUint32(doId)
self.send(datagram) self.send(datagram)
def sendDeleteMsg(self, doId): def sendDeleteMsg(self, doId):
# This method is only used in conjunction with the CMU LAN
# server.
datagram = PyDatagram() datagram = PyDatagram()
datagram.addUint16(CLIENT_OBJECT_DELETE) datagram.addUint16(CLIENT_OBJECT_DELETE)
datagram.addUint32(doId) datagram.addUint32(doId)
self.send(datagram) self.send(datagram)
def sendRemoveZoneMsg(self, zoneId, visibleZoneList=None): def sendRemoveZoneMsg(self, zoneId, visibleZoneList=None):
# This method is only used in conjunction with the CMU LAN
# server.
datagram = PyDatagram() datagram = PyDatagram()
datagram.addUint16(CLIENT_REMOVE_ZONE) datagram.addUint16(CLIENT_REMOVE_ZONE)
datagram.addUint32(zoneId) datagram.addUint32(zoneId)
@ -638,53 +92,44 @@ class ClientRepository(ConnectionRepository):
# send the message # send the message
self.send(datagram) self.send(datagram)
def getObjectsOfClass(self, objClass): def sendUpdateZone(self, obj, zoneId):
""" returns dict of doId:object, containing all objects id = obj.doId
that inherit from 'class'. returned dict is safely mutable. """ assert self.isLocalId(id)
doDict = {} self.sendDeleteMsg(id, 1)
for doId, do in self.doId2do.items(): obj.zone = zoneId
if isinstance(do, objClass): self.send(obj.dclass.clientFormatGenerate(obj, id, zoneId, []))
doDict[doId] = do
return doDict
def getObjectsOfExactClass(self, objClass): def sendSetZoneMsg(self, zoneId, visibleZoneList=None):
""" returns dict of doId:object, containing all objects that
are exactly of type 'class' (neglecting inheritance). returned
dict is safely mutable. """
doDict = {}
for doId, do in self.doId2do.items():
if do.__class__ == objClass:
doDict[doId] = do
return doDict
def sendSetLocation(self,doId,parentId,zoneId):
datagram = PyDatagram() datagram = PyDatagram()
datagram.addUint16(CLIENT_OBJECT_LOCATION) # Add message type
datagram.addUint32(doId) datagram.addUint16(CLIENT_SET_ZONE_CMU)
datagram.addUint32(parentId) # Add zone id
datagram.addUint32(zoneId) datagram.addUint32(zoneId)
self.send(datagram)
# 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): def handleDatagram(self, di):
if self.notify.getDebug(): if self.notify.getDebug():
print "ClientRepository received datagram:" print "ClientRepository received datagram:"
di.getDatagram().dumpHex(ostream) di.getDatagram().dumpHex(ostream)
msgType = self.getMsgType() msgType = self.getMsgType()
if self.handler == None:
self.handleMessageType(msgType, di)
else:
self.handler(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 publicServerDatagramHandler(self, msgType, di):
# These are the sort of messages we may expect from the public # These are the sort of messages we may expect from the public
# Panda server. # Panda server.
@ -705,81 +150,58 @@ class ClientRepository(ConnectionRepository):
else: else:
self.handleMessageType(msgType, di) self.handleMessageType(msgType, di)
def sendHeartbeat(self): # If we're processing a lot of datagrams within one frame, we
datagram = PyDatagram() # may forget to send heartbeats. Keep them coming!
# Add message type self.considerHeartbeat()
datagram.addUint16(CLIENT_HEARTBEAT)
# Send it!
self.send(datagram)
self.lastHeartbeat = globalClock.getRealTime()
# This is important enough to consider flushing immediately
# (particularly if we haven't run readerPollTask recently).
self.considerFlush()
def considerHeartbeat(self): def handleGenerateWithRequired(self, di):
"""Send a heartbeat message if we haven't sent one recently.""" # Get the class Id
if not self.heartbeatStarted: classId = di.getUint16()
self.notify.debug("Heartbeats not started; not sending.") # Get the DO Id
return 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()
elapsed = globalClock.getRealTime() - self.lastHeartbeat def generateWithRequiredFields(self, dclass, doId, di):
if elapsed < 0 or elapsed > self.heartbeatInterval: if self.doId2do.has_key(doId):
# It's time to send the heartbeat again (or maybe someone # ...it is in our dictionary.
# reset the clock back). # Just update it.
self.notify.info("Sending heartbeat mid-frame.") distObj = self.doId2do[doId]
self.startHeartbeat() assert(distObj.dclass == dclass)
distObj.generate()
def stopHeartbeat(self): distObj.updateRequiredFields(dclass, di)
taskMgr.remove("heartBeat") # updateRequiredFields calls announceGenerate
self.heartbeatStarted = 0 elif self.cache.contains(doId):
# ...it is in the cache.
def startHeartbeat(self): # Pull it out of the cache:
self.stopHeartbeat() distObj = self.cache.retrieve(doId)
self.heartbeatStarted = 1 assert(distObj.dclass == dclass)
self.sendHeartbeat() # put it in the dictionary:
self.waitForNextHeartBeat() self.doId2do[doId] = distObj
# and update it.
def sendHeartbeatTask(self, task): distObj.generate()
self.sendHeartbeat() distObj.updateRequiredFields(dclass, di)
self.waitForNextHeartBeat() # updateRequiredFields calls announceGenerate
return Task.done else:
# ...it is not in the dictionary or the cache.
def waitForNextHeartBeat(self): # Construct a new one
taskMgr.doMethodLater(self.heartbeatInterval, self.sendHeartbeatTask, classDef = dclass.getClassDef()
"heartBeat") if classDef == None:
self.notify.error("Could not create an undefined %s object." % (
## TODO: This should probably be move to a derived class for CMU dclass.getName()))
## def sendUpdateZone(self, obj, zoneId): distObj = classDef(self)
## # This method is only used in conjunction with the CMU LAN distObj.dclass = dclass
## # server. # Assign it an Id
## distObj.doId = doId
## id = obj.doId # Put the new do in the dictionary
## assert self.isLocalId(id) self.doId2do[doId] = distObj
## self.sendDeleteMsg(id, 1) # Update the required fields
## obj.zone = zoneId distObj.generateInit() # Only called when constructed
## self.send(obj.dclass.clientFormatGenerate(obj, id, zoneId, [])) distObj.generate()
distObj.updateRequiredFields(dclass, di)
def replaceMethod(self, oldMethod, newFunction): # updateRequiredFields calls announceGenerate
return 0 return distObj
def isLocalId(self,id):
return ((id >= self.DOIDbase) and (id < self.DOIDlast))
def haveCreateAuthority(self):
return (self.DOIDlast > self.DOIDnext)
def getWorld(self, doId):
# Get the world node for this object
obj = self.doId2do[doId]
worldNP = obj.getParent()
while 1:
nextNP = worldNP.getParent()
if nextNP == render:
break
elif worldNP.isEmpty():
return None
return worldNP

View File

@ -0,0 +1,572 @@
from pandac.PandaModules import *
from MsgTypes import *
from direct.task import Task
from direct.directnotify import DirectNotifyGlobal
import CRCache
from direct.distributed.ConnectionRepository import ConnectionRepository
from direct.showbase import PythonUtil
import ParentMgr
import RelatedObjectMgr
import time
from ClockDelta import *
from PyDatagram import PyDatagram
from PyDatagramIterator import PyDatagramIterator
class ClientRepositoryBase(ConnectionRepository):
"""
This maintains a client-side connection with a Panda server.
This base class exists to collect the common code between
ClientRepository, which is the CMU-provided, open-source version
of the client repository code, and OTPClientRepository, which is
the VR Studio's implementation of the same.
"""
notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepositoryBase")
def __init__(self, dcFileNames = None):
self.dcSuffix=""
ConnectionRepository.__init__(self, base.config, hasOwnerView=True)
self.context=100000
self.setClientDatagram(1)
self.recorder = base.recorder
self.readDCFile(dcFileNames)
self.cache=CRCache.CRCache()
self.cacheOwner=CRCache.CRCache()
self.serverDelta = 0
self.bootedIndex = None
self.bootedText = None
# create a parentMgr to handle distributed reparents
# this used to be 'token2nodePath'
self.parentMgr = ParentMgr.ParentMgr()
# The RelatedObjectMgr helps distributed objects find each
# other.
self.relatedObjectMgr = RelatedObjectMgr.RelatedObjectMgr(self)
# Keep track of how recently we last sent a heartbeat message.
# We want to keep these coming at heartbeatInterval seconds.
self.heartbeatInterval = base.config.GetDouble('heartbeat-interval', 10)
self.heartbeatStarted = 0
self.lastHeartbeat = 0
## def queryObjectAll(self, doID, context=0):
## """
## Get a one-time snapshot look at the object.
## """
## assert self.notify.debugStateCall(self)
## # Create a message
## datagram = PyDatagram()
## datagram.addServerHeader(
## doID, localAvatar.getDoId(), 2020)
## # A context that can be used to index the response if needed
## datagram.addUint32(context)
## self.send(datagram)
## # Make sure the message gets there.
## self.flush()
# Define uniqueName
def uniqueName(self, desc):
return desc
def getTables(self, ownerView):
if ownerView:
return self.doId2ownerView, self.cacheOwner
else:
return self.doId2do, self.cache
def sendDisconnect(self):
if self.isConnected():
# Tell the game server that we're going:
datagram = PyDatagram()
# Add message type
datagram.addUint16(CLIENT_DISCONNECT)
# Send the message
self.send(datagram)
self.notify.info("Sent disconnect message to server")
self.disconnect()
self.stopHeartbeat()
if 0: # Code that became obsolete before it was used:
def setWorldOffset(self, xOffset=0, yOffset=0):
self.worldXOffset=xOffset
self.worldYOffset=yOffset
def getWorldPos(self, nodePath):
pos = nodePath.getPos(self.worldScale)
return (int(round(pos.getX())), int(round(pos.getY())))
def sendWorldPos(self, x, y):
# The server will need to know the world
# offset of our current render node path
# and adjust the x, y accordingly. At one
# point I considered adding the world offset
# here, but that would just use extra bits.
onScreenDebug.add("worldPos", "%-4d, %-4d"%(x, y))
return #*#
datagram = PyDatagram()
# Add message type
datagram.addUint16(CLIENT_SET_WORLD_POS)
# Add x
datagram.addInt16(x)
# Add y
datagram.addSint16(y)
# send the message
self.send(datagram)
def checkWorldPos(self, nodePath):
worldPos = self.getWorldPos(nodePath)
if self.priorWorldPos != worldPos:
self.priorWorldPos = worldPos
self.sendWorldPos(worldPos[0], worldPos[1])
def allocateContext(self):
self.context+=1
return self.context
def setServerDelta(self, delta):
"""
Indicates the approximate difference in seconds between the
client's clock and the server's clock, in universal time (not
including timezone shifts). This is mainly useful for
reporting synchronization information to the logs; don't
depend on it for any precise timing requirements.
Also see Notify.setServerDelta(), which also accounts for a
timezone shift.
"""
self.serverDelta = delta
def getServerDelta(self):
return self.serverDelta
def getServerTimeOfDay(self):
"""
Returns the current time of day (seconds elapsed since the
1972 epoch) according to the server's clock. This is in GMT,
and hence is irrespective of timezones.
The value is computed based on the client's clock and the
known delta from the server's clock, which is not terribly
precisely measured and may drift slightly after startup, but
it should be accurate plus or minus a couple of seconds.
"""
return time.time() + self.serverDelta
def handleGenerateWithRequired(self, di):
parentId = di.getUint32()
zoneId = di.getUint32()
assert parentId == self.GameGlobalsId or parentId in self.doId2do
# 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, parentId, zoneId)
dclass.stopGenerate()
def handleGenerateWithRequiredOther(self, di):
parentId = di.getUint32()
zoneId = di.getUint32()
assert parentId == self.GameGlobalsId or parentId in self.doId2do
# 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.generateWithRequiredOtherFields(dclass, doId, di, parentId, zoneId)
dclass.stopGenerate()
def handleGenerateWithRequiredOtherOwner(self, di):
# Get the class Id
classId = di.getUint16()
# Get the DO Id
doId = di.getUint32()
# parentId and zoneId are not relevant here
parentId = di.getUint32()
zoneId = 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.generateWithRequiredOtherFieldsOwner(dclass, doId, di)
dclass.stopGenerate()
def handleQuietZoneGenerateWithRequired(self, di):
# Special handler for quiet zone generates -- we need to filter
parentId = di.getUint32()
zoneId = di.getUint32()
assert parentId in self.doId2do
# Get the class Id
classId = di.getUint16()
# Get the DO Id
doId = di.getUint32()
# Look up the dclass
dclass = self.dclassesByNumber[classId]
dclass.startGenerate()
distObj = self.generateWithRequiredFields(dclass, doId, di, parentId, zoneId)
dclass.stopGenerate()
def handleQuietZoneGenerateWithRequiredOther(self, di):
# Special handler for quiet zone generates -- we need to filter
parentId = di.getUint32()
zoneId = di.getUint32()
assert parentId in self.doId2do
# Get the class Id
classId = di.getUint16()
# Get the DO Id
doId = di.getUint32()
# Look up the dclass
dclass = self.dclassesByNumber[classId]
dclass.startGenerate()
distObj = self.generateWithRequiredOtherFields(dclass, doId, di, parentId, zoneId)
dclass.stopGenerate()
def generateWithRequiredFields(self, dclass, doId, di, parentId, zoneId):
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.setLocation(parentId, zoneId)
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.setLocation(parentId, zoneId)
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.setLocation(parentId, zoneId)
distObj.updateRequiredFields(dclass, di)
# updateRequiredFields calls announceGenerate
print "New DO:%s, dclass:%s"%(doId, dclass.getName())
return distObj
## def generateGlobalObject(self, doId, dcname):
## # Look up the dclass
## dclass = self.dclassesByName[dcname]
## # Create a new distributed object, and put it in the dictionary
## #distObj = self.generateWithRequiredFields(dclass, doId, di)
## # 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()
## # TODO: ROGER: where should we get parentId and zoneId?
## parentId = None
## zoneId = None
## distObj.setLocation(parentId, zoneId)
## # updateRequiredFields calls announceGenerate
## return distObj
def generateWithRequiredOtherFields(self, dclass, doId, di,
parentId = None, zoneId = None):
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.setLocation(parentId, zoneId)
distObj.updateRequiredOtherFields(dclass, di)
# updateRequiredOtherFields 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.setLocation(parentId, zoneId)
distObj.updateRequiredOtherFields(dclass, di)
# updateRequiredOtherFields 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.setLocation(parentId, zoneId)
distObj.updateRequiredOtherFields(dclass, di)
# updateRequiredOtherFields calls announceGenerate
return distObj
def generateWithRequiredOtherFieldsOwner(self, dclass, doId, di):
if self.doId2ownerView.has_key(doId):
# ...it is in our dictionary.
# Just update it.
distObj = self.doId2ownerView[doId]
assert distObj.dclass == dclass
distObj.generate()
distObj.updateRequiredOtherFields(dclass, di)
# updateRequiredOtherFields 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.updateRequiredOtherFields(dclass, di)
# updateRequiredOtherFields 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.updateRequiredOtherFields(dclass, di)
# updateRequiredOtherFields calls announceGenerate
return distObj
def handleDisable(self, di, ownerView=False):
# Get the DO Id
doId = di.getUint32()
# disable it.
self.disableDoId(doId, ownerView)
def disableDoId(self, doId, ownerView=False):
table, cache = self.getTables(ownerView)
# Make sure the object exists
if table.has_key(doId):
# Look up the object
distObj = table[doId]
# remove the object from the dictionary
del table[doId]
# Only cache the object if it is a "cacheable" type
# object; this way we don't clutter up the caches with
# trivial objects that don't benefit from caching.
if distObj.getCacheable():
cache.cache(distObj)
else:
distObj.deleteOrDelay()
else:
self._logFailedDisable(doId, ownerView)
def _logFailedDisable(self, doId, ownerView):
ClientRepository.notify.warning(
"Disable failed. DistObj "
+ str(doId) +
" is not in dictionary, ownerView=%s" % ownerView)
def handleDelete(self, di):
# overridden by ToontownClientRepository
assert 0
def handleUpdateField(self, di):
"""
This method is called when a CLIENT_OBJECT_UPDATE_FIELD
message is received; it decodes the update, unpacks the
arguments, and calls the corresponding method on the indicated
DistributedObject.
In fact, this method is exactly duplicated by the C++ method
cConnectionRepository::handle_update_field(), which was
written to optimize the message loop by handling all of the
CLIENT_OBJECT_UPDATE_FIELD messages in C++. That means that
nowadays, this Python method will probably never be called,
since UPDATE_FIELD messages will not even be passed to the
Python message handlers. But this method remains for
documentation purposes, and also as a "just in case" handler
in case we ever do come across a situation in the future in
which python might handle the UPDATE_FIELD message.
"""
# Get the DO Id
doId = di.getUint32()
#print("Updating " + str(doId))
# Find the DO
do = self.doId2do.get(doId)
if do is not None:
# Let the dclass finish the job
do.dclass.receiveUpdate(do, di)
else:
ClientRepository.notify.warning(
"Asked to update non-existent DistObj " + str(doId))
def handleGoGetLost(self, di):
# The server told us it's about to drop the connection on us.
# Get ready!
if (di.getRemainingSize() > 0):
self.bootedIndex = di.getUint16()
self.bootedText = di.getString()
ClientRepository.notify.warning(
"Server is booting us out (%d): %s" % (self.bootedIndex, self.bootedText))
else:
self.bootedIndex = None
self.bootedText = None
ClientRepository.notify.warning(
"Server is booting us out with no explanation.")
def handleServerHeartbeat(self, di):
# Got a heartbeat message from the server.
if base.config.GetBool('server-heartbeat-info', 1):
ClientRepository.notify.info("Server heartbeat.")
def handleSystemMessage(self, di):
# Got a system message from the server.
message = di.getString()
self.notify.info('Message from server: %s' % (message))
return message
def getObjectsOfClass(self, objClass):
""" returns dict of doId:object, containing all objects
that inherit from 'class'. returned dict is safely mutable. """
doDict = {}
for doId, do in self.doId2do.items():
if isinstance(do, objClass):
doDict[doId] = do
return doDict
def getObjectsOfExactClass(self, objClass):
""" returns dict of doId:object, containing all objects that
are exactly of type 'class' (neglecting inheritance). returned
dict is safely mutable. """
doDict = {}
for doId, do in self.doId2do.items():
if do.__class__ == objClass:
doDict[doId] = do
return doDict
def sendSetLocation(self,doId,parentId,zoneId):
datagram = PyDatagram()
datagram.addUint16(CLIENT_OBJECT_LOCATION)
datagram.addUint32(doId)
datagram.addUint32(parentId)
datagram.addUint32(zoneId)
self.send(datagram)
def sendHeartbeat(self):
datagram = PyDatagram()
# Add message type
datagram.addUint16(CLIENT_HEARTBEAT)
# Send it!
self.send(datagram)
self.lastHeartbeat = globalClock.getRealTime()
# This is important enough to consider flushing immediately
# (particularly if we haven't run readerPollTask recently).
self.considerFlush()
def considerHeartbeat(self):
"""Send a heartbeat message if we haven't sent one recently."""
if not self.heartbeatStarted:
self.notify.debug("Heartbeats not started; not sending.")
return
elapsed = globalClock.getRealTime() - self.lastHeartbeat
if elapsed < 0 or elapsed > self.heartbeatInterval:
# It's time to send the heartbeat again (or maybe someone
# reset the clock back).
self.notify.info("Sending heartbeat mid-frame.")
self.startHeartbeat()
def stopHeartbeat(self):
taskMgr.remove("heartBeat")
self.heartbeatStarted = 0
def startHeartbeat(self):
self.stopHeartbeat()
self.heartbeatStarted = 1
self.sendHeartbeat()
self.waitForNextHeartBeat()
def sendHeartbeatTask(self, task):
self.sendHeartbeat()
self.waitForNextHeartBeat()
return Task.done
def waitForNextHeartBeat(self):
taskMgr.doMethodLater(self.heartbeatInterval, self.sendHeartbeatTask,
"heartBeat")
def replaceMethod(self, oldMethod, newFunction):
return 0
def getWorld(self, doId):
# Get the world node for this object
obj = self.doId2do[doId]
worldNP = obj.getParent()
while 1:
nextNP = worldNP.getParent()
if nextNP == render:
break
elif worldNP.isEmpty():
return None
return worldNP
def isLocalId(self, id):
# By default, no ID's are local. See also
# ClientRepository.isLocalId().
return 0

View File

@ -139,6 +139,10 @@ class ConnectionRepository(
self.dclassesByNumber = {} self.dclassesByNumber = {}
self.hashVal = 0 self.hashVal = 0
if isinstance(dcFileNames, types.StringTypes):
# If we were given a single string, make it a list.
dcFileNames = [dcFileNames]
dcImports = {} dcImports = {}
if dcFileNames == None: if dcFileNames == None:
readResult = dcFile.readAll() readResult = dcFile.readAll()

View File

@ -29,6 +29,7 @@ CLIENT_OBJECT_DISABLE_OWNER = 26
CLIENT_OBJECT_DISABLE_OWNER_RESP = 26 CLIENT_OBJECT_DISABLE_OWNER_RESP = 26
CLIENT_OBJECT_DELETE = 27 CLIENT_OBJECT_DELETE = 27
CLIENT_OBJECT_DELETE_RESP = 27 CLIENT_OBJECT_DELETE_RESP = 27
CLIENT_SET_ZONE_CMU = 29
CLIENT_REMOVE_ZONE = 30 CLIENT_REMOVE_ZONE = 30
CLIENT_SET_AVATAR = 32 CLIENT_SET_AVATAR = 32
CLIENT_CREATE_OBJECT_REQUIRED = 34 CLIENT_CREATE_OBJECT_REQUIRED = 34

View File

@ -212,7 +212,7 @@ class ServerRepository:
if type == CLIENT_DISCONNECT: if type == CLIENT_DISCONNECT:
self.handleClientDisconnect(datagram.getConnection()) self.handleClientDisconnect(datagram.getConnection())
elif type == CLIENT_SET_ZONE: elif type == CLIENT_SET_ZONE_CMU:
self.handleSetZone(dgi, datagram.getConnection()) self.handleSetZone(dgi, datagram.getConnection())
elif type == CLIENT_REMOVE_ZONE: elif type == CLIENT_REMOVE_ZONE:
self.handleRemoveZone(dgi, datagram.getConnection()) self.handleRemoveZone(dgi, datagram.getConnection())
@ -270,12 +270,13 @@ class ServerRepository:
"Received update for field %s on object %s; no such field for class %s." % ( "Received update for field %s on object %s; no such field for class %s." % (
fieldid, doid, dclass.getName())) fieldid, doid, dclass.getName()))
return return
if (dcfield.isBroadcast()): if (dcfield.hasKeyword('broadcast')):
if (dcfield.isP2p()):
if (dcfield.hasKeyword('p2p')):
self.sendToZoneExcept(self.DOIDtoZones[doid], datagram, 0) self.sendToZoneExcept(self.DOIDtoZones[doid], datagram, 0)
else: else:
self.sendToZoneExcept(self.DOIDtoZones[doid], datagram, connection) self.sendToZoneExcept(self.DOIDtoZones[doid], datagram, connection)
elif (dcfield.isP2p()): elif (dcfield.hasKeyword('p2p')):
doidbase = (doid / self.DOIDrange) * self.DOIDrange doidbase = (doid / self.DOIDrange) * self.DOIDrange
self.cw.send(datagram, self.DOIDtoClient[doidbase]) self.cw.send(datagram, self.DOIDtoClient[doidbase])
else: else:
@ -352,7 +353,7 @@ class ServerRepository:
del self.ClientDOIDbase[connection] del self.ClientDOIDbase[connection]
del self.ClientObjects[connection] del self.ClientObjects[connection]
# client told us it's zone(s), store information # client told us its zone(s), store information
def handleSetZone(self, dgi, connection): def handleSetZone(self, dgi, connection):
while dgi.getRemainingSize() > 0: while dgi.getRemainingSize() > 0:
ZoneID = dgi.getUint32() ZoneID = dgi.getUint32()