DoInterestManager.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. """
  2. The DoInterestManager keeps track of which parent/zones that we currently
  3. have interest in. When you want to "look" into a zone you add an interest
  4. to that zone. When you want to get rid of, or ignore, the objects in that
  5. zone, remove interest in that zone.
  6. p.s. A great deal of this code is just code moved from ClientRepository.py.
  7. """
  8. from pandac.PandaModules import *
  9. from MsgTypes import *
  10. from direct.showbase.PythonUtil import *
  11. from direct.showbase import DirectObject
  12. from PyDatagram import PyDatagram
  13. from direct.directnotify.DirectNotifyGlobal import directNotify
  14. import types
  15. from direct.showbase.PythonUtil import report
  16. class InterestState:
  17. StateActive = 'Active'
  18. StatePendingDel = 'PendingDel'
  19. def __init__(self, desc, state, context, event, parentId, zoneIdList,
  20. eventCounter, auto=False):
  21. self.desc = desc
  22. self.state = state
  23. self.context = context
  24. # We must be ready to keep track of multiple events. If somebody
  25. # requested an interest to be removed and we get a second request
  26. # for removal of the same interest before we get a response for the
  27. # first interest removal, we now have two parts of the codebase
  28. # waiting for a response on the removal of a single interest.
  29. self.events = []
  30. self.eventCounter = eventCounter
  31. if event is not None:
  32. self.addEvent(event)
  33. self.parentId = parentId
  34. self.zoneIdList = zoneIdList
  35. self.auto = auto
  36. def addEvent(self, event):
  37. self.events.append(event)
  38. self.eventCounter.num += 1
  39. def getEvents(self):
  40. return list(self.events)
  41. def clearEvents(self):
  42. self.eventCounter.num -= len(self.events)
  43. assert self.eventCounter.num >= 0
  44. self.events = []
  45. def sendEvents(self):
  46. for event in self.events:
  47. messenger.send(event)
  48. self.clearEvents()
  49. def setDesc(self, desc):
  50. self.desc = desc
  51. def isPendingDelete(self):
  52. return self.state == InterestState.StatePendingDel
  53. def __repr__(self):
  54. return 'InterestState(desc=%s, state=%s, context=%s, event=%s, parentId=%s, zoneIdList=%s)' % (
  55. self.desc, self.state, self.context, self.events, self.parentId, self.zoneIdList)
  56. class InterestHandle:
  57. """This class helps to ensure that valid handles get passed in to DoInterestManager funcs"""
  58. def __init__(self, id):
  59. self._id = id
  60. def asInt(self):
  61. return self._id
  62. def __eq__(self, other):
  63. if type(self) == type(other):
  64. return self._id == other._id
  65. return self._id == other
  66. def __repr__(self):
  67. return '%s(%s)' % (self.__class__.__name__, self._id)
  68. # context value for interest changes that have no complete event
  69. NO_CONTEXT = 0
  70. class DoInterestManager(DirectObject.DirectObject):
  71. """
  72. Top level Interest Manager
  73. """
  74. notify = directNotify.newCategory("DoInterestManager")
  75. try:
  76. tempbase = base
  77. except:
  78. tempbase = simbase
  79. InterestDebug = tempbase.config.GetBool('interest-debug', False)
  80. del tempbase
  81. # 'handle' is a number that represents a single interest set that the
  82. # client has requested; the interest set may be modified
  83. _HandleSerialNum = 0
  84. # high bit is reserved for server interests
  85. _HandleMask = 0x7FFF
  86. # 'context' refers to a single request to change an interest set
  87. _ContextIdSerialNum = 100
  88. _ContextIdMask = 0x3FFFFFFF # avoid making Python create a long
  89. _interests = {}
  90. if __debug__:
  91. _debug_interestHistory = []
  92. _debug_maxDescriptionLen = 35
  93. _SerialGen = SerialNumGen()
  94. _SerialNum = serialNum()
  95. def __init__(self):
  96. assert DoInterestManager.notify.debugCall()
  97. DirectObject.DirectObject.__init__(self)
  98. self._addInterestEvent = uniqueName('DoInterestManager-Add')
  99. self._removeInterestEvent = uniqueName('DoInterestManager-Remove')
  100. self._noNewInterests = False
  101. self._completeDelayedCallback = None
  102. # keep track of request contexts that have not completed
  103. self._outstandingContexts = set()
  104. self._completeEventCount = ScratchPad(num=0)
  105. self._allInterestsCompleteCallbacks = []
  106. def __verbose(self):
  107. return self.InterestDebug or self.getVerbose()
  108. def _getAnonymousEvent(self, desc):
  109. return 'anonymous-%s-%s' % (desc, DoInterestManager._SerialGen.next())
  110. def setNoNewInterests(self, flag):
  111. self._noNewInterests = flag
  112. def noNewInterests(self):
  113. return self._noNewInterests
  114. def setAllInterestsCompleteCallback(self, callback):
  115. if ((self._completeEventCount.num == 0) and
  116. (self._completeDelayedCallback is None)):
  117. callback()
  118. else:
  119. self._allInterestsCompleteCallbacks.append(callback)
  120. def getAllInterestsCompleteEvent(self):
  121. return 'allInterestsComplete-%s' % DoInterestManager._SerialNum
  122. def resetInterestStateForConnectionLoss(self):
  123. DoInterestManager._interests.clear()
  124. self._outstandingContexts = set()
  125. self._completeEventCount = ScratchPad(num=0)
  126. if __debug__:
  127. self._addDebugInterestHistory("RESET", "", 0, 0, 0, [])
  128. def isValidInterestHandle(self, handle):
  129. # pass in a handle (or anything else) and this will return true if it is
  130. # still a valid interest handle
  131. if not isinstance(handle, InterestHandle):
  132. return False
  133. return DoInterestManager._interests.has_key(handle.asInt())
  134. def updateInterestDescription(self, handle, desc):
  135. iState = DoInterestManager._interests.get(handle.asInt())
  136. if iState:
  137. iState.setDesc(desc)
  138. def addInterest(self, parentId, zoneIdList, description, event=None, auto=False):
  139. """
  140. Look into a (set of) zone(s).
  141. """
  142. assert DoInterestManager.notify.debugCall()
  143. #assert not self._noNewInterests
  144. handle = self._getNextHandle()
  145. if self._noNewInterests:
  146. DoInterestManager.notify.warning(
  147. "addInterest: addingInterests on delete: %s" % (handle))
  148. return
  149. # make sure we've got parenting rules set in the DC
  150. if parentId not in (self.getGameDoId(),):
  151. parent = self.getDo(parentId)
  152. if not parent:
  153. DoInterestManager.notify.error(
  154. 'addInterest: attempting to add interest under unknown object %s' % parentId)
  155. else:
  156. if not parent.hasParentingRules():
  157. DoInterestManager.notify.error(
  158. 'addInterest: no setParentingRules defined in the DC for object %s (%s)'
  159. '' % (parentId, parent.__class__.__name__))
  160. if auto:
  161. contextId = 0
  162. event = None
  163. else:
  164. contextId = self._getNextContextId()
  165. self._outstandingContexts.add(contextId)
  166. if event is None:
  167. event = self._getAnonymousEvent('addInterest')
  168. DoInterestManager._interests[handle] = InterestState(
  169. description, InterestState.StateActive, contextId, event, parentId, zoneIdList, self._completeEventCount)
  170. if self.__verbose():
  171. print 'CR::INTEREST.addInterest(handle=%s, parentId=%s, zoneIdList=%s, description=%s, event=%s, auto=%s)' % (
  172. handle, parentId, zoneIdList, description, event, auto)
  173. if not auto:
  174. self._sendAddInterest(handle, contextId, parentId, zoneIdList, description)
  175. if event:
  176. messenger.send(self._getAddInterestEvent(), [event])
  177. assert self.printInterestsIfDebug()
  178. return InterestHandle(handle)
  179. def removeInterest(self, handle, event=None, auto=False):
  180. """
  181. Stop looking in a (set of) zone(s)
  182. """
  183. assert DoInterestManager.notify.debugCall()
  184. assert isinstance(handle, InterestHandle)
  185. existed = False
  186. if ((not auto) and (event is None)):
  187. event = self._getAnonymousEvent('removeInterest')
  188. handle = handle.asInt()
  189. if DoInterestManager._interests.has_key(handle):
  190. existed = True
  191. intState = DoInterestManager._interests[handle]
  192. if event:
  193. messenger.send(self._getRemoveInterestEvent(),
  194. [event, intState.parentId, intState.zoneIdList])
  195. if intState.isPendingDelete():
  196. self.notify.warning(
  197. 'removeInterest: interest %s already pending removal' %
  198. handle)
  199. # this interest is already pending delete, so let's just tack this
  200. # callback onto the list
  201. if event is not None:
  202. intState.addEvent(event)
  203. else:
  204. if len(intState.events) > 0:
  205. # we're not pending a removal, but we have outstanding events?
  206. # probably we are waiting for an add/alter complete.
  207. # should we send those events now?
  208. self.notify.warning('removeInterest: abandoning events: %s' %
  209. intState.events)
  210. intState.clearEvents()
  211. intState.state = InterestState.StatePendingDel
  212. if auto:
  213. self._considerRemoveInterest(handle)
  214. else:
  215. contextId = self._getNextContextId()
  216. intState.context = contextId
  217. if event is not None:
  218. intState.addEvent(event)
  219. self._sendRemoveInterest(handle, contextId)
  220. if event is None:
  221. self._considerRemoveInterest(handle)
  222. if self.__verbose():
  223. print 'CR::INTEREST.removeInterest(handle=%s, event=%s, auto=%s)' % (
  224. handle, event, auto)
  225. else:
  226. DoInterestManager.notify.warning(
  227. "removeInterest: handle not found: %s" % (handle))
  228. assert self.printInterestsIfDebug()
  229. return existed
  230. def alterInterest(self, handle, parentId, zoneIdList, description=None,
  231. event=None):
  232. """
  233. Removes old interests and adds new interests.
  234. Note that when an interest is changed, only the most recent
  235. change's event will be triggered. Previous events are abandoned.
  236. If this is a problem, consider opening multiple interests.
  237. """
  238. assert DoInterestManager.notify.debugCall()
  239. assert isinstance(handle, InterestHandle)
  240. #assert not self._noNewInterests
  241. handle = handle.asInt()
  242. if self._noNewInterests:
  243. DoInterestManager.notify.warning(
  244. "alterInterest: addingInterests on delete: %s" % (handle))
  245. return
  246. exists = False
  247. if event is None:
  248. event = self._getAnonymousEvent('alterInterest')
  249. if DoInterestManager._interests.has_key(handle):
  250. if description is not None:
  251. DoInterestManager._interests[handle].desc = description
  252. else:
  253. description = DoInterestManager._interests[handle].desc
  254. # are we overriding an existing change?
  255. if DoInterestManager._interests[handle].context != NO_CONTEXT:
  256. DoInterestManager._interests[handle].clearEvents()
  257. contextId = self._getNextContextId()
  258. DoInterestManager._interests[handle].context = contextId
  259. DoInterestManager._interests[handle].parentId = parentId
  260. DoInterestManager._interests[handle].zoneIdList = zoneIdList
  261. DoInterestManager._interests[handle].addEvent(event)
  262. if self.__verbose():
  263. print 'CR::INTEREST.alterInterest(handle=%s, parentId=%s, zoneIdList=%s, description=%s, event=%s)' % (
  264. handle, parentId, zoneIdList, description, event)
  265. self._sendAddInterest(handle, contextId, parentId, zoneIdList, description, action='modify')
  266. exists = True
  267. assert self.printInterestsIfDebug()
  268. else:
  269. DoInterestManager.notify.warning(
  270. "alterInterest: handle not found: %s" % (handle))
  271. return exists
  272. def openAutoInterests(self, obj):
  273. if hasattr(obj, '_autoInterestHandle'):
  274. # must be multiple inheritance
  275. self.notify.debug('openAutoInterests(%s): interests already open' % obj.__class__.__name__)
  276. return
  277. autoInterests = obj.getAutoInterests()
  278. obj._autoInterestHandle = None
  279. if not len(autoInterests):
  280. return
  281. obj._autoInterestHandle = self.addInterest(obj.doId, autoInterests, '%s-autoInterest' % obj.__class__.__name__, auto=True)
  282. def closeAutoInterests(self, obj):
  283. if not hasattr(obj, '_autoInterestHandle'):
  284. # must be multiple inheritance
  285. self.notify.debug('closeAutoInterests(%s): interests already closed' % obj)
  286. return
  287. if obj._autoInterestHandle is not None:
  288. self.removeInterest(obj._autoInterestHandle, auto=True)
  289. del obj._autoInterestHandle
  290. # events for InterestWatcher
  291. def _getAddInterestEvent(self):
  292. return self._addInterestEvent
  293. def _getRemoveInterestEvent(self):
  294. return self._removeInterestEvent
  295. def _getInterestState(self, handle):
  296. return DoInterestManager._interests[handle]
  297. def _getNextHandle(self):
  298. handle = DoInterestManager._HandleSerialNum
  299. while True:
  300. handle = (handle + 1) & DoInterestManager._HandleMask
  301. # skip handles that are already in use
  302. if handle not in DoInterestManager._interests:
  303. break
  304. DoInterestManager.notify.warning(
  305. 'interest %s already in use' % handle)
  306. DoInterestManager._HandleSerialNum = handle
  307. return DoInterestManager._HandleSerialNum
  308. def _getNextContextId(self):
  309. contextId = DoInterestManager._ContextIdSerialNum
  310. while True:
  311. contextId = (contextId + 1) & DoInterestManager._ContextIdMask
  312. # skip over the 'no context' id
  313. if contextId != NO_CONTEXT:
  314. break
  315. DoInterestManager._ContextIdSerialNum = contextId
  316. return DoInterestManager._ContextIdSerialNum
  317. def _considerRemoveInterest(self, handle):
  318. """
  319. Consider whether we should cull the interest set.
  320. """
  321. assert DoInterestManager.notify.debugCall()
  322. if DoInterestManager._interests.has_key(handle):
  323. if DoInterestManager._interests[handle].isPendingDelete():
  324. # make sure there is no pending event for this interest
  325. if DoInterestManager._interests[handle].context == NO_CONTEXT:
  326. assert len(DoInterestManager._interests[handle].events) == 0
  327. del DoInterestManager._interests[handle]
  328. if __debug__:
  329. def printInterestsIfDebug(self):
  330. if DoInterestManager.notify.getDebug():
  331. self.printInterests()
  332. return 1 # for assert
  333. def _addDebugInterestHistory(self, action, description, handle,
  334. contextId, parentId, zoneIdList):
  335. if description is None:
  336. description = ''
  337. DoInterestManager._debug_interestHistory.append(
  338. (action, description, handle, contextId, parentId, zoneIdList))
  339. DoInterestManager._debug_maxDescriptionLen = max(
  340. DoInterestManager._debug_maxDescriptionLen, len(description))
  341. def printInterestHistory(self):
  342. print "***************** Interest History *************"
  343. format = '%9s %' + str(DoInterestManager._debug_maxDescriptionLen) + 's %6s %6s %9s %s'
  344. print format % (
  345. "Action", "Description", "Handle", "Context", "ParentId",
  346. "ZoneIdList")
  347. for i in DoInterestManager._debug_interestHistory:
  348. print format % tuple(i)
  349. print "Note: interests with a Context of 0 do not get" \
  350. " done/finished notices."
  351. def printInterestSets(self):
  352. print "******************* Interest Sets **************"
  353. format = '%6s %' + str(DoInterestManager._debug_maxDescriptionLen) + 's %10s %5s %9s %9s %10s'
  354. print format % (
  355. "Handle", "Description", "State", "Context",
  356. "ParentId", "ZoneIdList", "Event")
  357. for id, state in DoInterestManager._interests.items():
  358. if len(state.events) == 0:
  359. event = ''
  360. elif len(state.events) == 1:
  361. event = state.events[0]
  362. else:
  363. event = state.events
  364. print format % (id, state.desc, state.state, state.context,
  365. state.parentId, state.zoneIdList, event)
  366. print "************************************************"
  367. def printInterests(self):
  368. self.printInterestHistory()
  369. self.printInterestSets()
  370. def _sendAddInterest(self, handle, contextId, parentId, zoneIdList, description,
  371. action=None):
  372. """
  373. Part of the new otp-server code.
  374. handle is a client-side created number that refers to
  375. a set of interests. The same handle number doesn't
  376. necessarily have any relationship to the same handle
  377. on another client.
  378. """
  379. assert DoInterestManager.notify.debugCall()
  380. if __debug__:
  381. if isinstance(zoneIdList, types.ListType):
  382. zoneIdList.sort()
  383. if action is None:
  384. action = 'add'
  385. self._addDebugInterestHistory(
  386. action, description, handle, contextId, parentId, zoneIdList)
  387. if parentId == 0:
  388. DoInterestManager.notify.error(
  389. 'trying to set interest to invalid parent: %s' % parentId)
  390. datagram = PyDatagram()
  391. # Add message type
  392. datagram.addUint16(CLIENT_ADD_INTEREST)
  393. datagram.addUint16(handle)
  394. datagram.addUint32(contextId)
  395. datagram.addUint32(parentId)
  396. if isinstance(zoneIdList, types.ListType):
  397. vzl = list(zoneIdList)
  398. vzl.sort()
  399. uniqueElements(vzl)
  400. for zone in vzl:
  401. datagram.addUint32(zone)
  402. else:
  403. datagram.addUint32(zoneIdList)
  404. self.send(datagram)
  405. def _sendRemoveInterest(self, handle, contextId):
  406. """
  407. handle is a client-side created number that refers to
  408. a set of interests. The same handle number doesn't
  409. necessarily have any relationship to the same handle
  410. on another client.
  411. """
  412. assert DoInterestManager.notify.debugCall()
  413. assert handle in DoInterestManager._interests
  414. datagram = PyDatagram()
  415. # Add message type
  416. datagram.addUint16(CLIENT_REMOVE_INTEREST)
  417. datagram.addUint16(handle)
  418. if contextId != 0:
  419. datagram.addUint32(contextId)
  420. self.send(datagram)
  421. if __debug__:
  422. state = DoInterestManager._interests[handle]
  423. self._addDebugInterestHistory(
  424. "remove", state.desc, handle, contextId,
  425. state.parentId, state.zoneIdList)
  426. def cleanupWaitAllInterestsClosed(self):
  427. if self._completeDelayedCallback is not None:
  428. self._completeDelayedCallback.destroy()
  429. self._completeDelayedCallback = None
  430. def handleInterestDoneMessage(self, di):
  431. """
  432. This handles the interest done messages and may dispatch an event
  433. """
  434. assert DoInterestManager.notify.debugCall()
  435. handle = di.getUint16()
  436. contextId = di.getUint32()
  437. if self.__verbose():
  438. print 'CR::INTEREST.interestDone(handle=%s)' % handle
  439. DoInterestManager.notify.debug(
  440. "handleInterestDoneMessage--> Received handle %s, context %s" % (
  441. handle, contextId))
  442. eventsToSend = []
  443. # if the context matches, send out the event
  444. if contextId == DoInterestManager._interests[handle].context:
  445. DoInterestManager._interests[handle].context = NO_CONTEXT
  446. # the event handlers may call back into the interest manager. Send out
  447. # the events after we're once again in a stable state.
  448. #DoInterestManager._interests[handle].sendEvents()
  449. eventsToSend = list(DoInterestManager._interests[handle].getEvents())
  450. DoInterestManager._interests[handle].clearEvents()
  451. else:
  452. DoInterestManager.notify.debug(
  453. "handleInterestDoneMessage--> handle: %s: Expecting context %s, got %s" % (
  454. handle, DoInterestManager._interests[handle].context, contextId))
  455. if __debug__:
  456. state = DoInterestManager._interests[handle]
  457. self._addDebugInterestHistory(
  458. "finished", state.desc, handle, contextId, state.parentId,
  459. state.zoneIdList)
  460. self._considerRemoveInterest(handle)
  461. for event in eventsToSend:
  462. messenger.send(event)
  463. # if there are no more outstanding interest-completes, send out global all-done event
  464. if self._completeEventCount.num == 0:
  465. # wait for 3 frames, if no new interests, send out all-done event
  466. def checkMoreInterests():
  467. return self._completeEventCount.num > 0
  468. def sendEvent():
  469. messenger.send(self.getAllInterestsCompleteEvent())
  470. for callback in self._allInterestsCompleteCallbacks:
  471. callback()
  472. self._allInterestsCompleteCallbacks = []
  473. self.cleanupWaitAllInterestsClosed()
  474. self._completeDelayedCallback = FrameDelayedCall(
  475. 'waitForAllInterestCompletes',
  476. callback=sendEvent,
  477. frames=3,
  478. cancelFunc=checkMoreInterests)
  479. checkMoreInterests = None
  480. sendEvent = None
  481. assert self.printInterestsIfDebug()
  482. if __debug__:
  483. import unittest
  484. class AsyncTestCase(unittest.TestCase):
  485. def setCompleted(self):
  486. self._async_completed = True
  487. def isCompleted(self):
  488. return getattr(self, '_async_completed', False)
  489. class AsyncTestSuite(unittest.TestSuite):
  490. pass
  491. class AsyncTestLoader(unittest.TestLoader):
  492. suiteClass = AsyncTestSuite
  493. class AsyncTextTestRunner(unittest.TextTestRunner):
  494. def run(self, testCase):
  495. result = self._makeResult()
  496. startTime = time.time()
  497. test(result)
  498. stopTime = time.time()
  499. timeTaken = stopTime - startTime
  500. result.printErrors()
  501. self.stream.writeln(result.separator2)
  502. run = result.testsRun
  503. self.stream.writeln("Ran %d test%s in %.3fs" %
  504. (run, run != 1 and "s" or "", timeTaken))
  505. self.stream.writeln()
  506. if not result.wasSuccessful():
  507. self.stream.write("FAILED (")
  508. failed, errored = map(len, (result.failures, result.errors))
  509. if failed:
  510. self.stream.write("failures=%d" % failed)
  511. if errored:
  512. if failed: self.stream.write(", ")
  513. self.stream.write("errors=%d" % errored)
  514. self.stream.writeln(")")
  515. else:
  516. self.stream.writeln("OK")
  517. return result
  518. class TestInterestAddRemove(AsyncTestCase, DirectObject.DirectObject):
  519. def testInterestAdd(self):
  520. event = uniqueName('InterestAdd')
  521. self.acceptOnce(event, self.gotInterestAddResponse)
  522. self.handle = base.cr.addInterest(base.cr.GameGlobalsId, 100, 'TestInterest', event=event)
  523. def gotInterestAddResponse(self):
  524. event = uniqueName('InterestRemove')
  525. self.acceptOnce(event, self.gotInterestRemoveResponse)
  526. base.cr.removeInterest(self.handle, event=event)
  527. def gotInterestRemoveResponse(self):
  528. self.setCompleted()
  529. def runTests():
  530. suite = unittest.makeSuite(TestInterestAddRemove)
  531. unittest.AsyncTextTestRunner(verbosity=2).run(suite)