mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 10:54:24 -04:00
separate out ClientRepositoryBase, ClientRepository, and OTPClientRepository
This commit is contained in:
parent
92291308eb
commit
e89d02c9c5
@ -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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
572
direct/src/distributed/ClientRepositoryBase.py
Normal file
572
direct/src/distributed/ClientRepositoryBase.py
Normal 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
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user