|
|
@@ -0,0 +1,204 @@
|
|
|
+"""RelatedObjectMgr module: contains the RelatedObjectMgr class"""
|
|
|
+
|
|
|
+from ShowBaseGlobal import *
|
|
|
+from ToontownGlobals import *
|
|
|
+import DirectObject
|
|
|
+import DirectNotifyGlobal
|
|
|
+
|
|
|
+class RelatedObjectMgr(DirectObject.DirectObject):
|
|
|
+ """
|
|
|
+
|
|
|
+ This class manages a relationship between DistributedObjects that
|
|
|
+ know about each other, and are expected to be generated together.
|
|
|
+ Ideally, we should be able to guarantee the ordering of the
|
|
|
+ generate calls, but there are certain cases in which the objects
|
|
|
+ may not be generated in the correct order as defined by the
|
|
|
+ toon.dc file.
|
|
|
+
|
|
|
+ To handle cases like these robustly, it is necessary for each
|
|
|
+ object to deal with the possibility that its companion object has
|
|
|
+ not yet been generated. This may mean deferring some operations
|
|
|
+ until the expected companion object has been generated.
|
|
|
+
|
|
|
+ This class helps manage that process. To use it, an object should
|
|
|
+ register its desire to be associated with the other object's doId.
|
|
|
+ When the other object is generated (or immediately, if the object
|
|
|
+ already exists), the associated callback will be called. There is
|
|
|
+ also a timeout callback in case the object never appears.
|
|
|
+ """
|
|
|
+
|
|
|
+ notify = DirectNotifyGlobal.directNotify.newCategory('RelatedObjectMgr')
|
|
|
+
|
|
|
+ doLaterSequence = 1
|
|
|
+
|
|
|
+ def __init__(self, cr):
|
|
|
+ self.cr = cr
|
|
|
+ self.pendingObjects = {}
|
|
|
+
|
|
|
+ def destroy(self):
|
|
|
+ del self.cr
|
|
|
+ del self.pendingObjects
|
|
|
+ self.ignoreAll()
|
|
|
+
|
|
|
+ def requestObjects(self, doIdList, callback,
|
|
|
+ timeout = None, timeoutCallback = None):
|
|
|
+ """
|
|
|
+ Requests the indicated callback to be called when all the
|
|
|
+ objects in the doIdList are generated. If all the objects
|
|
|
+ already exist, the callback is called immediately. In either
|
|
|
+ case, the list of objects, in the order given in doIdList, is
|
|
|
+ passed to the callback.
|
|
|
+
|
|
|
+ If all of the objects are not generated within the indicated
|
|
|
+ timeout time, the timeoutCallback is called instead, with the
|
|
|
+ original doIdList as the parameter. If the timeoutCallback is
|
|
|
+ None, then the original callback is called on timeout, with
|
|
|
+ the list of objects that have been generated so far, and None
|
|
|
+ for objects that have not been generated.
|
|
|
+
|
|
|
+ If any element of doIdList is None or 0, it is ignored, and
|
|
|
+ None is passed in its place in the object list passed to the
|
|
|
+ callback.
|
|
|
+
|
|
|
+ The return value may be saved and passed to a future call to
|
|
|
+ abortRequest(), in order to abort a pending request before the
|
|
|
+ timeout expires.
|
|
|
+
|
|
|
+ """
|
|
|
+ assert(self.notify.debug("requestObjects(%s, timeout=%s)" % (doIdList, timeout)))
|
|
|
+
|
|
|
+ # First, see if we have all of the objects already.
|
|
|
+ objects, doIdsPending = self.__generateObjectList(doIdList)
|
|
|
+ if len(doIdsPending) == 0:
|
|
|
+ # All the objects exist, so just call the callback
|
|
|
+ # immediately.
|
|
|
+ assert(self.notify.debug("All objects already exist."))
|
|
|
+ callback(objects)
|
|
|
+ return
|
|
|
+
|
|
|
+ # Some objects don't exist yet, so start listening for them, and
|
|
|
+ # also set a timeout in case they don't come.
|
|
|
+ assert(self.notify.debug("Some objects pending: %s" % (doIdsPending)))
|
|
|
+
|
|
|
+ # Make a copy of the original doIdList, so we can save it over
|
|
|
+ # a period of time without worrying about the caller modifying
|
|
|
+ # it.
|
|
|
+ doIdList = doIdList[:]
|
|
|
+
|
|
|
+ doLaterName = None
|
|
|
+ if timeout != None:
|
|
|
+ doLaterName = "RelatedObject-%s" % (RelatedObjectMgr.doLaterSequence)
|
|
|
+ assert(self.notify.debug("doLaterName = %s" % (doLaterName)))
|
|
|
+
|
|
|
+ RelatedObjectMgr.doLaterSequence += 1
|
|
|
+
|
|
|
+ tuple = (callback, timeoutCallback, doIdsPending, doIdList, doLaterName)
|
|
|
+
|
|
|
+ for doId in doIdsPending:
|
|
|
+ pendingList = self.pendingObjects.get(doId)
|
|
|
+ if pendingList == None:
|
|
|
+ pendingList = []
|
|
|
+ self.pendingObjects[doId] = pendingList
|
|
|
+ self.__listenFor(doId)
|
|
|
+
|
|
|
+ pendingList.append(tuple)
|
|
|
+
|
|
|
+ if doLaterName:
|
|
|
+ # Now spawn a do-later to catch the timeout.
|
|
|
+ taskMgr.doMethodLater(timeout, self.__timeoutExpired, doLaterName,
|
|
|
+ extraArgs = [tuple])
|
|
|
+
|
|
|
+ return tuple
|
|
|
+
|
|
|
+ def abortRequest(self, tuple):
|
|
|
+ """
|
|
|
+ Aborts a previous request. The parameter is the return value
|
|
|
+ from a previous call to requestObjects(). The pending request
|
|
|
+ is removed from the queue and no further callbacks will be called.
|
|
|
+ """
|
|
|
+ callback, timeoutCallback, doIdsPending, doIdList, doLaterName = tuple
|
|
|
+ assert(self.notify.debug("aborting request for %s (remaining: %s)" % (doIdList, doIdsPending)))
|
|
|
+
|
|
|
+ taskMgr.remove(doLaterName)
|
|
|
+ self.__removePending(tuple, doIdsPending)
|
|
|
+
|
|
|
+ def __timeoutExpired(self, tuple):
|
|
|
+ callback, timeoutCallback, doIdsPending, doIdList, doLaterName = tuple
|
|
|
+ assert(self.notify.debug("timeout expired for %s (remaining: %s)" % (doIdList, doIdsPending)))
|
|
|
+
|
|
|
+ self.__removePending(tuple, doIdsPending)
|
|
|
+
|
|
|
+ if timeoutCallback:
|
|
|
+ timeoutCallback(doIdList)
|
|
|
+ else:
|
|
|
+ objects, doIdsPending = self.__generateObjectList(doIdList)
|
|
|
+ callback(objects)
|
|
|
+
|
|
|
+ def __removePending(self, tuple, doIdsPending):
|
|
|
+ # Removes all the pending events for the doIdsPending list.
|
|
|
+ while len(doIdsPending) > 0:
|
|
|
+ # We pop doId's off the list instead of simply iterating
|
|
|
+ # through the list, so that we will shorten the list (and
|
|
|
+ # all other outstanding instances of the list) as we go.
|
|
|
+ doId = doIdsPending.pop()
|
|
|
+ pendingList = self.pendingObjects[doId]
|
|
|
+ pendingList.remove(tuple)
|
|
|
+ if len(pendingList) == 0:
|
|
|
+ del self.pendingObjects[doId]
|
|
|
+ self.__noListenFor(doId)
|
|
|
+
|
|
|
+
|
|
|
+ def __listenFor(self, doId):
|
|
|
+ # Start listening for the indicated object to be generated.
|
|
|
+ assert(self.notify.debug("Now listening for generate from %s" % (doId)))
|
|
|
+ announceGenerateName = "generate-%s" % (doId)
|
|
|
+ self.acceptOnce(announceGenerateName, self.__generated)
|
|
|
+
|
|
|
+ def __noListenFor(self, doId):
|
|
|
+ # Stop listening for the indicated object to be generated.
|
|
|
+ assert(self.notify.debug("No longer listening for generate from %s" % (doId)))
|
|
|
+ announceGenerateName = "generate-%s" % (doId)
|
|
|
+ self.ignore(announceGenerateName)
|
|
|
+
|
|
|
+ def __generated(self, object):
|
|
|
+ # The indicated object has been generated.
|
|
|
+ doId = object.doId
|
|
|
+ assert(self.notify.debug("Got generate from %s" % (doId)))
|
|
|
+ pendingList = self.pendingObjects[doId]
|
|
|
+ del self.pendingObjects[doId]
|
|
|
+
|
|
|
+ for tuple in pendingList:
|
|
|
+ callback, timeoutCallback, doIdsPending, doIdList, doLaterName = tuple
|
|
|
+
|
|
|
+ # Here we are depending on Python to unify this one list
|
|
|
+ # across all objects that share it. When we remove our
|
|
|
+ # doId from our reference to the list, it is also removed
|
|
|
+ # from all the other references.
|
|
|
+ doIdsPending.remove(doId)
|
|
|
+
|
|
|
+ if len(doIdsPending) == 0:
|
|
|
+ # That was the last doId on the list. Call the
|
|
|
+ # callback!
|
|
|
+ assert(self.notify.debug("All objects generated on list: %s" % (doIdList)))
|
|
|
+ taskMgr.remove(doLaterName)
|
|
|
+
|
|
|
+ objects, doIdsPending = self.__generateObjectList(doIdList)
|
|
|
+ callback(objects)
|
|
|
+
|
|
|
+ else:
|
|
|
+ assert(self.notify.debug("Objects still pending: %s" % (doIdsPending)))
|
|
|
+
|
|
|
+ def __generateObjectList(self, doIdList):
|
|
|
+ objects = []
|
|
|
+ doIdsPending = []
|
|
|
+
|
|
|
+ for doId in doIdList:
|
|
|
+ if doId:
|
|
|
+ object = self.cr.doId2do.get(doId)
|
|
|
+ objects.append(object)
|
|
|
+ if object == None:
|
|
|
+ doIdsPending.append(doId)
|
|
|
+ else:
|
|
|
+ objects.append(None)
|
|
|
+
|
|
|
+ return objects, doIdsPending
|