panda3d/direct/src/distributed/ServerRepository.py
2005-03-09 20:33:13 +00:00

413 lines
16 KiB
Python

"""ServerRepository module: contains the ServerRepository class"""
from pandac.PandaModules import *
#from TaskManagerGlobal import *
from direct.distributed.MsgTypes import *
from direct.task import Task
from direct.directnotify import DirectNotifyGlobal
#from NetDatagram import NetDatagram
#from Datagram import Datagram
#from Datagram import *
from pandac import Datagram
from pandac import DatagramIterator
from direct.distributed.PyDatagram import PyDatagram
from direct.distributed.PyDatagramIterator import PyDatagramIterator
#from PointerToConnection import PointerToConnection
import time
import types
class ServerRepository:
""" This maintains the server-side connection with a Panda server.
It is only for use with the Panda LAN server provided by CMU."""
notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepository")
def __init__(self, tcpPort, udpPort):
self.qcm = QueuedConnectionManager()
self.qcl = QueuedConnectionListener(self.qcm, 0)
self.qcr = QueuedConnectionReader(self.qcm, 0)
self.cw = ConnectionWriter(self.qcm,0)
self.tcpRendezvous = self.qcm.openTCPServerRendezvous(tcpPort, 10)
print self.tcpRendezvous
self.qcl.addConnection(self.tcpRendezvous)
taskMgr.add(self.listenerPoll, "serverListenerPollTask")
taskMgr.add(self.readerPollUntilEmpty, "serverReaderPollTask")
taskMgr.add(self.clientHardDisconnectTask, "clientHardDisconnect")
self.ClientIP = {}
self.ClientZones = {}
self.ClientDOIDbase = {}
self.ClientObjects = {}
self.DOIDnext = 1
self.DOIDrange = 1000000
self.DOIDtoClient = {}
self.DOIDtoZones = {}
self.DOIDtoDClass = {}
self.ZonesToClients = {}
self.ZonetoDOIDs = {}
self.dcFile = DCFile()
self.dcSuffix = ''
self.readDCFile()
def importModule(self, dcImports, moduleName, importSymbols):
""" Imports the indicated moduleName and all of its symbols
into the current namespace. This more-or-less reimplements
the Python import command. """
module = __import__(moduleName, globals(), locals(), importSymbols)
if importSymbols:
# "from moduleName import symbolName, symbolName, ..."
# Copy just the named symbols into the dictionary.
if importSymbols == ['*']:
# "from moduleName import *"
if hasattr(module, "__all__"):
importSymbols = module.__all__
else:
importSymbols = module.__dict__.keys()
for symbolName in importSymbols:
if hasattr(module, symbolName):
dcImports[symbolName] = getattr(module, symbolName)
else:
raise StandardError, 'Symbol %s not defined in module %s.' % (symbolName, moduleName)
else:
# "import moduleName"
# Copy the root module name into the dictionary.
# Follow the dotted chain down to the actual module.
components = moduleName.split('.')
dcImports[components[0]] = module
def readDCFile(self, dcFileNames = None):
"""
Reads in the dc files listed in dcFileNames, or if
dcFileNames is None, reads in all of the dc files listed in
the Configrc file.
"""
dcFile = self.dcFile
dcFile.clear()
self.dclassesByName = {}
self.dclassesByNumber = {}
self.hashVal = 0
dcImports = {}
if dcFileNames == None:
readResult = dcFile.readAll()
if not readResult:
self.notify.error("Could not read dc file.")
else:
for dcFileName in dcFileNames:
readResult = dcFile.read(Filename(dcFileName))
if not readResult:
self.notify.error("Could not read dc file: %s" % (dcFileName))
self.hashVal = dcFile.getHash()
# Now import all of the modules required by the DC file.
for n in range(dcFile.getNumImportModules()):
moduleName = dcFile.getImportModule(n)
# Maybe the module name is represented as "moduleName/AI".
suffix = moduleName.split('/')
moduleName = suffix[0]
if self.dcSuffix and self.dcSuffix in suffix[1:]:
moduleName += self.dcSuffix
importSymbols = []
for i in range(dcFile.getNumImportSymbols(n)):
symbolName = dcFile.getImportSymbol(n, i)
# Maybe the symbol name is represented as "symbolName/AI".
suffix = symbolName.split('/')
symbolName = suffix[0]
if self.dcSuffix and self.dcSuffix in suffix[1:]:
symbolName += self.dcSuffix
importSymbols.append(symbolName)
self.importModule(dcImports, moduleName, importSymbols)
# Now get the class definition for the classes named in the DC
# file.
for i in range(dcFile.getNumClasses()):
dclass = dcFile.getClass(i)
number = dclass.getNumber()
className = dclass.getName() + self.dcSuffix
# Does the class have a definition defined in the newly
# imported namespace?
classDef = dcImports.get(className)
# Also try it without the dcSuffix.
if classDef == None:
classDef = dcImports.get(dcClass.getName())
if classDef == None:
self.notify.info("No class definition for %s." % (className))
else:
if type(classDef) == types.ModuleType:
if not hasattr(classDef, className):
self.notify.error("Module %s does not define class %s." % (className, className))
classDef = getattr(classDef, className)
if type(classDef) != types.ClassType:
self.notify.error("Symbol %s is not a class name." % (className))
else:
dclass.setClassDef(classDef)
self.dclassesByName[className] = dclass
if number >= 0:
self.dclassesByNumber[number] = dclass
# listens for new clients
def listenerPoll(self, task):
if self.qcl.newConnectionAvailable():
rendezvous = PointerToConnection()
netAddress = NetAddress()
newConnection = PointerToConnection()
retVal = self.qcl.getNewConnection(rendezvous, netAddress,
newConnection)
if retVal:
# Crazy dereferencing
newConnection=newConnection.p()
self.qcr.addConnection(newConnection)
# Add clients infomation to dictionary
self.ClientIP[newConnection] = netAddress.getIpString()
self.ClientZones[newConnection] = []
self.ClientObjects[newConnection] = []
self.lastConnection = newConnection
self.sendDOIDrange(self.lastConnection)
else:
self.notify.warning(
"getNewConnection returned false")
return Task.cont
# continuously polls for new messages on the server
def readerPollUntilEmpty(self, task):
while self.readerPollOnce():
pass
return Task.cont
# checks for available messages to the server
def readerPollOnce(self):
availGetVal = self.qcr.dataAvailable()
if availGetVal:
datagram = NetDatagram()
readRetVal = self.qcr.getData(datagram)
if readRetVal:
# need to send to message processing unit
self.handleDatagram(datagram)
else:
self.notify.warning("getData returned false")
return availGetVal
# switching station for messages
def handleDatagram(self, datagram):
dgi = DatagramIterator.DatagramIterator(datagram)
type = dgi.getUint16()
if type == CLIENT_DISCONNECT:
self.handleClientDisconnect(datagram.getConnection())
elif type == CLIENT_SET_ZONE:
self.handleSetZone(dgi, datagram.getConnection())
elif type == CLIENT_REMOVE_ZONE:
self.handleRemoveZone(dgi, datagram.getConnection())
elif type == CLIENT_CREATE_OBJECT_REQUIRED:
self.handleClientCreateObjectRequired(datagram, dgi)
elif type == CLIENT_OBJECT_UPDATE_FIELD:
self.handleClientUpdateField(datagram, dgi)
elif type == CLIENT_OBJECT_DELETE:
self.handleClientDeleteObject(datagram, dgi.getUint32())
elif type == CLIENT_OBJECT_DISABLE:
self.handleClientDisable(datagram, dgi.getUint32())
else:
self.notify.error("unrecognized message")
# client wants to create an object, so we store appropriate data,
# and then pass message along to corresponding zones
def handleClientCreateObjectRequired(self, datagram, dgi):
connection = datagram.getConnection()
# no need to create a new message, just forward the received
# message as it has the same msg type number
zone = dgi.getUint32()
classid = dgi.getUint16()
doid = dgi.getUint32()
rest = dgi.getRemainingBytes()
datagram = NetDatagram()
datagram.addUint16(CLIENT_CREATE_OBJECT_REQUIRED)
datagram.addUint16(classid)
datagram.addUint32(doid)
datagram.appendData(rest)
dclass = self.dclassesByNumber[classid]
if self.ClientObjects[connection].count(doid) == 0:
self.ClientObjects[connection].append(doid)
self.DOIDtoZones[doid] = zone
self.DOIDtoDClass[doid] = dclass
if zone in self.ZonetoDOIDs:
if self.ZonetoDOIDs[zone].count(doid)==0:
self.ZonetoDOIDs[zone].append(doid)
else:
self.ZonetoDOIDs[zone] = [doid]
self.sendToZoneExcept(zone, datagram, connection)
# client wants to update an object, forward message along
# to corresponding zone
def handleClientUpdateField(self, datagram, dgi):
connection = datagram.getConnection()
doid = dgi.getUint32()
fieldid = dgi.getUint16()
dclass = self.DOIDtoDClass[doid]
dcfield = dclass.getFieldByIndex(fieldid)
if dcfield == None:
self.notify.error(
"Received update for field %s on object %s; no such field for class %s." % (
fieldid, doid, dclass.getName()))
return
if (dcfield.isBroadcast()):
if (dcfield.isP2p()):
self.sendToZoneExcept(self.DOIDtoZones[doid], datagram, 0)
else:
self.sendToZoneExcept(self.DOIDtoZones[doid], datagram, connection)
elif (dcfield.isP2p()):
doidbase = (doid / self.DOIDrange) * self.DOIDrange
self.cw.send(datagram, self.DOIDtoClient[doidbase])
else:
self.notify.warning(
"Message is not broadcast, p2p, or broadcast+p2p")
# client disables an object, let everyone know who is in
# that zone know about it
def handleClientDisable(self, datagram, doid):
# now send disable message to all clients that need to know
if doid in self.DOIDtoZones:
self.sendToZoneExcept(self.DOIDtoZones[doid], datagram, 0)
# client deletes an object, let everyone who is in zone with
# object know about it
def handleClientDeleteObject(self, datagram, doid):
if doid in self.DOIDtoZones:
self.sendToZoneExcept(self.DOIDtoZones[doid], datagram, 0)
self.ClientObjects[datagram.getConnection()].remove(doid)
self.ZonetoDOIDs[self.DOIDtoZones[doid]].remove(doid)
del self.DOIDtoZones[doid]
del self.DOIDtoDClass[doid]
def sendAvatarGenerate(self):
datagram = PyDatagram()
# Message type is 1
datagram.addUint16(ALL_OBJECT_GENERATE_WITH_REQUIRED)
# Avatar class type is 2
datagram.addUint8(2)
# A sample id
datagram.addUint32(10)
# The only required field is the zone field
datagram.addUint32(999)
self.cw.send(datagram, self.lastConnection)
# sends the client the range of doid's that the client can use
def sendDOIDrange(self, connection):
# reuse DOID assignments if we can
id = self.DOIDnext + self.DOIDrange
self.DOIDnext = self.DOIDnext + self.DOIDrange
self.DOIDtoClient[id] = connection
self.ClientDOIDbase[connection] = id
datagram = NetDatagram()
datagram.addUint16(CLIENT_SET_DOID_RANGE)
datagram.addUint32(id)
datagram.addUint32(self.DOIDrange)
print "Sending DOID range: ",id,self.DOIDrange
self.cw.send(datagram, connection)
# a client disconnected from us, we need to update our data, also tell other clients to remove
# the disconnected clients objects
def handleClientDisconnect(self, connection):
if (self.ClientIP.has_key(connection)):
del self.DOIDtoClient[self.ClientDOIDbase[connection]]
for zone in self.ClientZones[connection]:
if len(self.ZonesToClients[zone]) == 1:
del self.ZonesToClients[zone]
else:
self.ZonesToClients[zone].remove(connection)
for obj in self.ClientObjects[connection]:
#create and send delete message
datagram = NetDatagram()
datagram.addUint16(CLIENT_OBJECT_DELETE_RESP)
datagram.addUint32(obj)
self.sendToZoneExcept(self.DOIDtoZones[obj], datagram, 0)
self.ZonetoDOIDs[self.DOIDtoZones[obj]].remove(obj)
del self.DOIDtoZones[obj]
del self.DOIDtoDClass[obj]
del self.ClientIP[connection]
del self.ClientZones[connection]
del self.ClientDOIDbase[connection]
del self.ClientObjects[connection]
# client told us it's zone(s), store information
def handleSetZone(self, dgi, connection):
while dgi.getRemainingSize() > 0:
ZoneID = dgi.getUint32()
if self.ClientZones[connection].count(ZoneID) == 0:
self.ClientZones[connection].append(ZoneID)
if ZoneID in self.ZonesToClients:
if self.ZonesToClients[ZoneID].count(connection) == 0:
self.ZonesToClients[ZoneID].append(connection)
else:
self.ZonesToClients[ZoneID] = [connection]
# We have a new member, need to get all of the data from clients who may have objects in this zone
datagram = NetDatagram()
datagram.addUint16(CLIENT_REQUEST_GENERATES)
datagram.addUint32(ZoneID)
self.sendToAll(datagram)
print "SENDING REQUEST GENERATES (",ZoneID,") TO ALL"
# client has moved zones, need to update them
def handleRemoveZone(self, dgi, connection):
while dgi.getRemainingSize() > 0:
ZoneID = dgi.getUint32()
if self.ClientZones[connection].count(ZoneID) == 1:
self.ClientZones[connection].remove(ZoneID)
if ZoneID in self.ZonesToClients:
if self.ZonesToClients[ZoneID].count(connection) == 1:
self.ZonesToClients[ZoneID].remove(connection)
for i in self.ZonetoDOIDs[ZoneID]:
datagram = NetDatagram()
datagram.addUint16(CLIENT_OBJECT_DELETE)
datagram.addUint32(i)
self.cw.send(datagram, connection)
# client did not tell us he was leaving but we lost connection to him, so we need to update our data and tell others
def clientHardDisconnectTask(self, task):
for i in self.ClientIP.keys():
if not self.qcr.isConnectionOk(i):
self.handleClientDisconnect(i)
return Task.cont
# sends a message to everyone who is in the zone
def sendToZoneExcept(self, ZoneID, datagram, connection):
if ZoneID in self.ZonesToClients:
for conn in self.ZonesToClients[ZoneID]:
if (conn != connection):
self.cw.send(datagram, conn)
# sends a message to all connected clients
def sendToAll(self, datagram):
for client in self.ClientIP.keys():
self.cw.send(datagram, client)