DoInterestManager.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  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. class InterestState:
  15. StateActive = 'Active'
  16. StatePendingDel = 'PendingDel'
  17. def __init__(self, desc, state, scope, event, parentId, zoneIdList):
  18. self.desc = desc
  19. self.state = state
  20. self.scope = scope
  21. # We must be ready to keep track of multiple events. If somebody
  22. # requested an interest to be removed and we get a second request
  23. # for removal of the same interest before we get a response for the
  24. # first interest removal, we now have two parts of the codebase
  25. # waiting for a response on the removal of a single interest.
  26. self.events = [event]
  27. self.parentId = parentId
  28. self.zoneIdList = zoneIdList
  29. def addEvent(self, event):
  30. self.events.append(event)
  31. def getEvents(self):
  32. return list(self.events)
  33. def clearEvents(self):
  34. self.events = []
  35. def sendEvents(self):
  36. for event in self.events:
  37. messenger.send(event)
  38. self.clearEvents()
  39. def isPendingDelete(self):
  40. return self.state == InterestState.StatePendingDel
  41. def __repr__(self):
  42. return 'InterestState(desc=%s, state=%s, scope=%s, event=%s, parentId=%s, zoneIdList=%s)' % (
  43. self.desc, self.state, self.scope, self.events, self.parentId, self.zoneIdList)
  44. # scope value for interest changes that have no complete event
  45. NO_SCOPE = 0
  46. class DoInterestManager(DirectObject.DirectObject):
  47. """
  48. Top level Interest Manager
  49. """
  50. notify = directNotify.newCategory("DoInterestManager")
  51. try:
  52. tempbase = base
  53. except:
  54. tempbase = simbase
  55. InterestDebug = tempbase.config.GetBool('interest-debug', True)
  56. del tempbase
  57. # 'handle' is a number that represents a single interest set that the
  58. # client has requested; the interest set may be modified
  59. _HandleSerialNum = 0
  60. # high bit is reserved for server interests
  61. _HandleMask = 0x7FFF
  62. # 'scope' refers to a single request to change an interest set
  63. _ScopeIdSerialNum = 100
  64. _ScopeIdMask = 0x3FFFFFFF # avoid making Python create a long
  65. _interests = {}
  66. if __debug__:
  67. _debug_interestHistory = []
  68. _debug_maxDescriptionLen = 20
  69. def __init__(self):
  70. assert DoInterestManager.notify.debugCall()
  71. DirectObject.DirectObject.__init__(self)
  72. def addInterest(self, parentId, zoneIdList, description, event=None):
  73. """
  74. Look into a (set of) zone(s).
  75. """
  76. assert DoInterestManager.notify.debugCall()
  77. handle = self._getNextHandle()
  78. scopeId = self._getNextScopeId()
  79. DoInterestManager._interests[handle] = InterestState(
  80. description, InterestState.StateActive, scopeId, event, parentId, zoneIdList)
  81. if self.InterestDebug:
  82. print 'INTEREST DEBUG: addInterest(): handle=%s, parent=%s, zoneIds=%s, description=%s, event=%s' % (
  83. handle, parentId, zoneIdList, description, event)
  84. self._sendAddInterest(handle, scopeId, parentId, zoneIdList, description)
  85. assert self.printInterestsIfDebug()
  86. return handle
  87. def countOpenInterests(self):
  88. openInterestsCount = 0
  89. for interest in DoInterestManager._interests.values():
  90. # I'm only getting NO_SCOPE interests, so for now I'll key off
  91. # of events
  92. #if interest.scope != NO_SCOPE:
  93. # openInterestsCount += 1
  94. #if len(interest.events) != 0:
  95. openInterestsCount += 1
  96. return openInterestsCount
  97. def removeInterest(self, handle, event=None):
  98. """
  99. Stop looking in a (set of) zone(s)
  100. """
  101. assert DoInterestManager.notify.debugCall()
  102. existed = False
  103. if DoInterestManager._interests.has_key(handle):
  104. existed = True
  105. intState = DoInterestManager._interests[handle]
  106. if intState.isPendingDelete():
  107. self.notify.warning(
  108. 'removeInterest: interest %s already pending removal' %
  109. handle)
  110. # this interest is already pending delete, so let's just tack this
  111. # callback onto the list
  112. if event is not None:
  113. intState.addEvent(event)
  114. else:
  115. if len(intState.events) > 0:
  116. # we're not pending a removal, but we have outstanding events?
  117. # probably we are waiting for an add/alter complete.
  118. # should we send those events now?
  119. self.notify.warning('removeInterest: abandoning events: %s' %
  120. intState.events)
  121. intState.clearEvents()
  122. scopeId = self._getNextScopeId()
  123. intState.state = InterestState.StatePendingDel
  124. intState.scope = scopeId
  125. if event is not None:
  126. intState.addEvent(event)
  127. if self.InterestDebug:
  128. print 'INTEREST DEBUG: removeInterest(): handle=%s, event=%s' % (
  129. handle, event)
  130. self._sendRemoveInterest(handle, scopeId)
  131. if event is None:
  132. self._considerRemoveInterest(handle)
  133. else:
  134. DoInterestManager.notify.warning(
  135. "removeInterest: handle not found: %s" % (handle))
  136. assert self.printInterestsIfDebug()
  137. return existed
  138. def alterInterest(self, handle, parentId, zoneIdList, description=None,
  139. event=None):
  140. """
  141. Removes old interests and adds new interests.
  142. Note that when an interest is changed, only the most recent
  143. change's event will be triggered. Previous events are abandoned.
  144. If this is a problem, consider opening multiple interests.
  145. """
  146. assert DoInterestManager.notify.debugCall()
  147. exists = False
  148. if DoInterestManager._interests.has_key(handle):
  149. if description is not None:
  150. DoInterestManager._interests[handle].desc = description
  151. else:
  152. description = DoInterestManager._interests[handle].desc
  153. # are we overriding an existing change?
  154. if DoInterestManager._interests[handle].scope != NO_SCOPE:
  155. DoInterestManager._interests[handle].clearEvents()
  156. scopeId = self._getNextScopeId()
  157. DoInterestManager._interests[handle].scope = scopeId
  158. DoInterestManager._interests[handle].addEvent(event)
  159. if self.InterestDebug:
  160. print 'INTEREST DEBUG: alterInterest(): handle=%s, parent=%s, zoneIds=%s, description=%s, event=%s' % (
  161. handle, parentId, zoneIdList, description, event)
  162. self._sendAddInterest(handle, scopeId, parentId, zoneIdList, description, action='modify')
  163. exists = True
  164. assert self.printInterestsIfDebug()
  165. else:
  166. DoInterestManager.notify.warning(
  167. "alterInterest: handle not found: %s" % (handle))
  168. return exists
  169. def _getInterestState(self, handle):
  170. return DoInterestManager._interests[handle]
  171. def _getNextHandle(self):
  172. handle = DoInterestManager._HandleSerialNum
  173. while True:
  174. handle = (handle + 1) & DoInterestManager._HandleMask
  175. # skip handles that are already in use
  176. if handle not in DoInterestManager._interests:
  177. break
  178. DoInterestManager.notify.warning(
  179. 'interest %s already in use' % handle)
  180. DoInterestManager._HandleSerialNum = handle
  181. return DoInterestManager._HandleSerialNum
  182. def _getNextScopeId(self):
  183. scopeId = DoInterestManager._ScopeIdSerialNum
  184. while True:
  185. scopeId = (scopeId + 1) & DoInterestManager._ScopeIdMask
  186. # skip over the 'no scope' id
  187. if scopeId != NO_SCOPE:
  188. break
  189. DoInterestManager._ScopeIdSerialNum = scopeId
  190. return DoInterestManager._ScopeIdSerialNum
  191. def _considerRemoveInterest(self, handle):
  192. """
  193. Consider whether we should cull the interest set.
  194. """
  195. assert DoInterestManager.notify.debugCall()
  196. if DoInterestManager._interests.has_key(handle):
  197. if DoInterestManager._interests[handle].isPendingDelete():
  198. # make sure there is no pending event for this interest
  199. if DoInterestManager._interests[handle].scope == NO_SCOPE:
  200. del DoInterestManager._interests[handle]
  201. if __debug__:
  202. def printInterestsIfDebug(self):
  203. if DoInterestManager.notify.getDebug():
  204. self.printInterests()
  205. return 1 # for assert
  206. def _addDebugInterestHistory(self, action, description, handle,
  207. scopeId, parentId, zoneIdList):
  208. if description is None:
  209. description = ''
  210. DoInterestManager._debug_interestHistory.append(
  211. (action, description, handle, scopeId, parentId, zoneIdList))
  212. DoInterestManager._debug_maxDescriptionLen = max(
  213. DoInterestManager._debug_maxDescriptionLen, len(description))
  214. def printInterests(self):
  215. print "***************** Interest History *************"
  216. format = '%9s %' + str(DoInterestManager._debug_maxDescriptionLen) + 's %6s %6s %9s %s'
  217. print format % (
  218. "Action", "Description", "Handle", "Scope", "ParentId",
  219. "ZoneIdList")
  220. for i in DoInterestManager._debug_interestHistory:
  221. print format % tuple(i)
  222. print "Note: interests with a Scope of 0 do not get" \
  223. " done/finished notices."
  224. print "******************* Interest Sets **************"
  225. format = '%6s %' + str(DoInterestManager._debug_maxDescriptionLen) + 's %10s %5s %9s %9s %10s'
  226. print format % (
  227. "Handle", "Description", "State", "Scope",
  228. "ParentId", "ZoneIdList", "Event")
  229. for id, state in DoInterestManager._interests.items():
  230. if len(state.events) == 0:
  231. event = ''
  232. elif len(state.events) == 1:
  233. event = state.events[0]
  234. else:
  235. event = state.events
  236. print format % (id, state.desc, state.state, state.scope,
  237. state.parentId, state.zoneIdList, event)
  238. print "************************************************"
  239. def _sendAddInterest(self, handle, scopeId, parentId, zoneIdList, description,
  240. action=None):
  241. """
  242. Part of the new otp-server code.
  243. handle is a client-side created number that refers to
  244. a set of interests. The same handle number doesn't
  245. necessarily have any relationship to the same handle
  246. on another client.
  247. """
  248. assert DoInterestManager.notify.debugCall()
  249. if __debug__:
  250. if isinstance(zoneIdList, types.ListType):
  251. zoneIdList.sort()
  252. if action is None:
  253. action = 'add'
  254. self._addDebugInterestHistory(
  255. action, description, handle, scopeId, parentId, zoneIdList)
  256. if parentId == 0:
  257. DoInterestManager.notify.error(
  258. 'trying to set interest to invalid parent: %s' % parentId)
  259. datagram = PyDatagram()
  260. # Add message type
  261. datagram.addUint16(CLIENT_ADD_INTEREST)
  262. datagram.addUint16(handle)
  263. datagram.addUint32(scopeId)
  264. datagram.addUint32(parentId)
  265. if isinstance(zoneIdList, types.ListType):
  266. vzl = list(zoneIdList)
  267. vzl.sort()
  268. uniqueElements(vzl)
  269. for zone in vzl:
  270. datagram.addUint32(zone)
  271. else:
  272. datagram.addUint32(zoneIdList)
  273. self.send(datagram)
  274. def _sendRemoveInterest(self, handle, scopeId):
  275. """
  276. handle is a client-side created number that refers to
  277. a set of interests. The same handle number doesn't
  278. necessarily have any relationship to the same handle
  279. on another client.
  280. """
  281. assert DoInterestManager.notify.debugCall()
  282. assert handle in DoInterestManager._interests
  283. datagram = PyDatagram()
  284. # Add message type
  285. datagram.addUint16(CLIENT_REMOVE_INTEREST)
  286. datagram.addUint16(handle)
  287. if scopeId != 0:
  288. datagram.addUint32(scopeId)
  289. self.send(datagram)
  290. if __debug__:
  291. state = DoInterestManager._interests[handle]
  292. self._addDebugInterestHistory(
  293. "remove", state.desc, handle, scopeId,
  294. state.parentId, state.zoneIdList)
  295. def handleInterestDoneMessage(self, di):
  296. """
  297. This handles the interest done messages and may dispatch an event
  298. """
  299. assert DoInterestManager.notify.debugCall()
  300. handle = di.getUint16()
  301. scopeId = di.getUint32()
  302. if self.InterestDebug:
  303. print 'INTEREST DEBUG: interestDone(): handle=%s' % handle
  304. DoInterestManager.notify.debug(
  305. "handleInterestDoneMessage--> Received handle %s, scope %s" % (
  306. handle, scopeId))
  307. eventsToSend = []
  308. # if the scope matches, send out the event
  309. if scopeId == DoInterestManager._interests[handle].scope:
  310. DoInterestManager._interests[handle].scope = NO_SCOPE
  311. # the event handlers may call back into the interest manager. Send out
  312. # the events after we're once again in a stable state.
  313. #DoInterestManager._interests[handle].sendEvents()
  314. eventsToSend = list(DoInterestManager._interests[handle].getEvents())
  315. DoInterestManager._interests[handle].clearEvents()
  316. else:
  317. DoInterestManager.notify.warning(
  318. "handleInterestDoneMessage--> handle: %s: Expecting scope %s, got %s" % (
  319. handle, DoInterestManager._interests[handle].scope, scopeId))
  320. if __debug__:
  321. state = DoInterestManager._interests[handle]
  322. self._addDebugInterestHistory(
  323. "finished", state.desc, handle, scopeId, state.parentId,
  324. state.zoneIdList)
  325. self._considerRemoveInterest(handle)
  326. for event in eventsToSend:
  327. messenger.send(event)
  328. assert self.printInterestsIfDebug()
  329. if __debug__:
  330. import unittest
  331. class AsyncTestCase(unittest.TestCase):
  332. def setCompleted(self):
  333. self._async_completed = True
  334. def isCompleted(self):
  335. return getattr(self, '_async_completed', False)
  336. class AsyncTestSuite(unittest.TestSuite):
  337. pass
  338. class AsyncTestLoader(unittest.TestLoader):
  339. suiteClass = AsyncTestSuite
  340. class AsyncTextTestRunner(unittest.TextTestRunner):
  341. def run(self, testCase):
  342. result = self._makeResult()
  343. startTime = time.time()
  344. test(result)
  345. stopTime = time.time()
  346. timeTaken = stopTime - startTime
  347. result.printErrors()
  348. self.stream.writeln(result.separator2)
  349. run = result.testsRun
  350. self.stream.writeln("Ran %d test%s in %.3fs" %
  351. (run, run != 1 and "s" or "", timeTaken))
  352. self.stream.writeln()
  353. if not result.wasSuccessful():
  354. self.stream.write("FAILED (")
  355. failed, errored = map(len, (result.failures, result.errors))
  356. if failed:
  357. self.stream.write("failures=%d" % failed)
  358. if errored:
  359. if failed: self.stream.write(", ")
  360. self.stream.write("errors=%d" % errored)
  361. self.stream.writeln(")")
  362. else:
  363. self.stream.writeln("OK")
  364. return result
  365. class TestInterestAddRemove(AsyncTestCase, DirectObject.DirectObject):
  366. def testInterestAdd(self):
  367. event = uniqueName('InterestAdd')
  368. self.acceptOnce(event, self.gotInterestAddResponse)
  369. self.handle = base.cr.addInterest(base.cr.GameGlobalsId, 100, 'TestInterest', event=event)
  370. def gotInterestAddResponse(self):
  371. event = uniqueName('InterestRemove')
  372. self.acceptOnce(event, self.gotInterestRemoveResponse)
  373. base.cr.removeInterest(self.handle, event=event)
  374. def gotInterestRemoveResponse(self):
  375. self.setCompleted()
  376. def runTests():
  377. suite = unittest.makeSuite(TestInterestAddRemove)
  378. unittest.AsyncTextTestRunner(verbosity=2).run(suite)