RelatedObjectMgr.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. """RelatedObjectMgr module: contains the RelatedObjectMgr class"""
  2. # from direct.showbase.ShowBaseGlobal import *
  3. from direct.showbase import DirectObject
  4. from direct.directnotify import DirectNotifyGlobal
  5. class RelatedObjectMgr(DirectObject.DirectObject):
  6. """
  7. This class manages a relationship between DistributedObjects that
  8. know about each other, and are expected to be generated together.
  9. Ideally, we should be able to guarantee the ordering of the
  10. generate calls, but there are certain cases in which the objects
  11. may not be generated in the correct order as defined by the
  12. toon.dc file.
  13. To handle cases like these robustly, it is necessary for each
  14. object to deal with the possibility that its companion object has
  15. not yet been generated. This may mean deferring some operations
  16. until the expected companion object has been generated.
  17. This class helps manage that process. To use it, an object should
  18. register its desire to be associated with the other object's doId.
  19. When the other object is generated (or immediately, if the object
  20. already exists), the associated callback will be called. There is
  21. also a timeout callback in case the object never appears.
  22. """
  23. notify = DirectNotifyGlobal.directNotify.newCategory('RelatedObjectMgr')
  24. doLaterSequence = 1
  25. def __init__(self, cr):
  26. self.cr = cr
  27. self.pendingObjects = {}
  28. def destroy(self):
  29. self.abortAllRequests()
  30. del self.cr
  31. del self.pendingObjects
  32. def requestObjects(self, doIdList, allCallback = None, eachCallback = None,
  33. timeout = None, timeoutCallback = None):
  34. """
  35. Requests a callback to be called when the objects in the
  36. doIdList are generated. The allCallback will be called only
  37. when all the objects have been generated (and it receives a
  38. list of objects, in the order given in doIdList). The
  39. eachCallback is called as each object is generated, and
  40. receives only the object itself.
  41. If the objects already exist, the appropriate callback is
  42. called immediately.
  43. If all of the objects are not generated within the indicated
  44. timeout time, the timeoutCallback is called instead, with the
  45. original doIdList as the parameter. If the timeoutCallback is
  46. None, then allCallback is called on timeout, with the list of
  47. objects that have been generated so far, and None for objects
  48. that have not been generated.
  49. If any element of doIdList is None or 0, it is ignored, and
  50. None is passed in its place in the object list passed to the
  51. callback.
  52. The return value may be saved and passed to a future call to
  53. abortRequest(), in order to abort a pending request before the
  54. timeout expires.
  55. Actually, you should be careful to call abortRequest() if you
  56. have made a call to requestObjects() that has not been resolved.
  57. To find examples, do a search for abortRequest() to find out
  58. how other code is using it. A common idiom is to store the
  59. result from requestObjects() and call abortRequest() if delete()
  60. or destroy() is called on the requesting object.
  61. See Also: abortRequest()
  62. """
  63. assert self.notify.debug("requestObjects(%s, timeout=%s)" % (doIdList, timeout))
  64. # First, see if we have all of the objects already.
  65. objects, doIdsPending = self.__generateObjectList(doIdList)
  66. # Call the eachCallback immediately on any objects we already
  67. # have.
  68. if eachCallback:
  69. for object in objects:
  70. if object:
  71. eachCallback(object)
  72. if len(doIdsPending) == 0:
  73. # All the objects exist, so just call the callback
  74. # immediately.
  75. assert self.notify.debug("All objects already exist.")
  76. if allCallback:
  77. allCallback(objects)
  78. return
  79. # Some objects don't exist yet, so start listening for them, and
  80. # also set a timeout in case they don't come.
  81. assert self.notify.debug("Some objects pending: %s" % (doIdsPending))
  82. # Make a copy of the original doIdList, so we can save it over
  83. # a period of time without worrying about the caller modifying
  84. # it.
  85. doIdList = doIdList[:]
  86. doLaterName = None
  87. if timeout != None:
  88. doLaterName = "RelatedObject-%s" % (RelatedObjectMgr.doLaterSequence)
  89. assert self.notify.debug("doLaterName = %s" % (doLaterName))
  90. RelatedObjectMgr.doLaterSequence += 1
  91. tuple = (allCallback, eachCallback, timeoutCallback,
  92. doIdsPending, doIdList, doLaterName)
  93. for doId in doIdsPending:
  94. pendingList = self.pendingObjects.get(doId)
  95. if pendingList == None:
  96. pendingList = []
  97. self.pendingObjects[doId] = pendingList
  98. self.__listenFor(doId)
  99. pendingList.append(tuple)
  100. if doLaterName:
  101. # Now spawn a do-later to catch the timeout.
  102. taskMgr.doMethodLater(timeout, self.__timeoutExpired, doLaterName,
  103. extraArgs = [tuple])
  104. return tuple
  105. def abortRequest(self, tuple):
  106. """
  107. Aborts a previous request. The parameter is the return value
  108. from a previous call to requestObjects(). The pending request
  109. is removed from the queue and no further callbacks will be called.
  110. See Also: requestObjects()
  111. """
  112. if tuple:
  113. allCallback, eachCallback, timeoutCallback, doIdsPending, doIdList, doLaterName = tuple
  114. assert self.notify.debug("aborting request for %s (remaining: %s)" % (doIdList, doIdsPending))
  115. if doLaterName:
  116. taskMgr.remove(doLaterName)
  117. self.__removePending(tuple, doIdsPending)
  118. def abortAllRequests(self):
  119. """
  120. Call this method to abruptly abort all pending requests, but
  121. leave the RelatedObjectMgr in a state for accepting more
  122. requests.
  123. """
  124. # Stop listening for all events.
  125. self.ignoreAll()
  126. # Iterate through all the pendingObjects and stop any pending
  127. # tasks.
  128. for pendingList in self.pendingObjects.values():
  129. for tuple in pendingList:
  130. allCallback, eachCallback, timeoutCallback, doIdsPending, doIdList, doLaterName = tuple
  131. if doLaterName:
  132. taskMgr.remove(doLaterName)
  133. self.pendingObjects = {}
  134. def __timeoutExpired(self, tuple):
  135. allCallback, eachCallback, timeoutCallback, doIdsPending, doIdList, doLaterName = tuple
  136. assert self.notify.debug("timeout expired for %s (remaining: %s)" % (doIdList, doIdsPending))
  137. self.__removePending(tuple, doIdsPending)
  138. if timeoutCallback:
  139. timeoutCallback(doIdList)
  140. else:
  141. objects, doIdsPending = self.__generateObjectList(doIdList)
  142. if allCallback:
  143. allCallback(objects)
  144. def __removePending(self, tuple, doIdsPending):
  145. # Removes all the pending events for the doIdsPending list.
  146. while len(doIdsPending) > 0:
  147. # We pop doId's off the list instead of simply iterating
  148. # through the list, so that we will shorten the list (and
  149. # all other outstanding instances of the list) as we go.
  150. doId = doIdsPending.pop()
  151. pendingList = self.pendingObjects[doId]
  152. pendingList.remove(tuple)
  153. if len(pendingList) == 0:
  154. del self.pendingObjects[doId]
  155. self.__noListenFor(doId)
  156. def __listenFor(self, doId):
  157. # Start listening for the indicated object to be generated.
  158. assert self.notify.debug("Now listening for generate from %s" % (doId))
  159. announceGenerateName = "generate-%s" % (doId)
  160. self.acceptOnce(announceGenerateName, self.__generated)
  161. def __noListenFor(self, doId):
  162. # Stop listening for the indicated object to be generated.
  163. assert self.notify.debug("No longer listening for generate from %s" % (doId))
  164. announceGenerateName = "generate-%s" % (doId)
  165. self.ignore(announceGenerateName)
  166. def __generated(self, object):
  167. # The indicated object has been generated.
  168. doId = object.doId
  169. assert self.notify.debug("Got generate from %s" % (doId))
  170. pendingList = self.pendingObjects[doId]
  171. del self.pendingObjects[doId]
  172. for tuple in pendingList:
  173. allCallback, eachCallback, timeoutCallback, doIdsPending, doIdList, doLaterName = tuple
  174. # Here we are depending on Python to unify this one list
  175. # across all objects that share it. When we remove our
  176. # doId from our reference to the list, it is also removed
  177. # from all the other references.
  178. doIdsPending.remove(doId)
  179. if eachCallback:
  180. eachCallback(object)
  181. if len(doIdsPending) == 0:
  182. # That was the last doId on the list. Call the
  183. # allCallback!
  184. assert self.notify.debug("All objects generated on list: %s" % (doIdList,))
  185. if doLaterName:
  186. taskMgr.remove(doLaterName)
  187. objects, doIdsPending = self.__generateObjectList(doIdList)
  188. if None in objects:
  189. self.notify.warning('calling %s with None.\n objects=%s\n doIdsPending=%s\n doIdList=%s\n' % (allCallback,objects,doIdsPending,doIdList))
  190. if allCallback:
  191. allCallback(objects)
  192. else:
  193. assert self.notify.debug("Objects still pending: %s" % (doIdsPending))
  194. def __generateObjectList(self, doIdList):
  195. objects = []
  196. doIdsPending = []
  197. for doId in doIdList:
  198. if doId:
  199. object = self.cr.doId2do.get(doId)
  200. objects.append(object)
  201. if object == None:
  202. doIdsPending.append(doId)
  203. else:
  204. objects.append(None)
  205. return objects, doIdsPending