DistributedObject.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. """DistributedObject module: contains the DistributedObject class"""
  2. from direct.showbase.PandaObject import *
  3. from direct.directnotify.DirectNotifyGlobal import directNotify
  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. self.children = {}
  33. # Most DistributedObjects are simple and require no real
  34. # effort to load. Some, particularly actors, may take
  35. # some significant time to load; these we can optimize by
  36. # caching them when they go away instead of necessarily
  37. # deleting them. The object should set cacheable to 1 if
  38. # it needs to be optimized in this way.
  39. self.setCacheable(0)
  40. # This count tells whether the object can be deleted right away,
  41. # or not.
  42. self.delayDeleteCount = 0
  43. # This flag tells whether a delete has been requested on this
  44. # object.
  45. self.deleteImminent = 0
  46. # Keep track of our state as a distributed object. This
  47. # is only trustworthy if the inheriting class properly
  48. # calls up the chain for disable() and generate().
  49. self.activeState = ESNew
  50. # These are used by getCallbackContext() and doCallbackContext().
  51. self.__nextContext = 0
  52. self.__callbacks = {}
  53. # This is used by doneBarrier().
  54. self.__barrierContext = None
  55. #zone of the distributed object, default to 0
  56. self.zone = 0
  57. self.parentId = None
  58. self.zoneId = None
  59. if __debug__:
  60. def status(self, indent=0):
  61. """
  62. print out "doId(parentId,zoneId) className
  63. and conditionally show generated, disabled, neverDisable,
  64. or cachable"
  65. """
  66. spaces=' '*(indent+2)
  67. try:
  68. print "%s%s:"%(
  69. ' '*indent, self.__class__.__name__)
  70. print "%sfrom DistributedObject doId:%s, parent:%s, zone:%s"%(
  71. spaces,
  72. self.doId, self.parentId, self.zoneId),
  73. flags=[]
  74. if self.activeState == ESGenerated:
  75. flags.append("generated")
  76. if self.activeState < ESGenerating:
  77. flags.append("disabled")
  78. if self.neverDisable:
  79. flags.append("neverDisable")
  80. if self.cacheable:
  81. flags.append("cacheable")
  82. if len(flags):
  83. print "(%s)"%(" ".join(flags),),
  84. print
  85. except Exception, e: print "%serror printing status"%(spaces,), e
  86. def setNeverDisable(self, bool):
  87. assert((bool == 1) or (bool == 0))
  88. self.neverDisable = bool
  89. def getNeverDisable(self):
  90. return self.neverDisable
  91. def setCacheable(self, bool):
  92. assert((bool == 1) or (bool == 0))
  93. self.cacheable = bool
  94. return None
  95. def getCacheable(self):
  96. return self.cacheable
  97. def deleteOrDelay(self):
  98. if self.delayDeleteCount > 0:
  99. self.deleteImminent = 1
  100. else:
  101. self.disableAnnounceAndDelete()
  102. def delayDelete(self, flag):
  103. # Flag should be 0 or 1, meaning increment or decrement count
  104. # Also see DelayDelete.py
  105. if (flag == 1):
  106. self.delayDeleteCount += 1
  107. elif (flag == 0):
  108. self.delayDeleteCount -= 1
  109. else:
  110. self.notify.error("Invalid flag passed to delayDelete: " + str(flag))
  111. if (self.delayDeleteCount < 0):
  112. self.notify.error("Somebody decremented delayDelete for doId %s without incrementing"
  113. % (self.doId))
  114. elif (self.delayDeleteCount == 0):
  115. assert(self.notify.debug("delayDeleteCount for doId %s now 0"
  116. % (self.doId)))
  117. if self.deleteImminent:
  118. assert(self.notify.debug("delayDeleteCount for doId %s -- deleteImminent"
  119. % (self.doId)))
  120. self.disableAnnounceAndDelete()
  121. else:
  122. self.notify.debug("delayDeleteCount for doId %s now %s"
  123. % (self.doId, self.delayDeleteCount))
  124. # Return the count just for kicks
  125. return self.delayDeleteCount
  126. def disableAnnounceAndDelete(self):
  127. self.disableAndAnnounce()
  128. self.delete()
  129. def disableAndAnnounce(self):
  130. """
  131. Inheritors should *not* redefine this function.
  132. """
  133. # We must send the disable announce message *before* we
  134. # actually disable the object. That way, the various cleanup
  135. # tasks can run first and take care of restoring the object to
  136. # a normal, nondisabled state; and *then* the disable function
  137. # can properly disable it (for instance, by parenting it to
  138. # hidden).
  139. if self.activeState != ESDisabled:
  140. self.activeState = ESDisabling
  141. messenger.send(self.uniqueName("disable"))
  142. self.disable()
  143. def announceGenerate(self):
  144. """
  145. Sends a message to the world after the object has been
  146. generated and all of its required fields filled in.
  147. """
  148. assert(self.notify.debug('announceGenerate(): %s' % (self.doId)))
  149. if self.activeState != ESGenerated:
  150. self.activeState = ESGenerated
  151. messenger.send(self.uniqueName("generate"), [self])
  152. def disable(self):
  153. """
  154. Inheritors should redefine this to take appropriate action on disable
  155. """
  156. assert(self.notify.debug('disable(): %s' % (self.doId)))
  157. if self.activeState != ESDisabled:
  158. self.activeState = ESDisabled
  159. self.__callbacks = {}
  160. if wantOtpServer:
  161. #self.cr.deleteObjectLocation(self.doId, self.parentId, self.zoneId)
  162. self.setLocation(None, None)
  163. # TODO: disable my children
  164. def isDisabled(self):
  165. """
  166. Returns true if the object has been disabled and/or deleted,
  167. or if it is brand new and hasn't yet been generated.
  168. """
  169. return (self.activeState < ESGenerating)
  170. def isGenerated(self):
  171. """
  172. Returns true if the object has been fully generated by now,
  173. and not yet disabled.
  174. """
  175. assert self.notify.debugStateCall(self)
  176. return (self.activeState == ESGenerated)
  177. def delete(self):
  178. """
  179. Inheritors should redefine this to take appropriate action on delete
  180. """
  181. assert(self.notify.debug('delete(): %s' % (self.doId)))
  182. try:
  183. self.DistributedObject_deleted
  184. except:
  185. self.DistributedObject_deleted = 1
  186. self.cr = None
  187. self.dclass = None
  188. def generate(self):
  189. """
  190. Inheritors should redefine this to take appropriate action on generate
  191. """
  192. assert self.notify.debugStateCall(self)
  193. self.activeState = ESGenerating
  194. # this has already been set at this point
  195. #self.cr.storeObjectLocation(self.doId, self.parentId, self.zoneId)
  196. def generateInit(self):
  197. """
  198. This method is called when the DistributedObject is first introduced
  199. to the world... Not when it is pulled from the cache.
  200. """
  201. self.activeState = ESGenerating
  202. def getDoId(self):
  203. """
  204. Return the distributed object id
  205. """
  206. return self.doId
  207. def updateRequiredFields(self, dclass, di):
  208. dclass.receiveUpdateBroadcastRequired(self, di)
  209. self.announceGenerate()
  210. def updateAllRequiredFields(self, dclass, di):
  211. dclass.receiveUpdateAllRequired(self, di)
  212. self.announceGenerate()
  213. def updateRequiredOtherFields(self, dclass, di):
  214. # First, update the required fields
  215. dclass.receiveUpdateBroadcastRequired(self, di)
  216. # Announce generate after updating all the required fields,
  217. # but before we update the non-required fields.
  218. self.announceGenerate()
  219. dclass.receiveUpdateOther(self, di)
  220. def sendUpdate(self, fieldName, args = [], sendToId = None):
  221. if self.cr:
  222. dg = self.dclass.clientFormatUpdate(
  223. fieldName, sendToId or self.doId, args)
  224. self.cr.send(dg)
  225. else:
  226. self.notify.warning("sendUpdate failed, because self.cr is not set")
  227. def sendDisableMsg(self):
  228. self.cr.sendDisableMsg(self.doId)
  229. def sendDeleteMsg(self):
  230. self.cr.sendDeleteMsg(self.doId)
  231. def taskName(self, taskString):
  232. return (taskString + "-" + str(self.getDoId()))
  233. def uniqueName(self, idString):
  234. return (idString + "-" + str(self.getDoId()))
  235. def getCallbackContext(self, callback, extraArgs = []):
  236. # Some objects implement a back-and-forth handshake operation
  237. # with the AI via an arbitrary context number. This method
  238. # (coupled with doCallbackContext(), below) maps a Python
  239. # callback onto that context number so that client code may
  240. # easily call the method and wait for a callback, rather than
  241. # having to negotiate context numbers.
  242. # This method generates a new context number and stores the
  243. # callback so that it may later be called when the response is
  244. # returned.
  245. # This is intended to be called within derivations of
  246. # DistributedObject, not directly by other objects.
  247. context = self.__nextContext
  248. self.__callbacks[context] = (callback, extraArgs)
  249. # We assume the context number is passed as a uint16.
  250. self.__nextContext = (self.__nextContext + 1) & 0xffff
  251. return context
  252. def getCurrentContexts(self):
  253. # Returns a list of the currently outstanding contexts created
  254. # by getCallbackContext().
  255. return self.__callbacks.keys()
  256. def getCallback(self, context):
  257. # Returns the callback that was passed in to the previous
  258. # call to getCallbackContext.
  259. return self.__callbacks[context][0]
  260. def getCallbackArgs(self, context):
  261. # Returns the extraArgs that were passed in to the previous
  262. # call to getCallbackContext.
  263. return self.__callbacks[context][1]
  264. def doCallbackContext(self, context, args):
  265. # This is called after the AI has responded to the message
  266. # sent via getCallbackContext(), above. The context number is
  267. # looked up in the table and the associated callback is
  268. # issued.
  269. # This is intended to be called within derivations of
  270. # DistributedObject, not directly by other objects.
  271. tuple = self.__callbacks.get(context)
  272. if tuple:
  273. callback, extraArgs = tuple
  274. completeArgs = args + extraArgs
  275. if callback != None:
  276. callback(*completeArgs)
  277. del self.__callbacks[context]
  278. else:
  279. self.notify.warning("Got unexpected context from AI: %s" % (context))
  280. def setBarrierData(self, data):
  281. # This message is sent by the AI to tell us the barriers and
  282. # avIds for which the AI is currently waiting. The client
  283. # needs to look up its pending context in the table (and
  284. # ignore the other contexts). When the client is done
  285. # handling whatever it should handle in its current state, it
  286. # should call doneBarrier(), which will send the context
  287. # number back to the AI.
  288. for context, name, avIds in data:
  289. if base.localAvatar.doId in avIds:
  290. # We found localToon's id; stop here.
  291. self.__barrierContext = (context, name)
  292. assert(self.notify.debug('setBarrierData(%s, %s)' % (context, name)))
  293. return
  294. assert(self.notify.debug('setBarrierData(%s)' % (None)))
  295. self.__barrierContext = None
  296. def doneBarrier(self, name = None):
  297. # Tells the AI we have finished handling our task. If the
  298. # optional name parameter is specified, it must match the
  299. # barrier name specified on the AI, or the barrier is ignored.
  300. # This is used to ensure we are not clearing the wrong
  301. # barrier.
  302. # If this is None, it either means we have called
  303. # doneBarrier() twice, or we have not received a barrier
  304. # context from the AI. I think in either case it's ok to
  305. # silently ignore the error.
  306. if self.__barrierContext != None:
  307. context, aiName = self.__barrierContext
  308. if name == None or name == aiName:
  309. assert(self.notify.debug('doneBarrier(%s, %s)' % (context, aiName)))
  310. self.sendUpdate("setBarrierReady", [context])
  311. self.__barrierContext = None
  312. else:
  313. assert(self.notify.debug('doneBarrier(%s) ignored; current barrier is %s' % (name, aiName)))
  314. else:
  315. assert(self.notify.debug('doneBarrier(%s) ignored; no active barrier.' % (name)))
  316. if wantOtpServer:
  317. def addInterest(self, zoneId, note="", event=None):
  318. self.cr.addInterest(self.getDoId(), zoneId, note, event)
  319. def b_setLocation(self, parentId, zoneId):
  320. self.d_setLocation(parentId, zoneId)
  321. self.setLocation(parentId, zoneId)
  322. def d_setLocation(self, parentId, zoneId):
  323. self.cr.sendSetLocation(self.doId, parentId, zoneId)
  324. def setLocation(self, parentId, zoneId):
  325. #self.notify.info("setLocation: %s parentId: %s zoneId: %s" % (self.doId, parentId, zoneId))
  326. # parentId can be 'None', e.g. when an object is being disabled
  327. oldParentId = self.parentId
  328. oldZoneId = self.zoneId
  329. parentIsNew = (oldParentId != parentId)
  330. # notify any existing parent that we're moving away
  331. if (oldParentId is not None) and parentIsNew:
  332. oldParentObj = self.cr.doId2do.get(oldParentId)
  333. if oldParentObj:
  334. oldParentObj.handleChildLeave(self, oldZoneId)
  335. # The store must run first so we know the old location
  336. self.parentId = parentId
  337. self.zoneId = zoneId
  338. self.cr.storeObjectLocation(self.doId, parentId, zoneId)
  339. # Give the parent a chance to run code when a new child
  340. # sets location to it. For example, the parent may want to
  341. # scene graph reparent the child to some subnode it owns.
  342. if (self.parentId is not None) and parentIsNew:
  343. parentObj = self.cr.doId2do.get(parentId)
  344. if parentObj:
  345. parentObj.handleChildArrive(self, zoneId)
  346. def getLocation(self):
  347. try:
  348. if self.parentId == 0 and self.zoneId == 0:
  349. return None
  350. # This is a -1 stuffed into a uint32
  351. if self.parentId == 0xffffffff and self.zoneId == 0xffffffff:
  352. return None
  353. return (self.parentId, self.zoneId)
  354. except AttributeError:
  355. return None
  356. def handleChildArrive(self, childObj, zoneId):
  357. self.notify.debugCall()
  358. # A new child has just setLocation beneath us. Give us a
  359. # chance to run code when a new child sets location to us. For
  360. # example, we may want to scene graph reparent the child to
  361. # some subnode we own.
  362. ## zone=self.children.setdefault(zoneId, {})
  363. ## zone[childObj.doId]=childObj
  364. # Inheritors should override
  365. pass
  366. def handleChildLeave(self, childObj, zoneId):
  367. self.notify.debugCall()
  368. # A child is about to setLocation away from us. Give us a
  369. # chance to run code just before a child sets location away from us.
  370. ## zone=self.children[zoneId]
  371. ## del zone[childObj.doId]
  372. ## if not len(zone):
  373. ## del self.children[zoneId]
  374. # Inheritors should override
  375. pass
  376. def getParentObj(self):
  377. if self.parentId is None:
  378. return None
  379. return self.cr.doId2do.get(self.parentId)
  380. def isLocal(self):
  381. # This returns true if the distributed object is "local,"
  382. # which means the client created it instead of the AI, and it
  383. # gets some other special handling. Normally, only the local
  384. # avatar class overrides this to return true.
  385. return self.cr and self.cr.isLocalId(self.doId)
  386. def updateZone(self, zoneId):
  387. self.cr.sendUpdateZone(self, zoneId)
  388. def isGridParent(self):
  389. # If this distributed object is a DistributedGrid return 1. 0 by default
  390. return 0