| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464 |
- """DistributedObject module: contains the DistributedObject class"""
- from direct.showbase.PandaObject import *
- from direct.directnotify.DirectNotifyGlobal import directNotify
- from PyDatagram import PyDatagram
- from PyDatagramIterator import PyDatagramIterator
- # Values for DistributedObject.activeState
- ESNew = 1
- ESDeleted = 2
- ESDisabling = 3
- ESDisabled = 4 # values here and lower are considered "disabled"
- ESGenerating = 5 # values here and greater are considered "generated"
- ESGenerated = 6
- class DistributedObject(PandaObject):
- """
- The Distributed Object class is the base class for all network based
- (i.e. distributed) objects. These will usually (always?) have a
- dclass entry in a *.dc file.
- """
- notify = directNotify.newCategory("DistributedObject")
- # A few objects will set neverDisable to 1... Examples are
- # localToon, and anything that lives in the UberZone. This
- # keeps them from being disabled when you change zones,
- # even to the quiet zone.
- neverDisable = 0
- def __init__(self, cr):
- assert self.notify.debugStateCall(self)
- try:
- self.DistributedObject_initialized
- except:
- self.DistributedObject_initialized = 1
- self.cr = cr
- self.children = {}
- # Most DistributedObjects are simple and require no real
- # effort to load. Some, particularly actors, may take
- # some significant time to load; these we can optimize by
- # caching them when they go away instead of necessarily
- # deleting them. The object should set cacheable to 1 if
- # it needs to be optimized in this way.
- self.setCacheable(0)
- # This count tells whether the object can be deleted right away,
- # or not.
- self.delayDeleteCount = 0
- # This flag tells whether a delete has been requested on this
- # object.
- self.deleteImminent = 0
- # Keep track of our state as a distributed object. This
- # is only trustworthy if the inheriting class properly
- # calls up the chain for disable() and generate().
- self.activeState = ESNew
- # These are used by getCallbackContext() and doCallbackContext().
- self.__nextContext = 0
- self.__callbacks = {}
- # This is used by doneBarrier().
- self.__barrierContext = None
- #zone of the distributed object, default to 0
- self.zone = 0
-
- self.parentId = None
- self.zoneId = None
- if __debug__:
- def status(self, indent=0):
- """
- print out "doId(parentId,zoneId) className
- and conditionally show generated, disabled, neverDisable,
- or cachable"
- """
- spaces=' '*(indent+2)
- try:
- print "%s%s:"%(
- ' '*indent, self.__class__.__name__)
- print "%sfrom DistributedObject doId:%s, parent:%s, zone:%s"%(
- spaces,
- self.doId, self.parentId, self.zoneId),
- flags=[]
- if self.activeState == ESGenerated:
- flags.append("generated")
- if self.activeState < ESGenerating:
- flags.append("disabled")
- if self.neverDisable:
- flags.append("neverDisable")
- if self.cacheable:
- flags.append("cacheable")
- if len(flags):
- print "(%s)"%(" ".join(flags),),
- print
- except Exception, e: print "%serror printing status"%(spaces,), e
- def setNeverDisable(self, bool):
- assert((bool == 1) or (bool == 0))
- self.neverDisable = bool
- def getNeverDisable(self):
- return self.neverDisable
- def setCacheable(self, bool):
- assert((bool == 1) or (bool == 0))
- self.cacheable = bool
- return None
- def getCacheable(self):
- return self.cacheable
- def deleteOrDelay(self):
- if self.delayDeleteCount > 0:
- self.deleteImminent = 1
- else:
- self.disableAnnounceAndDelete()
- def delayDelete(self, flag):
- # Flag should be 0 or 1, meaning increment or decrement count
- # Also see DelayDelete.py
- if (flag == 1):
- self.delayDeleteCount += 1
- elif (flag == 0):
- self.delayDeleteCount -= 1
- else:
- self.notify.error("Invalid flag passed to delayDelete: " + str(flag))
- if (self.delayDeleteCount < 0):
- self.notify.error("Somebody decremented delayDelete for doId %s without incrementing"
- % (self.doId))
- elif (self.delayDeleteCount == 0):
- assert(self.notify.debug("delayDeleteCount for doId %s now 0"
- % (self.doId)))
- if self.deleteImminent:
- assert(self.notify.debug("delayDeleteCount for doId %s -- deleteImminent"
- % (self.doId)))
- self.disableAnnounceAndDelete()
- else:
- self.notify.debug("delayDeleteCount for doId %s now %s"
- % (self.doId, self.delayDeleteCount))
- # Return the count just for kicks
- return self.delayDeleteCount
- def disableAnnounceAndDelete(self):
- self.disableAndAnnounce()
- self.delete()
- def disableAndAnnounce(self):
- """
- Inheritors should *not* redefine this function.
- """
- # We must send the disable announce message *before* we
- # actually disable the object. That way, the various cleanup
- # tasks can run first and take care of restoring the object to
- # a normal, nondisabled state; and *then* the disable function
- # can properly disable it (for instance, by parenting it to
- # hidden).
- if self.activeState != ESDisabled:
- self.activeState = ESDisabling
- messenger.send(self.uniqueName("disable"))
- self.disable()
- def announceGenerate(self):
- """
- Sends a message to the world after the object has been
- generated and all of its required fields filled in.
- """
- assert(self.notify.debug('announceGenerate(): %s' % (self.doId)))
- if self.activeState != ESGenerated:
- self.activeState = ESGenerated
- messenger.send(self.uniqueName("generate"), [self])
- def disable(self):
- """
- Inheritors should redefine this to take appropriate action on disable
- """
- assert(self.notify.debug('disable(): %s' % (self.doId)))
- if self.activeState != ESDisabled:
- self.activeState = ESDisabled
- self.__callbacks = {}
- if wantOtpServer:
- #self.cr.deleteObjectLocation(self.doId, self.parentId, self.zoneId)
- self.setLocation(None, None)
- # TODO: disable my children
- def isDisabled(self):
- """
- Returns true if the object has been disabled and/or deleted,
- or if it is brand new and hasn't yet been generated.
- """
- return (self.activeState < ESGenerating)
- def isGenerated(self):
- """
- Returns true if the object has been fully generated by now,
- and not yet disabled.
- """
- assert self.notify.debugStateCall(self)
- return (self.activeState == ESGenerated)
- def delete(self):
- """
- Inheritors should redefine this to take appropriate action on delete
- """
- assert(self.notify.debug('delete(): %s' % (self.doId)))
- try:
- self.DistributedObject_deleted
- except:
- self.DistributedObject_deleted = 1
- self.cr = None
- self.dclass = None
- def generate(self):
- """
- Inheritors should redefine this to take appropriate action on generate
- """
- assert self.notify.debugStateCall(self)
- self.activeState = ESGenerating
- # this has already been set at this point
- #self.cr.storeObjectLocation(self.doId, self.parentId, self.zoneId)
- def generateInit(self):
- """
- This method is called when the DistributedObject is first introduced
- to the world... Not when it is pulled from the cache.
- """
- self.activeState = ESGenerating
- def getDoId(self):
- """
- Return the distributed object id
- """
- return self.doId
- def updateRequiredFields(self, dclass, di):
- dclass.receiveUpdateBroadcastRequired(self, di)
- self.announceGenerate()
- def updateAllRequiredFields(self, dclass, di):
- dclass.receiveUpdateAllRequired(self, di)
- self.announceGenerate()
- def updateRequiredOtherFields(self, dclass, di):
- # First, update the required fields
- dclass.receiveUpdateBroadcastRequired(self, di)
- # Announce generate after updating all the required fields,
- # but before we update the non-required fields.
- self.announceGenerate()
- dclass.receiveUpdateOther(self, di)
- def sendUpdate(self, fieldName, args = [], sendToId = None):
- if self.cr:
- dg = self.dclass.clientFormatUpdate(
- fieldName, sendToId or self.doId, args)
- self.cr.send(dg)
- else:
- self.notify.warning("sendUpdate failed, because self.cr is not set")
- def sendDisableMsg(self):
- self.cr.sendDisableMsg(self.doId)
- def sendDeleteMsg(self):
- self.cr.sendDeleteMsg(self.doId)
- def taskName(self, taskString):
- return (taskString + "-" + str(self.getDoId()))
- def uniqueName(self, idString):
- return (idString + "-" + str(self.getDoId()))
- def getCallbackContext(self, callback, extraArgs = []):
- # Some objects implement a back-and-forth handshake operation
- # with the AI via an arbitrary context number. This method
- # (coupled with doCallbackContext(), below) maps a Python
- # callback onto that context number so that client code may
- # easily call the method and wait for a callback, rather than
- # having to negotiate context numbers.
- # This method generates a new context number and stores the
- # callback so that it may later be called when the response is
- # returned.
- # This is intended to be called within derivations of
- # DistributedObject, not directly by other objects.
- context = self.__nextContext
- self.__callbacks[context] = (callback, extraArgs)
- # We assume the context number is passed as a uint16.
- self.__nextContext = (self.__nextContext + 1) & 0xffff
- return context
- def getCurrentContexts(self):
- # Returns a list of the currently outstanding contexts created
- # by getCallbackContext().
- return self.__callbacks.keys()
- def getCallback(self, context):
- # Returns the callback that was passed in to the previous
- # call to getCallbackContext.
- return self.__callbacks[context][0]
- def getCallbackArgs(self, context):
- # Returns the extraArgs that were passed in to the previous
- # call to getCallbackContext.
- return self.__callbacks[context][1]
- def doCallbackContext(self, context, args):
- # This is called after the AI has responded to the message
- # sent via getCallbackContext(), above. The context number is
- # looked up in the table and the associated callback is
- # issued.
- # This is intended to be called within derivations of
- # DistributedObject, not directly by other objects.
- tuple = self.__callbacks.get(context)
- if tuple:
- callback, extraArgs = tuple
- completeArgs = args + extraArgs
- if callback != None:
- callback(*completeArgs)
- del self.__callbacks[context]
- else:
- self.notify.warning("Got unexpected context from AI: %s" % (context))
- def setBarrierData(self, data):
- # This message is sent by the AI to tell us the barriers and
- # avIds for which the AI is currently waiting. The client
- # needs to look up its pending context in the table (and
- # ignore the other contexts). When the client is done
- # handling whatever it should handle in its current state, it
- # should call doneBarrier(), which will send the context
- # number back to the AI.
- for context, name, avIds in data:
- if base.localAvatar.doId in avIds:
- # We found localToon's id; stop here.
- self.__barrierContext = (context, name)
- assert(self.notify.debug('setBarrierData(%s, %s)' % (context, name)))
- return
- assert(self.notify.debug('setBarrierData(%s)' % (None)))
- self.__barrierContext = None
- def doneBarrier(self, name = None):
- # Tells the AI we have finished handling our task. If the
- # optional name parameter is specified, it must match the
- # barrier name specified on the AI, or the barrier is ignored.
- # This is used to ensure we are not clearing the wrong
- # barrier.
- # If this is None, it either means we have called
- # doneBarrier() twice, or we have not received a barrier
- # context from the AI. I think in either case it's ok to
- # silently ignore the error.
- if self.__barrierContext != None:
- context, aiName = self.__barrierContext
- if name == None or name == aiName:
- assert(self.notify.debug('doneBarrier(%s, %s)' % (context, aiName)))
- self.sendUpdate("setBarrierReady", [context])
- self.__barrierContext = None
- else:
- assert(self.notify.debug('doneBarrier(%s) ignored; current barrier is %s' % (name, aiName)))
- else:
- assert(self.notify.debug('doneBarrier(%s) ignored; no active barrier.' % (name)))
- if wantOtpServer:
- def addInterest(self, zoneId, note="", event=None):
- self.cr.addInterest(self.getDoId(), zoneId, note, event)
- def b_setLocation(self, parentId, zoneId):
- self.d_setLocation(parentId, zoneId)
- self.setLocation(parentId, zoneId)
- def d_setLocation(self, parentId, zoneId):
- self.cr.sendSetLocation(self.doId, parentId, zoneId)
-
- def setLocation(self, parentId, zoneId):
- #self.notify.info("setLocation: %s parentId: %s zoneId: %s" % (self.doId, parentId, zoneId))
- # parentId can be 'None', e.g. when an object is being disabled
- oldParentId = self.parentId
- oldZoneId = self.zoneId
- parentIsNew = (oldParentId != parentId)
- # notify any existing parent that we're moving away
- if (oldParentId is not None) and parentIsNew:
- oldParentObj = self.cr.doId2do.get(oldParentId)
- if oldParentObj:
- oldParentObj.handleChildLeave(self, oldZoneId)
- # The store must run first so we know the old location
- self.parentId = parentId
- self.zoneId = zoneId
- self.cr.storeObjectLocation(self.doId, parentId, zoneId)
- # Give the parent a chance to run code when a new child
- # sets location to it. For example, the parent may want to
- # scene graph reparent the child to some subnode it owns.
- if (self.parentId is not None) and parentIsNew:
- parentObj = self.cr.doId2do.get(parentId)
- if parentObj:
- parentObj.handleChildArrive(self, zoneId)
-
- def getLocation(self):
- try:
- if self.parentId == 0 and self.zoneId == 0:
- return None
- # This is a -1 stuffed into a uint32
- if self.parentId == 0xffffffff and self.zoneId == 0xffffffff:
- return None
- return (self.parentId, self.zoneId)
- except AttributeError:
- return None
- def handleChildArrive(self, childObj, zoneId):
- self.notify.debugCall()
- # A new child has just setLocation beneath us. Give us a
- # chance to run code when a new child sets location to us. For
- # example, we may want to scene graph reparent the child to
- # some subnode we own.
- ## zone=self.children.setdefault(zoneId, {})
- ## zone[childObj.doId]=childObj
-
- # Inheritors should override
- pass
- def handleChildLeave(self, childObj, zoneId):
- self.notify.debugCall()
- # A child is about to setLocation away from us. Give us a
- # chance to run code just before a child sets location away from us.
- ## zone=self.children[zoneId]
- ## del zone[childObj.doId]
- ## if not len(zone):
- ## del self.children[zoneId]
-
- # Inheritors should override
- pass
- def getParentObj(self):
- if self.parentId is None:
- return None
- return self.cr.doId2do.get(self.parentId)
- def isLocal(self):
- # This returns true if the distributed object is "local,"
- # which means the client created it instead of the AI, and it
- # gets some other special handling. Normally, only the local
- # avatar class overrides this to return true.
- return self.cr and self.cr.isLocalId(self.doId)
- def updateZone(self, zoneId):
- self.cr.sendUpdateZone(self, zoneId)
- def isGridParent(self):
- # If this distributed object is a DistributedGrid return 1. 0 by default
- return 0
|