RelatedObjectMgr

This commit is contained in:
David Rose 2003-11-04 23:52:53 +00:00
parent e534118821
commit f367e64cb5
2 changed files with 209 additions and 0 deletions

View File

@ -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

View File

@ -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