DistributedObject.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  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. """
  15. The Distributed Object class is the base class for all network based
  16. (i.e. distributed) objects. These will usually (always?) have a
  17. dclass entry in a *.dc file.
  18. """
  19. notify = directNotify.newCategory("DistributedObject")
  20. # A few objects will set neverDisable to 1... Examples are
  21. # localToon, and anything that lives in the UberZone. This
  22. # keeps them from being disabled when you change zones,
  23. # even to the quiet zone.
  24. neverDisable = 0
  25. def __init__(self, cr):
  26. assert self.notify.debugStateCall(self)
  27. try:
  28. self.DistributedObject_initialized
  29. except:
  30. self.DistributedObject_initialized = 1
  31. self.cr = cr
  32. if wantOtpServer:
  33. # Location stores the parentId, zoneId of this object
  34. self.__location = (None, None)
  35. # Most DistributedObjects are simple and require no real
  36. # effort to load. Some, particularly actors, may take
  37. # some significant time to load; these we can optimize by
  38. # caching them when they go away instead of necessarily
  39. # deleting them. The object should set cacheable to 1 if
  40. # it needs to be optimized in this way.
  41. self.setCacheable(0)
  42. # This count tells whether the object can be deleted right away,
  43. # or not.
  44. self.delayDeleteCount = 0
  45. # This flag tells whether a delete has been requested on this
  46. # object.
  47. self.deleteImminent = 0
  48. # Keep track of our state as a distributed object. This
  49. # is only trustworthy if the inheriting class properly
  50. # calls up the chain for disable() and generate().
  51. self.activeState = ESNew
  52. # These are used by getCallbackContext() and doCallbackContext().
  53. self.__nextContext = 0
  54. self.__callbacks = {}
  55. # This is used by doneBarrier().
  56. self.__barrierContext = None
  57. #zone of the distributed object, default to 0
  58. self.zone = 0
  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. self.dclass = None
  161. def generate(self):
  162. """
  163. Inheritors should redefine this to take appropriate action on generate
  164. """
  165. assert self.notify.debugStateCall(self)
  166. self.activeState = ESGenerating
  167. def generateInit(self):
  168. """
  169. This method is called when the DistributedObject is first introduced
  170. to the world... Not when it is pulled from the cache.
  171. """
  172. self.activeState = ESGenerating
  173. def getDoId(self):
  174. """
  175. Return the distributed object id
  176. """
  177. return self.doId
  178. def updateRequiredFields(self, dclass, di):
  179. dclass.receiveUpdateBroadcastRequired(self, di)
  180. self.announceGenerate()
  181. def updateAllRequiredFields(self, dclass, di):
  182. dclass.receiveUpdateAllRequired(self, di)
  183. self.announceGenerate()
  184. def updateRequiredOtherFields(self, dclass, di):
  185. # First, update the required fields
  186. dclass.receiveUpdateBroadcastRequired(self, di)
  187. # Announce generate after updating all the required fields,
  188. # but before we update the non-required fields.
  189. self.announceGenerate()
  190. dclass.receiveUpdateOther(self, di)
  191. def sendUpdate(self, fieldName, args = [], sendToId = None):
  192. if self.cr:
  193. self.cr.sendUpdate(self, fieldName, args, sendToId)
  194. def sendDisableMsg(self):
  195. self.cr.sendDisableMsg(self.doId)
  196. def sendDeleteMsg(self):
  197. self.cr.sendDeleteMsg(self.doId)
  198. def taskName(self, taskString):
  199. return (taskString + "-" + str(self.getDoId()))
  200. def uniqueName(self, idString):
  201. return (idString + "-" + str(self.getDoId()))
  202. def getCallbackContext(self, callback, extraArgs = []):
  203. # Some objects implement a back-and-forth handshake operation
  204. # with the AI via an arbitrary context number. This method
  205. # (coupled with doCallbackContext(), below) maps a Python
  206. # callback onto that context number so that client code may
  207. # easily call the method and wait for a callback, rather than
  208. # having to negotiate context numbers.
  209. # This method generates a new context number and stores the
  210. # callback so that it may later be called when the response is
  211. # returned.
  212. # This is intended to be called within derivations of
  213. # DistributedObject, not directly by other objects.
  214. context = self.__nextContext
  215. self.__callbacks[context] = (callback, extraArgs)
  216. # We assume the context number is passed as a uint16.
  217. self.__nextContext = (self.__nextContext + 1) & 0xffff
  218. return context
  219. def getCurrentContexts(self):
  220. # Returns a list of the currently outstanding contexts created
  221. # by getCallbackContext().
  222. return self.__callbacks.keys()
  223. def getCallback(self, context):
  224. # Returns the callback that was passed in to the previous
  225. # call to getCallbackContext.
  226. return self.__callbacks[context][0]
  227. def getCallbackArgs(self, context):
  228. # Returns the extraArgs that were passed in to the previous
  229. # call to getCallbackContext.
  230. return self.__callbacks[context][1]
  231. def doCallbackContext(self, context, args):
  232. # This is called after the AI has responded to the message
  233. # sent via getCallbackContext(), above. The context number is
  234. # looked up in the table and the associated callback is
  235. # issued.
  236. # This is intended to be called within derivations of
  237. # DistributedObject, not directly by other objects.
  238. tuple = self.__callbacks.get(context)
  239. if tuple:
  240. callback, extraArgs = tuple
  241. completeArgs = args + extraArgs
  242. if callback != None:
  243. callback(*completeArgs)
  244. del self.__callbacks[context]
  245. else:
  246. self.notify.warning("Got unexpected context from AI: %s" % (context))
  247. def setBarrierData(self, data):
  248. # This message is sent by the AI to tell us the barriers and
  249. # avIds for which the AI is currently waiting. The client
  250. # needs to look up its pending context in the table (and
  251. # ignore the other contexts). When the client is done
  252. # handling whatever it should handle in its current state, it
  253. # should call doneBarrier(), which will send the context
  254. # number back to the AI.
  255. for context, name, avIds in data:
  256. if base.localAvatar.doId in avIds:
  257. # We found localToon's id; stop here.
  258. self.__barrierContext = (context, name)
  259. assert(self.notify.debug('setBarrierData(%s, %s)' % (context, name)))
  260. return
  261. assert(self.notify.debug('setBarrierData(%s)' % (None)))
  262. self.__barrierContext = None
  263. def doneBarrier(self, name = None):
  264. # Tells the AI we have finished handling our task. If the
  265. # optional name parameter is specified, it must match the
  266. # barrier name specified on the AI, or the barrier is ignored.
  267. # This is used to ensure we are not clearing the wrong
  268. # barrier.
  269. # If this is None, it either means we have called
  270. # doneBarrier() twice, or we have not received a barrier
  271. # context from the AI. I think in either case it's ok to
  272. # silently ignore the error.
  273. if self.__barrierContext != None:
  274. context, aiName = self.__barrierContext
  275. if name == None or name == aiName:
  276. assert(self.notify.debug('doneBarrier(%s, %s)' % (context, aiName)))
  277. self.sendUpdate("setBarrierReady", [context])
  278. self.__barrierContext = None
  279. else:
  280. assert(self.notify.debug('doneBarrier(%s) ignored; current barrier is %s' % (name, aiName)))
  281. else:
  282. assert(self.notify.debug('doneBarrier(%s) ignored; no active barrier.' % (name)))
  283. if wantOtpServer:
  284. def addInterest(self, zoneId, note="", event=None):
  285. self.cr.addInterest(self.getDoId(), zoneId, note, event)
  286. def setLocation(self, parentId, zoneId):
  287. # The store must run first so we know the old location
  288. self.cr.storeObjectLocation(self.doId, parentId, zoneId)
  289. self.__location = (parentId, zoneId)
  290. def getLocation(self):
  291. return self.__location
  292. def isLocal(self):
  293. # This returns true if the distributed object is "local,"
  294. # which means the client created it instead of the AI, and it
  295. # gets some other special handling. Normally, only the local
  296. # avatar class overrides this to return true.
  297. return self.cr and self.cr.isLocalId(self.doId)
  298. def updateZone(self, zoneId):
  299. self.cr.sendUpdateZone(self, zoneId)