ConnectionRepository.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. from panda3d.core import DocumentSpec, Filename, HTTPClient, VirtualFileSystem, getModelPath
  2. from panda3d.direct import CConnectionRepository, DCPacker
  3. from direct.task import Task
  4. from direct.task.TaskManagerGlobal import taskMgr
  5. from direct.directnotify.DirectNotifyGlobal import directNotify
  6. from direct.distributed.DoInterestManager import DoInterestManager
  7. from direct.distributed.DoCollectionManager import DoCollectionManager
  8. from direct.showbase import GarbageReport
  9. from direct.showbase.MessengerGlobal import messenger
  10. from .PyDatagramIterator import PyDatagramIterator
  11. import gc
  12. __all__ = ["ConnectionRepository", "GCTrigger"]
  13. class ConnectionRepository(
  14. DoInterestManager, DoCollectionManager, CConnectionRepository):
  15. """
  16. This is a base class for things that know how to establish a
  17. connection (and exchange datagrams) with a gameserver. This
  18. includes ClientRepository and AIRepository.
  19. """
  20. notify = directNotify.newCategory("ConnectionRepository")
  21. taskPriority = -30
  22. taskChain = None
  23. CM_HTTP=0
  24. CM_NET=1
  25. CM_NATIVE=2
  26. gcNotify = directNotify.newCategory("GarbageCollect")
  27. GarbageCollectTaskName = "allowGarbageCollect"
  28. GarbageThresholdTaskName = "adjustGarbageCollectThreshold"
  29. def __init__(self, connectMethod, config, hasOwnerView = False,
  30. threadedNet = None):
  31. assert self.notify.debugCall()
  32. if threadedNet is None:
  33. # Default value.
  34. threadedNet = config.GetBool('threaded-net', False)
  35. # let the C connection repository know whether we're supporting
  36. # 'owner' views of distributed objects (i.e. 'receives ownrecv',
  37. # 'I own this object and have a separate view of it regardless of
  38. # where it currently is located')
  39. CConnectionRepository.__init__(self, hasOwnerView, threadedNet)
  40. self.setWantMessageBundling(config.GetBool('want-message-bundling', 1))
  41. # DoInterestManager.__init__ relies on CConnectionRepository being
  42. # initialized
  43. DoInterestManager.__init__(self)
  44. DoCollectionManager.__init__(self)
  45. self.setPythonRepository(self)
  46. # Create a unique ID number for each ConnectionRepository in
  47. # the world, helpful for sending messages specific to each one.
  48. self.uniqueId = hash(self)
  49. # Accept this hook so that we can respond to lost-connection
  50. # events in the main thread, instead of within the network
  51. # thread (if there is one).
  52. self.accept(self._getLostConnectionEvent(), self.lostConnection)
  53. self.config = config
  54. if self.config.GetBool('verbose-repository'):
  55. self.setVerbose(1)
  56. # Set this to 'http' to establish a connection to the server
  57. # using the HTTPClient interface, which ultimately uses the
  58. # OpenSSL socket library (even though SSL is not involved).
  59. # This is not as robust a socket library as NET's, but the
  60. # HTTPClient interface does a good job of negotiating the
  61. # connection over an HTTP proxy if one is in use.
  62. #
  63. # Set it to 'net' to use Panda's net interface
  64. # (e.g. QueuedConnectionManager, etc.) to establish the
  65. # connection. This is a higher-level layer build on top of
  66. # the low-level "native net" library. There is no support for
  67. # proxies. This is a good, general choice.
  68. #
  69. # Set it to 'native' to use Panda's low-level native net
  70. # interface directly. This is much faster than either http or
  71. # net for high-bandwidth (e.g. server) applications, but it
  72. # doesn't support the simulated delay via the start_delay()
  73. # call.
  74. #
  75. # Set it to 'default' to use an appropriate interface
  76. # according to the type of ConnectionRepository we are
  77. # creating.
  78. userConnectMethod = self.config.GetString('connect-method', 'default')
  79. if userConnectMethod == 'http':
  80. connectMethod = self.CM_HTTP
  81. elif userConnectMethod == 'net':
  82. connectMethod = self.CM_NET
  83. elif userConnectMethod == 'native':
  84. connectMethod = self.CM_NATIVE
  85. self.connectMethod = connectMethod
  86. if self.connectMethod == self.CM_HTTP:
  87. self.notify.info("Using connect method 'http'")
  88. elif self.connectMethod == self.CM_NET:
  89. self.notify.info("Using connect method 'net'")
  90. elif self.connectMethod == self.CM_NATIVE:
  91. self.notify.info("Using connect method 'native'")
  92. self.connectHttp = None
  93. self.http = None
  94. # This DatagramIterator is constructed once, and then re-used
  95. # each time we read a datagram.
  96. self.private__di = PyDatagramIterator()
  97. self.recorder = None
  98. self.readerPollTaskObj = None
  99. # This is the string that is appended to symbols read from the
  100. # DC file. The AIRepository will redefine this to 'AI'.
  101. self.dcSuffix = ''
  102. self._serverAddress = ''
  103. if self.config.GetBool('gc-save-all', 0):
  104. # set gc to preserve every object involved in a cycle, even ones that
  105. # would normally be freed automatically during garbage collect
  106. # allows us to find and fix these cycles, reducing or eliminating the
  107. # need to run garbage collects
  108. # garbage collection CPU usage is O(n), n = number of Python objects
  109. gc.set_debug(gc.DEBUG_SAVEALL)
  110. if self.config.GetBool('want-garbage-collect-task', 1):
  111. # manual garbage-collect task
  112. taskMgr.add(self._garbageCollect, self.GarbageCollectTaskName, 200)
  113. # periodically increase gc threshold if there is no garbage
  114. taskMgr.doMethodLater(self.config.GetFloat('garbage-threshold-adjust-delay', 5 * 60.),
  115. self._adjustGcThreshold, self.GarbageThresholdTaskName)
  116. self._gcDefaultThreshold = gc.get_threshold()
  117. def _getLostConnectionEvent(self):
  118. return self.uniqueName('lostConnection')
  119. def _garbageCollect(self, task=None):
  120. # allow a collect
  121. # enable automatic garbage collection
  122. gc.enable()
  123. # creating an object with gc enabled causes garbage collection to trigger if appropriate
  124. gct = GCTrigger()
  125. # disable the automatic garbage collect during the rest of the frame
  126. gc.disable()
  127. return Task.cont
  128. def _adjustGcThreshold(self, task):
  129. # do an unconditional collect to make sure gc.garbage has a chance to be
  130. # populated before we start increasing the auto-collect threshold
  131. # don't distribute the leak check from the client to the AI, they both
  132. # do these garbage checks independently over time
  133. numGarbage = GarbageReport.checkForGarbageLeaks()
  134. if numGarbage == 0:
  135. self.gcNotify.debug('no garbage found, doubling gc threshold')
  136. a, b, c = gc.get_threshold()
  137. gc.set_threshold(min(a * 2, 1 << 30), b, c)
  138. task.delayTime = task.delayTime * 2
  139. retVal = Task.again
  140. else:
  141. self.gcNotify.warning('garbage found, reverting gc threshold')
  142. # the process is producing garbage, stick to the default collection threshold
  143. gc.set_threshold(*self._gcDefaultThreshold)
  144. retVal = Task.done
  145. return retVal
  146. def generateGlobalObject(self, doId, dcname, values=None):
  147. # Look up the dclass
  148. dclass = self.dclassesByName.get(dcname+self.dcSuffix)
  149. if dclass is None:
  150. #print "\n\n\nNeed to define", dcname+self.dcSuffix
  151. self.notify.warning("Need to define %s" % (dcname+self.dcSuffix))
  152. dclass = self.dclassesByName.get(dcname+'AI')
  153. if dclass is None:
  154. dclass = self.dclassesByName.get(dcname)
  155. # Create a new distributed object, and put it in the dictionary
  156. #distObj = self.generateWithRequiredFields(dclass, doId, di)
  157. # Construct a new one
  158. classDef = dclass.getClassDef()
  159. if classDef is None:
  160. self.notify.error("Could not create an undefined %s object."%(
  161. dclass.getName()))
  162. distObj = classDef(self)
  163. distObj.dclass = dclass
  164. # Assign it an Id
  165. distObj.doId = doId
  166. # Put the new do in the dictionary
  167. self.doId2do[doId] = distObj
  168. # Update the required fields
  169. distObj.generateInit() # Only called when constructed
  170. distObj.generate()
  171. if values is not None:
  172. for i in range(dclass.getNumInheritedFields()):
  173. field = dclass.getInheritedField(i)
  174. if field.asMolecularField() is None:
  175. value = values.get(field.getName(), None)
  176. if value is None and field.isRequired():
  177. # Gee, this could be better. What would really be
  178. # nicer is to get value from field.getDefaultValue
  179. # or similar, but that returns a binary string, not
  180. # a python tuple, like the following does. If you
  181. # want to change something better, please go ahead.
  182. packer = DCPacker()
  183. packer.beginPack(field)
  184. packer.packDefaultValue()
  185. packer.endPack()
  186. unpacker = DCPacker()
  187. unpacker.setUnpackData(packer.getString())
  188. unpacker.beginUnpack(field)
  189. value = unpacker.unpackObject()
  190. unpacker.endUnpack()
  191. if value is not None:
  192. function = getattr(distObj, field.getName())
  193. if function is not None:
  194. function(*value)
  195. else:
  196. self.notify.error(
  197. "\n\n\nNot able to find %s.%s" % (
  198. distObj.__class__.__name__,
  199. field.getName()
  200. )
  201. )
  202. distObj.announceGenerate()
  203. distObj.parentId = 0
  204. distObj.zoneId = 0
  205. # updateRequiredFields calls announceGenerate
  206. return distObj
  207. def readDCFile(self, dcFileNames = None):
  208. """
  209. Reads in the dc files listed in dcFileNames, or if
  210. dcFileNames is None, reads in all of the dc files listed in
  211. the Config.prc file.
  212. """
  213. dcFile = self.getDcFile()
  214. dcFile.clear()
  215. self.dclassesByName = {}
  216. self.dclassesByNumber = {}
  217. self.hashVal = 0
  218. if isinstance(dcFileNames, str):
  219. # If we were given a single string, make it a list.
  220. dcFileNames = [dcFileNames]
  221. dcImports = {}
  222. if dcFileNames is None:
  223. readResult = dcFile.readAll()
  224. if not readResult:
  225. self.notify.error("Could not read dc file.")
  226. else:
  227. searchPath = getModelPath().getValue()
  228. for dcFileName in dcFileNames:
  229. pathname = Filename(dcFileName)
  230. vfs = VirtualFileSystem.getGlobalPtr()
  231. vfs.resolveFilename(pathname, searchPath)
  232. readResult = dcFile.read(pathname)
  233. if not readResult:
  234. self.notify.error("Could not read dc file: %s" % (pathname))
  235. #if not dcFile.allObjectsValid():
  236. # names = []
  237. # for i in range(dcFile.getNumTypedefs()):
  238. # td = dcFile.getTypedef(i)
  239. # if td.isBogusTypedef():
  240. # names.append(td.getName())
  241. # nameList = ', '.join(names)
  242. # self.notify.error("Undefined types in DC file: " + nameList)
  243. self.hashVal = dcFile.getHash()
  244. # Now import all of the modules required by the DC file.
  245. for n in range(dcFile.getNumImportModules()):
  246. moduleName = dcFile.getImportModule(n)[:]
  247. # Maybe the module name is represented as "moduleName/AI".
  248. suffix = moduleName.split('/')
  249. moduleName = suffix[0]
  250. suffix=suffix[1:]
  251. if self.dcSuffix in suffix:
  252. moduleName += self.dcSuffix
  253. elif self.dcSuffix == 'UD' and 'AI' in suffix: #HACK:
  254. moduleName += 'AI'
  255. importSymbols = []
  256. for i in range(dcFile.getNumImportSymbols(n)):
  257. symbolName = dcFile.getImportSymbol(n, i)
  258. # Maybe the symbol name is represented as "symbolName/AI".
  259. suffix = symbolName.split('/')
  260. symbolName = suffix[0]
  261. suffix=suffix[1:]
  262. if self.dcSuffix in suffix:
  263. symbolName += self.dcSuffix
  264. elif self.dcSuffix == 'UD' and 'AI' in suffix: #HACK:
  265. symbolName += 'AI'
  266. importSymbols.append(symbolName)
  267. self.importModule(dcImports, moduleName, importSymbols)
  268. # Now get the class definition for the classes named in the DC
  269. # file.
  270. import inspect
  271. for i in range(dcFile.getNumClasses()):
  272. dclass = dcFile.getClass(i)
  273. number = dclass.getNumber()
  274. className = dclass.getName() + self.dcSuffix
  275. # Does the class have a definition defined in the newly
  276. # imported namespace?
  277. classDef = dcImports.get(className)
  278. if classDef is None and self.dcSuffix == 'UD': #HACK:
  279. className = dclass.getName() + 'AI'
  280. classDef = dcImports.get(className)
  281. # Also try it without the dcSuffix.
  282. if classDef is None:
  283. className = dclass.getName()
  284. classDef = dcImports.get(className)
  285. if classDef is None:
  286. self.notify.debug("No class definition for %s." % (className))
  287. else:
  288. if inspect.ismodule(classDef):
  289. if not hasattr(classDef, className):
  290. self.notify.warning("Module %s does not define class %s." % (className, className))
  291. continue
  292. classDef = getattr(classDef, className)
  293. if not inspect.isclass(classDef):
  294. self.notify.error("Symbol %s is not a class name." % (className))
  295. else:
  296. dclass.setClassDef(classDef)
  297. self.dclassesByName[className] = dclass
  298. if number >= 0:
  299. self.dclassesByNumber[number] = dclass
  300. # Owner Views
  301. if self.hasOwnerView():
  302. ownerDcSuffix = self.dcSuffix + 'OV'
  303. # dict of class names (without 'OV') that have owner views
  304. ownerImportSymbols = {}
  305. # Now import all of the modules required by the DC file.
  306. for n in range(dcFile.getNumImportModules()):
  307. moduleName = dcFile.getImportModule(n)
  308. # Maybe the module name is represented as "moduleName/AI".
  309. suffix = moduleName.split('/')
  310. moduleName = suffix[0]
  311. suffix=suffix[1:]
  312. if ownerDcSuffix in suffix:
  313. moduleName = moduleName + ownerDcSuffix
  314. importSymbols = []
  315. for i in range(dcFile.getNumImportSymbols(n)):
  316. symbolName = dcFile.getImportSymbol(n, i)
  317. # Check for the OV suffix
  318. suffix = symbolName.split('/')
  319. symbolName = suffix[0]
  320. suffix=suffix[1:]
  321. if ownerDcSuffix in suffix:
  322. symbolName += ownerDcSuffix
  323. importSymbols.append(symbolName)
  324. ownerImportSymbols[symbolName] = None
  325. self.importModule(dcImports, moduleName, importSymbols)
  326. # Now get the class definition for the owner classes named
  327. # in the DC file.
  328. for i in range(dcFile.getNumClasses()):
  329. dclass = dcFile.getClass(i)
  330. if dclass.getName() + ownerDcSuffix in ownerImportSymbols:
  331. number = dclass.getNumber()
  332. className = dclass.getName() + ownerDcSuffix
  333. # Does the class have a definition defined in the newly
  334. # imported namespace?
  335. classDef = dcImports.get(className)
  336. if classDef is None:
  337. self.notify.error("No class definition for %s." % className)
  338. else:
  339. if inspect.ismodule(classDef):
  340. if not hasattr(classDef, className):
  341. self.notify.error("Module %s does not define class %s." % (className, className))
  342. classDef = getattr(classDef, className)
  343. dclass.setOwnerClassDef(classDef)
  344. self.dclassesByName[className] = dclass
  345. def importModule(self, dcImports, moduleName, importSymbols):
  346. """
  347. Imports the indicated moduleName and all of its symbols
  348. into the current namespace. This more-or-less reimplements
  349. the Python import command.
  350. """
  351. module = __import__(moduleName, globals(), locals(), importSymbols)
  352. if importSymbols:
  353. # "from moduleName import symbolName, symbolName, ..."
  354. # Copy just the named symbols into the dictionary.
  355. if importSymbols == ['*']:
  356. # "from moduleName import *"
  357. if hasattr(module, "__all__"):
  358. importSymbols = module.__all__
  359. else:
  360. importSymbols = module.__dict__.keys()
  361. for symbolName in importSymbols:
  362. if hasattr(module, symbolName):
  363. dcImports[symbolName] = getattr(module, symbolName)
  364. else:
  365. raise Exception('Symbol %s not defined in module %s.' % (symbolName, moduleName))
  366. else:
  367. # "import moduleName"
  368. # Copy the root module name into the dictionary.
  369. # Follow the dotted chain down to the actual module.
  370. components = moduleName.split('.')
  371. dcImports[components[0]] = module
  372. def getServerAddress(self):
  373. return self._serverAddress
  374. def connect(self, serverList,
  375. successCallback = None, successArgs = [],
  376. failureCallback = None, failureArgs = []):
  377. """
  378. Attempts to establish a connection to the server. May return
  379. before the connection is established. The two callbacks
  380. represent the two functions to call (and their arguments) on
  381. success or failure, respectively. The failure callback also
  382. gets one additional parameter, which will be passed in first:
  383. the return status code giving reason for failure, if it is
  384. known.
  385. """
  386. ## if self.recorder and self.recorder.isPlaying():
  387. ## # If we have a recorder and it's already in playback mode,
  388. ## # don't actually attempt to connect to a gameserver since
  389. ## # we don't need to. Just let it play back the data.
  390. ## self.notify.info("Not connecting to gameserver; using playback data instead.")
  391. ## self.connectHttp = 1
  392. ## self.tcpConn = SocketStreamRecorder()
  393. ## self.recorder.addRecorder('gameserver', self.tcpConn)
  394. ## self.startReaderPollTask()
  395. ## if successCallback:
  396. ## successCallback(*successArgs)
  397. ## return
  398. hasProxy = 0
  399. if self.checkHttp():
  400. proxies = self.http.getProxiesForUrl(serverList[0])
  401. hasProxy = proxies != 'DIRECT'
  402. if hasProxy:
  403. self.notify.info("Connecting to gameserver via proxy list: %s" % (proxies))
  404. else:
  405. self.notify.info("Connecting to gameserver directly (no proxy).")
  406. #Redefine the connection to http or net in the default case
  407. self.bootedIndex = None
  408. self.bootedText = None
  409. if self.connectMethod == self.CM_HTTP:
  410. # In the HTTP case, we can't just iterate through the list
  411. # of servers, because each server attempt requires
  412. # spawning a request and then coming back later to check
  413. # the success or failure. Instead, we start the ball
  414. # rolling by calling the connect callback, which will call
  415. # itself repeatedly until we establish a connection (or
  416. # run out of servers).
  417. ch = self.http.makeChannel(0)
  418. self.httpConnectCallback(
  419. ch, serverList, 0,
  420. successCallback, successArgs,
  421. failureCallback, failureArgs)
  422. elif self.connectMethod == self.CM_NET or (not hasattr(self,"connectNative")):
  423. # Try each of the servers in turn.
  424. for url in serverList:
  425. self.notify.info("Connecting to %s via NET interface." % (url))
  426. if self.tryConnectNet(url):
  427. self.startReaderPollTask()
  428. if successCallback:
  429. successCallback(*successArgs)
  430. return
  431. # Failed to connect.
  432. if failureCallback:
  433. failureCallback(0, '', *failureArgs)
  434. elif self.connectMethod == self.CM_NATIVE:
  435. for url in serverList:
  436. self.notify.info("Connecting to %s via Native interface." % (url))
  437. if self.connectNative(url):
  438. self.startReaderPollTask()
  439. if successCallback:
  440. successCallback(*successArgs)
  441. return
  442. # Failed to connect.
  443. if failureCallback:
  444. failureCallback(0, '', *failureArgs)
  445. else:
  446. print("uh oh, we aren't using one of the tri-state CM variables")
  447. failureCallback(0, '', *failureArgs)
  448. def disconnect(self):
  449. """
  450. Closes the previously-established connection.
  451. """
  452. self.notify.info("Closing connection to server.")
  453. self._serverAddress = ''
  454. CConnectionRepository.disconnect(self)
  455. self.stopReaderPollTask()
  456. def shutdown(self):
  457. self.ignoreAll()
  458. CConnectionRepository.shutdown(self)
  459. def httpConnectCallback(self, ch, serverList, serverIndex,
  460. successCallback, successArgs,
  461. failureCallback, failureArgs):
  462. if ch.isConnectionReady():
  463. self.setConnectionHttp(ch)
  464. self._serverAddress = serverList[serverIndex-1]
  465. self.notify.info("Successfully connected to %s." % (self._serverAddress))
  466. ## if self.recorder:
  467. ## # If we have a recorder, we wrap the connect inside a
  468. ## # SocketStreamRecorder, which will trap incoming data
  469. ## # when the recorder is set to record mode. (It will
  470. ## # also play back data when the recorder is in playback
  471. ## # mode, but in that case we never get this far in the
  472. ## # code, since we just create an empty
  473. ## # SocketStreamRecorder without actually connecting to
  474. ## # the gameserver.)
  475. ## stream = SocketStreamRecorder(self.tcpConn, 1)
  476. ## self.recorder.addRecorder('gameserver', stream)
  477. ## # In this case, we pass ownership of the original
  478. ## # connection to the SocketStreamRecorder object.
  479. ## self.tcpConn.userManagesMemory = 0
  480. ## self.tcpConn = stream
  481. self.startReaderPollTask()
  482. if successCallback:
  483. successCallback(*successArgs)
  484. elif serverIndex < len(serverList):
  485. # No connection yet, but keep trying.
  486. url = serverList[serverIndex]
  487. self.notify.info("Connecting to %s via HTTP interface." % (url))
  488. ch.preserveStatus()
  489. ch.beginConnectTo(DocumentSpec(url))
  490. ch.spawnTask(name = 'connect-to-server',
  491. callback = self.httpConnectCallback,
  492. extraArgs = [ch, serverList, serverIndex + 1,
  493. successCallback, successArgs,
  494. failureCallback, failureArgs])
  495. else:
  496. # No more servers to try; we have to give up now.
  497. if failureCallback:
  498. failureCallback(ch.getStatusCode(), ch.getStatusString(),
  499. *failureArgs)
  500. def checkHttp(self):
  501. # Creates an HTTPClient, if possible, if we don't have one
  502. # already. This might fail if the OpenSSL library isn't
  503. # available. Returns the HTTPClient (also self.http), or None
  504. # if not set.
  505. if self.http is None:
  506. try:
  507. self.http = HTTPClient()
  508. except Exception:
  509. pass
  510. return self.http
  511. def startReaderPollTask(self):
  512. # Stop any tasks we are running now
  513. self.stopReaderPollTask()
  514. self.accept(CConnectionRepository.getOverflowEventName(),
  515. self.handleReaderOverflow)
  516. self.readerPollTaskObj = taskMgr.add(
  517. self.readerPollUntilEmpty, self.uniqueName("readerPollTask"),
  518. priority = self.taskPriority, taskChain = self.taskChain)
  519. def stopReaderPollTask(self):
  520. if self.readerPollTaskObj:
  521. taskMgr.remove(self.readerPollTaskObj)
  522. self.readerPollTaskObj = None
  523. self.ignore(CConnectionRepository.getOverflowEventName())
  524. def readerPollUntilEmpty(self, task):
  525. while self.readerPollOnce():
  526. pass
  527. return Task.cont
  528. def readerPollOnce(self):
  529. if self.checkDatagram():
  530. self.getDatagramIterator(self.private__di)
  531. self.handleDatagram(self.private__di)
  532. return 1
  533. # Unable to receive a datagram: did we lose the connection?
  534. if not self.isConnected():
  535. self.stopReaderPollTask()
  536. messenger.send(self.uniqueName('lostConnection'), taskChain = 'default')
  537. return 0
  538. def handleReaderOverflow(self):
  539. # this is called if the incoming-datagram queue overflowed and
  540. # we lost some data. Override and handle if desired.
  541. pass
  542. def lostConnection(self):
  543. # This should be overrided by a derived class to handle an
  544. # unexpectedly lost connection to the gameserver.
  545. self.notify.warning("Lost connection to gameserver.")
  546. def handleDatagram(self, di):
  547. # This class is meant to be pure virtual, and any classes that
  548. # inherit from it need to make their own handleDatagram method
  549. pass
  550. def send(self, datagram):
  551. # Zero-length datagrams might freak out the server. No point
  552. # in sending them, anyway.
  553. if datagram.getLength() > 0:
  554. ## if self.notify.getDebug():
  555. ## print "ConnectionRepository sending datagram:"
  556. ## datagram.dumpHex(ostream)
  557. self.sendDatagram(datagram)
  558. # debugging funcs for simulating a network-plug-pull
  559. def pullNetworkPlug(self):
  560. self.notify.warning('*** SIMULATING A NETWORK-PLUG-PULL ***')
  561. self.setSimulatedDisconnect(1)
  562. def networkPlugPulled(self):
  563. return self.getSimulatedDisconnect()
  564. def restoreNetworkPlug(self):
  565. if self.networkPlugPulled():
  566. self.notify.info('*** RESTORING SIMULATED PULLED-NETWORK-PLUG ***')
  567. self.setSimulatedDisconnect(0)
  568. def uniqueName(self, idString):
  569. return "%s-%s" % (idString, self.uniqueId)
  570. class GCTrigger:
  571. # used to trigger garbage collection
  572. pass