mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 10:54:24 -04:00
287 lines
10 KiB
Python
287 lines
10 KiB
Python
"""DistributedObject module: contains the DistributedObject class"""
|
|
|
|
from PandaObject import *
|
|
from DirectNotifyGlobal import *
|
|
|
|
# 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):
|
|
"""Distributed Object class:"""
|
|
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):
|
|
try:
|
|
self.DistributedObject_initialized
|
|
except:
|
|
self.DistributedObject_initialized = 1
|
|
self.cr = cr
|
|
|
|
# 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 = {}
|
|
|
|
return None
|
|
|
|
#def __del__(self):
|
|
# """
|
|
# For debugging purposes, this just prints out what got deleted
|
|
# """
|
|
# print ("Destructing: " + self.__class__.__name__ + " id: " + str(self.doId))
|
|
# PandaObject.__del__(self)
|
|
|
|
def setNeverDisable(self, bool):
|
|
assert((bool == 1) or (bool == 0))
|
|
self.neverDisable = bool
|
|
return None
|
|
|
|
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()
|
|
return None
|
|
|
|
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):
|
|
self.notify.debug("delayDeleteCount for doId %s now 0" % (self.doId))
|
|
if self.deleteImminent:
|
|
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()
|
|
return None
|
|
|
|
def disableAndAnnounce(self):
|
|
"""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()
|
|
return None
|
|
|
|
def announceGenerate(self):
|
|
"""announceGenerate(self)
|
|
Sends a message to the world after the object has been
|
|
generated and all of its required fields filled in.
|
|
"""
|
|
self.activeState = ESGenerated
|
|
messenger.send(self.uniqueName("generate"), [self])
|
|
|
|
def disable(self):
|
|
"""disable(self)
|
|
Inheritors should redefine this to take appropriate action on disable
|
|
"""
|
|
self.activeState = ESDisabled
|
|
self.__callbacks = {}
|
|
|
|
def isDisabled(self):
|
|
"""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 delete(self):
|
|
"""delete(self)
|
|
Inheritors should redefine this to take appropriate action on delete
|
|
"""
|
|
try:
|
|
self.DistributedObject_deleted
|
|
except:
|
|
self.DistributedObject_deleted = 1
|
|
del self.cr
|
|
return
|
|
|
|
def generate(self):
|
|
"""generate(self)
|
|
Inheritors should redefine this to take appropriate action on generate
|
|
"""
|
|
self.activeState = ESGenerating
|
|
|
|
def generateInit(self):
|
|
"""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):
|
|
"""getDoId(self)
|
|
Return the distributed object id
|
|
"""
|
|
return self.doId
|
|
|
|
def updateRequiredFields(self, cdc, di):
|
|
for i in cdc.broadcastRequiredCDU:
|
|
i.updateField(cdc, self, di)
|
|
self.announceGenerate()
|
|
|
|
def updateAllRequiredFields(self, cdc, di):
|
|
for i in cdc.allRequiredCDU:
|
|
i.updateField(cdc, self, di)
|
|
self.announceGenerate()
|
|
|
|
def updateRequiredOtherFields(self, cdc, di):
|
|
# First, update the required fields
|
|
for i in cdc.broadcastRequiredCDU:
|
|
i.updateField(cdc, self, di)
|
|
|
|
# Announce generate after updating all the required fields,
|
|
# but before we update the non-required fields.
|
|
self.announceGenerate()
|
|
|
|
# Determine how many other fields there are
|
|
numberOfOtherFields = di.getArg(STUint16)
|
|
# Update each of the other fields
|
|
for i in range(numberOfOtherFields):
|
|
cdc.updateField(self, di)
|
|
|
|
def sendUpdate(self, fieldName, args = [], sendToId = None):
|
|
self.cr.sendUpdate(self, fieldName, args, sendToId)
|
|
|
|
def taskName(self, taskString):
|
|
return (taskString + "-" + str(self.getDoId()))
|
|
|
|
def uniqueName(self, idString):
|
|
return (idString + "-" + str(self.getDoId()))
|
|
|
|
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 0
|
|
|
|
|
|
|
|
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))
|
|
|