| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578 |
- from pandac.PandaModules import *
- from direct.task import Task
- from direct.directnotify import DirectNotifyGlobal
- from direct.distributed.DoInterestManager import DoInterestManager
- from direct.distributed.DoCollectionManager import DoCollectionManager
- from PyDatagram import PyDatagram
- from PyDatagramIterator import PyDatagramIterator
- import types
- import imp
- class ConnectionRepository(
- DoInterestManager, DoCollectionManager, CConnectionRepository):
- """
- This is a base class for things that know how to establish a
- connection (and exchange datagrams) with a gameserver. This
- includes ClientRepository and AIRepository.
- """
- notify = DirectNotifyGlobal.directNotify.newCategory("ConnectionRepository")
- taskPriority = -30
- CM_HTTP=0
- CM_NET=1
- CM_NATIVE=2
- def __init__(self, connectMethod, config, hasOwnerView=False):
- assert self.notify.debugCall()
- # let the C connection repository know whether we're supporting
- # 'owner' views of distributed objects (i.e. 'receives ownrecv',
- # 'I own this object and have a separate view of it regardless of
- # where it currently is located')
- CConnectionRepository.__init__(self, hasOwnerView)
- # DoInterestManager.__init__ relies on CConnectionRepository being
- # initialized
- DoInterestManager.__init__(self)
- DoCollectionManager.__init__(self)
- self.setPythonRepository(self)
-
- self.config = config
- if self.config.GetBool('verbose-repository'):
- self.setVerbose(1)
- # Set this to 'http' to establish a connection to the server
- # using the HTTPClient interface, which ultimately uses the
- # OpenSSL socket library (even though SSL is not involved).
- # This is not as robust a socket library as NET's, but the
- # HTTPClient interface does a good job of negotiating the
- # connection over an HTTP proxy if one is in use.
- #
- # Set it to 'net' to use Panda's net interface
- # (e.g. QueuedConnectionManager, etc.) to establish the
- # connection. This is a higher-level layer build on top of
- # the low-level "native net" library. There is no support for
- # proxies. This is a good, general choice.
- #
- # Set it to 'native' to use Panda's low-level native net
- # interface directly. This is much faster than either http or
- # net for high-bandwidth (e.g. server) applications, but it
- # doesn't support the simulated delay via the start_delay()
- # call.
- #
- # Set it to 'default' to use an appropriate interface
- # according to the type of ConnectionRepository we are
- # creating.
- userConnectMethod = self.config.GetString('connect-method', 'default')
- if userConnectMethod == 'http':
- connectMethod = self.CM_HTTP
- elif userConnectMethod == 'net':
- connectMethod = self.CM_NET
- elif userConnectMethod == 'native':
- connectMethod = self.CM_NATIVE
- self.connectMethod = connectMethod
- if self.connectMethod == self.CM_HTTP:
- self.notify.info("Using connect method 'http'")
- elif self.connectMethod == self.CM_NET:
- self.notify.info("Using connect method 'net'")
- elif self.connectMethod == self.CM_NATIVE:
- self.notify.info("Using connect method 'native'")
-
- self.connectHttp = None
- self.http = None
- # This DatagramIterator is constructed once, and then re-used
- # each time we read a datagram.
- self.private__di = PyDatagramIterator()
- self.recorder = None
- # This is the string that is appended to symbols read from the
- # DC file. The AIRepository will redefine this to 'AI'.
- self.dcSuffix = ''
- self._serverAddress = ''
- if self.config.GetBool('want-debug-leak', 1):
- import gc
- gc.set_debug(gc.DEBUG_SAVEALL)
- def generateGlobalObject(self, doId, dcname, values=None):
- def applyFieldValues(distObj, dclass, values):
- for i in range(dclass.getNumInheritedFields()):
- field = dclass.getInheritedField(i)
- if field.asMolecularField() == None:
- value = values.get(field.getName(), None)
- if value is None and field.isRequired():
- # Gee, this could be better. What would really be
- # nicer is to get value from field.getDefaultValue
- # or similar, but that returns a binary string, not
- # a python tuple, like the following does. If you
- # want to change something better, please go ahead.
- packer = DCPacker()
- packer.beginPack(field)
- packer.packDefaultValue()
- packer.endPack()
- unpacker = DCPacker()
- unpacker.setUnpackData(packer.getString())
- unpacker.beginUnpack(field)
- value = unpacker.unpackObject()
- unpacker.endUnpack()
- if value is not None:
- function = getattr(distObj, field.getName())
- if function is not None:
- function(*value)
- else:
- self.notify.error("\n\n\nNot able to find %s.%s"%(
- distObj.__class__.__name__, field.getName()))
- # Look up the dclass
- dclass = self.dclassesByName.get(dcname+self.dcSuffix)
- if dclass is None:
- print "\n\n\nNeed to define", dcname+self.dcSuffix
- dclass = self.dclassesByName.get(dcname+'AI')
- if dclass is None:
- dclass = self.dclassesByName.get(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()
- if values is not None:
- applyFieldValues(distObj, dclass, values)
- distObj.announceGenerate()
- distObj.parentId = 0
- distObj.zoneId = 0
- # updateRequiredFields calls announceGenerate
- return distObj
- 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.getDcFile()
- dcFile.clear()
- self.dclassesByName = {}
- self.dclassesByNumber = {}
- self.hashVal = 0
- if isinstance(dcFileNames, types.StringTypes):
- # If we were given a single string, make it a list.
- dcFileNames = [dcFileNames]
- 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))
- if not dcFile.allObjectsValid():
- names = []
- for i in range(dcFile.getNumTypedefs()):
- td = dcFile.getTypedef(i)
- if td.isBogusTypedef():
- names.append(td.getName())
- nameList = ', '.join(names)
- self.notify.error("Undefined types in DC file: " + nameList)
- 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]
- suffix=suffix[1:]
- if self.dcSuffix in suffix:
- moduleName += self.dcSuffix
- elif self.dcSuffix == 'UD' and 'AI' in suffix: #HACK:
- moduleName += 'AI'
- 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]
- suffix=suffix[1:]
- if self.dcSuffix in suffix:
- symbolName += self.dcSuffix
- elif self.dcSuffix == 'UD' and 'AI' in suffix: #HACK:
- symbolName += 'AI'
- 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)
- if classDef is None and self.dcSuffix == 'UD': #HACK:
- className = dclass.getName() + 'AI'
- classDef = dcImports.get(className)
- # Also try it without the dcSuffix.
- if classDef == None:
- className = dclass.getName()
- classDef = dcImports.get(className)
- if classDef is None:
- self.notify.debug("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 and type(classDef) != types.TypeType:
- 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
- # Owner Views
- if self.hasOwnerView():
- ownerDcSuffix = self.dcSuffix + 'OV'
- # dict of class names (without 'OV') that have owner views
- ownerImportSymbols = {}
- # 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]
- suffix=suffix[1:]
- if ownerDcSuffix in suffix:
- moduleName = moduleName + ownerDcSuffix
- importSymbols = []
- for i in range(dcFile.getNumImportSymbols(n)):
- symbolName = dcFile.getImportSymbol(n, i)
- # Check for the OV suffix
- suffix = symbolName.split('/')
- symbolName = suffix[0]
- suffix=suffix[1:]
- if ownerDcSuffix in suffix:
- symbolName += ownerDcSuffix
- importSymbols.append(symbolName)
- ownerImportSymbols[symbolName] = None
- self.importModule(dcImports, moduleName, importSymbols)
- # Now get the class definition for the owner classes named
- # in the DC file.
- for i in range(dcFile.getNumClasses()):
- dclass = dcFile.getClass(i)
- if ((dclass.getName()+ownerDcSuffix) in ownerImportSymbols):
- number = dclass.getNumber()
- className = dclass.getName() + ownerDcSuffix
- # Does the class have a definition defined in the newly
- # imported namespace?
- classDef = dcImports.get(className)
- if classDef is None:
- self.notify.error("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)
- dclass.setOwnerClassDef(classDef)
- self.dclassesByName[className] = dclass
- 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 getServerAddress(self):
- return self._serverAddress
- def connect(self, serverList,
- successCallback = None, successArgs = [],
- failureCallback = None, failureArgs = []):
- """
- Attempts to establish a connection to the server. May return
- before the connection is established. The two callbacks
- represent the two functions to call (and their arguments) on
- success or failure, respectively. The failure callback also
- gets one additional parameter, which will be passed in first:
- the return status code giving reason for failure, if it is
- known.
- """
- ## if self.recorder and self.recorder.isPlaying():
- ## # If we have a recorder and it's already in playback mode,
- ## # don't actually attempt to connect to a gameserver since
- ## # we don't need to. Just let it play back the data.
- ## self.notify.info("Not connecting to gameserver; using playback data instead.")
- ## self.connectHttp = 1
- ## self.tcpConn = SocketStreamRecorder()
- ## self.recorder.addRecorder('gameserver', self.tcpConn)
- ## self.startReaderPollTask()
- ## if successCallback:
- ## successCallback(*successArgs)
- ## return
- hasProxy = 0
- if self.checkHttp():
- proxies = self.http.getProxiesForUrl(serverList[0])
- hasProxy = (proxies != 'DIRECT')
- if hasProxy:
- self.notify.info("Connecting to gameserver via proxy list: %s" % (proxies))
- else:
- self.notify.info("Connecting to gameserver directly (no proxy).")
- #Redefine the connection to http or net in the default case
- self.bootedIndex = None
- self.bootedText = None
- if self.connectMethod == self.CM_HTTP:
- # In the HTTP case, we can't just iterate through the list
- # of servers, because each server attempt requires
- # spawning a request and then coming back later to check
- # the success or failure. Instead, we start the ball
- # rolling by calling the connect callback, which will call
- # itself repeatedly until we establish a connection (or
- # run out of servers).
- ch = self.http.makeChannel(0)
- self.httpConnectCallback(
- ch, serverList, 0,
- successCallback, successArgs,
- failureCallback, failureArgs)
- elif self.connectMethod == self.CM_NET or (not hasattr(self,"connectNative")):
- # Try each of the servers in turn.
- for url in serverList:
- self.notify.info("Connecting to %s via NET interface." % (url.cStr()))
- if self.tryConnectNet(url):
- self.startReaderPollTask()
- if successCallback:
- successCallback(*successArgs)
- return
- # Failed to connect.
- if failureCallback:
- failureCallback(0, '', *failureArgs)
- elif self.connectMethod == self.CM_NATIVE:
- for url in serverList:
- self.notify.info("Connecting to %s via Native interface." % (url.cStr()))
- if self.connectNative(url):
- self.startReaderPollTask()
- if successCallback:
- successCallback(*successArgs)
- return
- # Failed to connect.
- if failureCallback:
- failureCallback(0, '', *failureArgs)
- else:
- print "uh oh, we aren't using one of the tri-state CM variables"
- failureCallback(0, '', *failureArgs)
- def disconnect(self):
- """
- Closes the previously-established connection.
- """
- self.notify.info("Closing connection to server.")
- self._serverAddress = ''
- CConnectionRepository.disconnect(self)
- self.stopReaderPollTask()
- def httpConnectCallback(self, ch, serverList, serverIndex,
- successCallback, successArgs,
- failureCallback, failureArgs):
- if ch.isConnectionReady():
- self.setConnectionHttp(ch)
- self._serverAddress = serverList[serverIndex-1]
- ## if self.recorder:
- ## # If we have a recorder, we wrap the connect inside a
- ## # SocketStreamRecorder, which will trap incoming data
- ## # when the recorder is set to record mode. (It will
- ## # also play back data when the recorder is in playback
- ## # mode, but in that case we never get this far in the
- ## # code, since we just create an empty
- ## # SocketStreamRecorder without actually connecting to
- ## # the gameserver.)
- ## stream = SocketStreamRecorder(self.tcpConn, 1)
- ## self.recorder.addRecorder('gameserver', stream)
- ## # In this case, we pass ownership of the original
- ## # connection to the SocketStreamRecorder object.
- ## self.tcpConn.userManagesMemory = 0
- ## self.tcpConn = stream
- self.startReaderPollTask()
- if successCallback:
- successCallback(*successArgs)
- elif serverIndex < len(serverList):
- # No connection yet, but keep trying.
- url = serverList[serverIndex]
- self.notify.info("Connecting to %s via HTTP interface." % (url.cStr()))
- ch.preserveStatus()
- ch.beginConnectTo(DocumentSpec(url))
- ch.spawnTask(name = 'connect-to-server',
- callback = self.httpConnectCallback,
- extraArgs = [ch, serverList, serverIndex + 1,
- successCallback, successArgs,
- failureCallback, failureArgs])
- else:
- # No more servers to try; we have to give up now.
- if failureCallback:
- failureCallback(ch.getStatusCode(), ch.getStatusString(),
- *failureArgs)
- def checkHttp(self):
- # Creates an HTTPClient, if possible, if we don't have one
- # already. This might fail if the OpenSSL library isn't
- # available. Returns the HTTPClient (also self.http), or None
- # if not set.
- if self.http == None:
- try:
- self.http = HTTPClient()
- except:
- pass
- return self.http
- def startReaderPollTask(self):
- print '########## startReaderPollTask'
- # Stop any tasks we are running now
- self.stopReaderPollTask()
- self.accept(CConnectionRepository.getOverflowEventName(),
- self.handleReaderOverflow)
- taskMgr.add(self.readerPollUntilEmpty, self.uniqueName("readerPollTask"),
- priority = self.taskPriority)
- def stopReaderPollTask(self):
- print '########## stopReaderPollTask'
- taskMgr.remove(self.uniqueName("readerPollTask"))
- self.ignore(CConnectionRepository.getOverflowEventName())
- def readerPollUntilEmpty(self, task):
- while self.readerPollOnce():
- pass
- return Task.cont
- def readerPollOnce(self):
- if self.checkDatagram():
- self.getDatagramIterator(self.private__di)
- self.handleDatagram(self.private__di)
- return 1
- # Unable to receive a datagram: did we lose the connection?
- if not self.isConnected():
- self.stopReaderPollTask()
- self.lostConnection()
- return 0
- def handleReaderOverflow(self):
- # this is called if the incoming-datagram queue overflowed and
- # we lost some data. Override and handle if desired.
- pass
- def lostConnection(self):
- # This should be overrided by a derived class to handle an
- # unexpectedly lost connection to the gameserver.
- self.notify.warning("Lost connection to gameserver.")
- def handleDatagram(self, di):
- # This class is meant to be pure virtual, and any classes that
- # inherit from it need to make their own handleDatagram method
- pass
- def send(self, datagram):
- # Zero-length datagrams might freak out the server. No point
- # in sending them, anyway.
- if datagram.getLength() > 0:
- if ConnectionRepository.notify.getDebug():
- print "ConnectionRepository sending datagram:"
- datagram.dumpHex(ostream)
- self.sendDatagram(datagram)
- # debugging funcs for simulating a network-plug-pull
- def pullNetworkPlug(self):
- self.notify.warning('*** SIMULATING A NETWORK-PLUG-PULL ***')
- self.setSimulatedDisconnect(1)
- def networkPlugPulled(self):
- return self.getSimulatedDisconnect()
- def restoreNetworkPlug(self):
- if self.networkPlugPulled():
- self.notify.info('*** RESTORING SIMULATED PULLED-NETWORK-PLUG ***')
- self.setSimulatedDisconnect(0)
|