DistributedObject.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. """DistributedObject module: contains the DistributedObject class"""
  2. from direct.showbase.PandaObject import *
  3. from direct.directnotify.DirectNotifyGlobal import *
  4. from PyDatagram import PyDatagram
  5. from PyDatagramIterator import PyDatagramIterator
  6. # Values for DistributedObject.activeState
  7. ESNew = 1
  8. ESDeleted = 2
  9. ESDisabling = 3
  10. ESDisabled = 4 # values here and lower are considered "disabled"
  11. ESGenerating = 5 # values here and greater are considered "generated"
  12. ESGenerated = 6
  13. class DistributedObject(PandaObject):
  14. """Distributed Object class:"""
  15. notify = directNotify.newCategory("DistributedObject")
  16. # A few objects will set neverDisable to 1... Examples are
  17. # localToon, and anything that lives in the UberZone. This
  18. # keeps them from being disabled when you change zones,
  19. # even to the quiet zone.
  20. neverDisable = 0
  21. def __init__(self, cr):
  22. assert self.notify.debugStateCall(self)
  23. try:
  24. self.DistributedObject_initialized
  25. except:
  26. self.DistributedObject_initialized = 1
  27. self.cr = cr
  28. if wantOtpServer:
  29. # Location stores the parentId, zoneId of this object
  30. self.__location = (None, None)
  31. # Most DistributedObjects are simple and require no real
  32. # effort to load. Some, particularly actors, may take
  33. # some significant time to load; these we can optimize by
  34. # caching them when they go away instead of necessarily
  35. # deleting them. The object should set cacheable to 1 if
  36. # it needs to be optimized in this way.
  37. self.setCacheable(0)
  38. # This count tells whether the object can be deleted right away,
  39. # or not.
  40. self.delayDeleteCount = 0
  41. # This flag tells whether a delete has been requested on this
  42. # object.
  43. self.deleteImminent = 0
  44. # Keep track of our state as a distributed object. This
  45. # is only trustworthy if the inheriting class properly
  46. # calls up the chain for disable() and generate().
  47. self.activeState = ESNew
  48. # These are used by getCallbackContext() and doCallbackContext().
  49. self.__nextContext = 0
  50. self.__callbacks = {}
  51. # This is used by doneBarrier().
  52. self.__barrierContext = None
  53. #def __del__(self):
  54. # """
  55. # For debugging purposes, this just prints out what got deleted
  56. # """
  57. # print ("Destructing: " + self.__class__.__name__ + " id: " + str(self.doId))
  58. # PandaObject.__del__(self)
  59. def setNeverDisable(self, bool):
  60. assert((bool == 1) or (bool == 0))
  61. self.neverDisable = bool
  62. def getNeverDisable(self):
  63. return self.neverDisable
  64. def setCacheable(self, bool):
  65. assert((bool == 1) or (bool == 0))
  66. self.cacheable = bool
  67. return None
  68. def getCacheable(self):
  69. return self.cacheable
  70. def deleteOrDelay(self):
  71. if self.delayDeleteCount > 0:
  72. self.deleteImminent = 1
  73. else:
  74. self.disableAnnounceAndDelete()
  75. def delayDelete(self, flag):
  76. # Flag should be 0 or 1, meaning increment or decrement count
  77. # Also see DelayDelete.py
  78. if (flag == 1):
  79. self.delayDeleteCount += 1
  80. elif (flag == 0):
  81. self.delayDeleteCount -= 1
  82. else:
  83. self.notify.error("Invalid flag passed to delayDelete: " + str(flag))
  84. if (self.delayDeleteCount < 0):
  85. self.notify.error("Somebody decremented delayDelete for doId %s without incrementing"
  86. % (self.doId))
  87. elif (self.delayDeleteCount == 0):
  88. assert(self.notify.debug("delayDeleteCount for doId %s now 0"
  89. % (self.doId)))
  90. if self.deleteImminent:
  91. assert(self.notify.debug("delayDeleteCount for doId %s -- deleteImminent"
  92. % (self.doId)))
  93. self.disableAnnounceAndDelete()
  94. else:
  95. self.notify.debug("delayDeleteCount for doId %s now %s"
  96. % (self.doId, self.delayDeleteCount))
  97. # Return the count just for kicks
  98. return self.delayDeleteCount
  99. def disableAnnounceAndDelete(self):
  100. self.disableAndAnnounce()
  101. self.delete()
  102. def disableAndAnnounce(self):
  103. """
  104. Inheritors should *not* redefine this function.
  105. """
  106. # We must send the disable announce message *before* we
  107. # actually disable the object. That way, the various cleanup
  108. # tasks can run first and take care of restoring the object to
  109. # a normal, nondisabled state; and *then* the disable function
  110. # can properly disable it (for instance, by parenting it to
  111. # hidden).
  112. if self.activeState != ESDisabled:
  113. self.activeState = ESDisabling
  114. messenger.send(self.uniqueName("disable"))
  115. self.disable()
  116. def announceGenerate(self):
  117. """
  118. Sends a message to the world after the object has been
  119. generated and all of its required fields filled in.
  120. """
  121. assert(self.notify.debug('announceGenerate(): %s' % (self.doId)))
  122. if self.activeState != ESGenerated:
  123. self.activeState = ESGenerated
  124. messenger.send(self.uniqueName("generate"), [self])
  125. def disable(self):
  126. """
  127. Inheritors should redefine this to take appropriate action on disable
  128. """
  129. assert(self.notify.debug('disable(): %s' % (self.doId)))
  130. if self.activeState != ESDisabled:
  131. self.activeState = ESDisabled
  132. self.__callbacks = {}
  133. if wantOtpServer:
  134. self.cr.deleteObjectLocation(self.doId, self.__location[0], self.__location[1])
  135. self.__location = (None, None)
  136. # TODO: disable my children
  137. def isDisabled(self):
  138. """
  139. Returns true if the object has been disabled and/or deleted,
  140. or if it is brand new and hasn't yet been generated.
  141. """
  142. return (self.activeState < ESGenerating)
  143. def isGenerated(self):
  144. """
  145. Returns true if the object has been fully generated by now,
  146. and not yet disabled.
  147. """
  148. assert self.notify.debugStateCall(self)
  149. return (self.activeState == ESGenerated)
  150. def delete(self):
  151. """
  152. Inheritors should redefine this to take appropriate action on delete
  153. """
  154. assert(self.notify.debug('delete(): %s' % (self.doId)))
  155. try:
  156. self.DistributedObject_deleted
  157. except:
  158. self.DistributedObject_deleted = 1
  159. self.cr = None
  160. def generate(self):
  161. """
  162. Inheritors should redefine this to take appropriate action on generate
  163. """
  164. assert self.notify.debugStateCall(self)
  165. self.activeState = ESGenerating
  166. def generateInit(self):
  167. """
  168. This method is called when the DistributedObject is first introduced
  169. to the world... Not when it is pulled from the cache.
  170. """
  171. self.activeState = ESGenerating
  172. def getDoId(self):
  173. """
  174. Return the distributed object id
  175. """
  176. return self.doId
  177. def updateRequiredFields(self, dclass, di):
  178. dclass.receiveUpdateBroadcastRequired(self, di)
  179. self.announceGenerate()
  180. def updateAllRequiredFields(self, dclass, di):
  181. dclass.receiveUpdateAllRequired(self, di)
  182. self.announceGenerate()
  183. def updateRequiredOtherFields(self, dclass, di):
  184. # First, update the required fields
  185. dclass.receiveUpdateBroadcastRequired(self, di)
  186. # Announce generate after updating all the required fields,
  187. # but before we update the non-required fields.
  188. self.announceGenerate()
  189. dclass.receiveUpdateOther(self, di)
  190. def sendUpdate(self, fieldName, args = [], sendToId = None):
  191. if self.cr:
  192. self.cr.sendUpdate(self, fieldName, args, sendToId)
  193. def taskName(self, taskString):
  194. return (taskString + "-" + str(self.getDoId()))
  195. def uniqueName(self, idString):
  196. return (idString + "-" + str(self.getDoId()))
  197. def isLocal(self):
  198. # This returns true if the distributed object is "local,"
  199. # which means the client created it instead of the AI, and it
  200. # gets some other special handling. Normally, only the local
  201. # avatar class overrides this to return true.
  202. return 0
  203. def getCallbackContext(self, callback, extraArgs = []):
  204. # Some objects implement a back-and-forth handshake operation
  205. # with the AI via an arbitrary context number. This method
  206. # (coupled with doCallbackContext(), below) maps a Python
  207. # callback onto that context number so that client code may
  208. # easily call the method and wait for a callback, rather than
  209. # having to negotiate context numbers.
  210. # This method generates a new context number and stores the
  211. # callback so that it may later be called when the response is
  212. # returned.
  213. # This is intended to be called within derivations of
  214. # DistributedObject, not directly by other objects.
  215. context = self.__nextContext
  216. self.__callbacks[context] = (callback, extraArgs)
  217. # We assume the context number is passed as a uint16.
  218. self.__nextContext = (self.__nextContext + 1) & 0xffff
  219. return context
  220. def getCurrentContexts(self):
  221. # Returns a list of the currently outstanding contexts created
  222. # by getCallbackContext().
  223. return self.__callbacks.keys()
  224. def getCallback(self, context):
  225. # Returns the callback that was passed in to the previous
  226. # call to getCallbackContext.
  227. return self.__callbacks[context][0]
  228. def getCallbackArgs(self, context):
  229. # Returns the extraArgs that were passed in to the previous
  230. # call to getCallbackContext.
  231. return self.__callbacks[context][1]
  232. def doCallbackContext(self, context, args):
  233. # This is called after the AI has responded to the message
  234. # sent via getCallbackContext(), above. The context number is
  235. # looked up in the table and the associated callback is
  236. # issued.
  237. # This is intended to be called within derivations of
  238. # DistributedObject, not directly by other objects.
  239. tuple = self.__callbacks.get(context)
  240. if tuple:
  241. callback, extraArgs = tuple
  242. completeArgs = args + extraArgs
  243. if callback != None:
  244. callback(*completeArgs)
  245. del self.__callbacks[context]
  246. else:
  247. self.notify.warning("Got unexpected context from AI: %s" % (context))
  248. def setBarrierData(self, data):
  249. # This message is sent by the AI to tell us the barriers and
  250. # avIds for which the AI is currently waiting. The client
  251. # needs to look up its pending context in the table (and
  252. # ignore the other contexts). When the client is done
  253. # handling whatever it should handle in its current state, it
  254. # should call doneBarrier(), which will send the context
  255. # number back to the AI.
  256. dg = PyDatagram(data)
  257. dgi = PyDatagramIterator(dg)
  258. while dgi.getRemainingSize() > 0:
  259. context = dgi.getUint16()
  260. numToons = dgi.getUint16()
  261. for i in range(numToons):
  262. avId = dgi.getUint32()
  263. if avId == base.localAvatar.doId:
  264. # We found localToon's Id; stop here.
  265. self.__barrierContext = context
  266. assert(self.notify.debug('setBarrierData(%s)' % (context)))
  267. return
  268. assert(self.notify.debug('setBarrierData(%s)' % (None)))
  269. self.__barrierContext = None
  270. def doneBarrier(self):
  271. # Tells the AI we have finished handling our task.
  272. assert(self.notify.debug('doneBarrier(%s)' % (self.__barrierContext)))
  273. # If this is None, it either means we have called
  274. # doneBarrier() twice, or we have not received a barrier
  275. # context from the AI. I think in either case it's ok to
  276. # silently ignore the error.
  277. if self.__barrierContext != None:
  278. self.sendUpdate("setBarrierReady", [self.__barrierContext])
  279. self.__barrierContext = None
  280. if wantOtpServer:
  281. def addInterest(self, zoneId, note=""):
  282. self.cr.addInterest(self.getDoId(), zoneId, note)
  283. def setLocation(self, parentId, zoneId):
  284. # The store must run first so we know the old location
  285. self.cr.storeObjectLocation(self.doId, parentId, zoneId)
  286. self.__location = (parentId, zoneId)
  287. def getLocation(self):
  288. return self.__location