From f367e64cb58b4673de7e27a3f11d143251068312 Mon Sep 17 00:00:00 2001 From: David Rose Date: Tue, 4 Nov 2003 23:52:53 +0000 Subject: [PATCH] RelatedObjectMgr --- direct/src/distributed/ClientRepository.py | 5 + direct/src/distributed/RelatedObjectMgr.py | 204 +++++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 direct/src/distributed/RelatedObjectMgr.py diff --git a/direct/src/distributed/ClientRepository.py b/direct/src/distributed/ClientRepository.py index cfc3c15a6b..f955d5e920 100644 --- a/direct/src/distributed/ClientRepository.py +++ b/direct/src/distributed/ClientRepository.py @@ -9,6 +9,7 @@ import CRCache import ConnectionRepository import PythonUtil import ParentMgr +import RelatedObjectMgr import time class ClientRepository(ConnectionRepository.ConnectionRepository): @@ -32,6 +33,10 @@ class ClientRepository(ConnectionRepository.ConnectionRepository): # this used to be 'token2nodePath' self.parentMgr = ParentMgr.ParentMgr() + # The RelatedObjectMgr helps distributed objects find each + # other. + self.relatedObjectMgr = RelatedObjectMgr.RelatedObjectMgr(self) + def setServerDelta(self, delta): """ Indicates the approximate difference in seconds between the diff --git a/direct/src/distributed/RelatedObjectMgr.py b/direct/src/distributed/RelatedObjectMgr.py new file mode 100644 index 0000000000..0fc471c4d3 --- /dev/null +++ b/direct/src/distributed/RelatedObjectMgr.py @@ -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