DistributedObject.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. """DistributedObject module: contains the DistributedObject class"""
  2. from pandac.PandaModules import *
  3. from direct.directnotify.DirectNotifyGlobal import directNotify
  4. from direct.distributed.DistributedObjectBase import DistributedObjectBase
  5. #from PyDatagram import PyDatagram
  6. #from PyDatagramIterator import PyDatagramIterator
  7. # Values for DistributedObject.activeState
  8. ESNew = 1
  9. ESDeleted = 2
  10. ESDisabling = 3
  11. ESDisabled = 4 # values here and lower are considered "disabled"
  12. ESGenerating = 5 # values here and greater are considered "generated"
  13. ESGenerated = 6
  14. # update this table if the values above change
  15. ESNum2Str = {
  16. ESNew: 'ESNew',
  17. ESDeleted: 'ESDeleted',
  18. ESDisabling: 'ESDisabling',
  19. ESDisabled: 'ESDisabed',
  20. ESGenerating: 'ESGenerating',
  21. ESGenerated: 'ESGenerated',
  22. }
  23. class DistributedObject(DistributedObjectBase):
  24. """
  25. The Distributed Object class is the base class for all network based
  26. (i.e. distributed) objects. These will usually (always?) have a
  27. dclass entry in a *.dc file.
  28. """
  29. notify = directNotify.newCategory("DistributedObject")
  30. # A few objects will set neverDisable to 1... Examples are
  31. # localToon, and anything that lives in the UberZone. This
  32. # keeps them from being disabled when you change zones,
  33. # even to the quiet zone.
  34. neverDisable = 0
  35. DelayDeleteSerialGen = SerialNumGen()
  36. def __init__(self, cr):
  37. assert self.notify.debugStateCall(self)
  38. try:
  39. self.DistributedObject_initialized
  40. except:
  41. self.DistributedObject_initialized = 1
  42. DistributedObjectBase.__init__(self, cr)
  43. # Most DistributedObjects are simple and require no real
  44. # effort to load. Some, particularly actors, may take
  45. # some significant time to load; these we can optimize by
  46. # caching them when they go away instead of necessarily
  47. # deleting them. The object should set cacheable to 1 if
  48. # it needs to be optimized in this way.
  49. self.setCacheable(0)
  50. self._token2delayDeleteName = {}
  51. # This flag tells whether a delete has been requested on this
  52. # object.
  53. self.deleteImminent = 0
  54. # Keep track of our state as a distributed object. This
  55. # is only trustworthy if the inheriting class properly
  56. # calls up the chain for disable() and generate().
  57. self.activeState = ESNew
  58. # These are used by getCallbackContext() and doCallbackContext().
  59. self.__nextContext = 0
  60. self.__callbacks = {}
  61. # This is used by doneBarrier().
  62. self.__barrierContext = None
  63. ## TODO: This should probably be move to a derived class for CMU
  64. ## #zone of the distributed object, default to 0
  65. ## self.zone = 0
  66. if __debug__:
  67. def status(self, indent=0):
  68. """
  69. print out "doId(parentId, zoneId) className
  70. and conditionally show generated, disabled, neverDisable,
  71. or cachable"
  72. """
  73. spaces=' '*(indent+2)
  74. try:
  75. print "%s%s:"%(
  76. ' '*indent, self.__class__.__name__)
  77. print "%sfrom DistributedObject doId:%s, parent:%s, zone:%s"%(
  78. spaces,
  79. self.doId, self.parentId, self.zoneId),
  80. flags=[]
  81. if self.activeState == ESGenerated:
  82. flags.append("generated")
  83. if self.activeState < ESGenerating:
  84. flags.append("disabled")
  85. if self.neverDisable:
  86. flags.append("neverDisable")
  87. if self.cacheable:
  88. flags.append("cacheable")
  89. if len(flags):
  90. print "(%s)"%(" ".join(flags),),
  91. print
  92. except Exception, e: print "%serror printing status"%(spaces,), e
  93. def getAutoInterests(self):
  94. # returns the sub-zones under this object that are automatically
  95. # opened for us by the server.
  96. # have we already cached it?
  97. def _getAutoInterests(cls):
  98. # returns set of auto-interests for this class and all derived
  99. # have we already computed this class's autoInterests?
  100. if 'autoInterests' in cls.__dict__:
  101. autoInterests = cls.autoInterests
  102. else:
  103. autoInterests = set()
  104. # grab autoInterests from base classes
  105. for base in cls.__bases__:
  106. autoInterests.update(_getAutoInterests(base))
  107. # grab autoInterests from this class
  108. if cls.__name__ in self.cr.dclassesByName:
  109. dclass = self.cr.dclassesByName[cls.__name__]
  110. field = dclass.getFieldByName('AutoInterest')
  111. if field is not None:
  112. p = DCPacker()
  113. p.setUnpackData(field.getDefaultValue())
  114. len = p.rawUnpackUint16()/4
  115. for i in xrange(len):
  116. zone = int(p.rawUnpackUint32())
  117. autoInterests.add(zone)
  118. autoInterests.update(autoInterests)
  119. cls.autoInterests = autoInterests
  120. return set(autoInterests)
  121. autoInterests = _getAutoInterests(self.__class__)
  122. # if the server starts supporting multiple auto-interest per class, this check
  123. # should be removed
  124. if len(autoInterests) > 1:
  125. self.notify.error(
  126. 'only one auto-interest allowed per DC class, %s has %s autoInterests (%s)' %
  127. (self.dclass.getName(), len(autoInterests), list(autoInterests)))
  128. _getAutoInterests = None
  129. return list(autoInterests)
  130. def setNeverDisable(self, bool):
  131. assert bool == 1 or bool == 0
  132. self.neverDisable = bool
  133. def getNeverDisable(self):
  134. return self.neverDisable
  135. def setCacheable(self, bool):
  136. assert bool == 1 or bool == 0
  137. self.cacheable = bool
  138. def getCacheable(self):
  139. return self.cacheable
  140. def deleteOrDelay(self):
  141. if len(self._token2delayDeleteName) > 0:
  142. self.deleteImminent = 1
  143. # Object is delayDeleted. Clean up distributedObject state,
  144. # remove from repository tables, so that we won't crash if
  145. # another instance of the same object gets generated while
  146. # this instance is still delayDeleted.
  147. messenger.send(self.getDisableEvent())
  148. self.activeState = ESDisabled
  149. self._deactivate()
  150. else:
  151. self.disableAnnounceAndDelete()
  152. def getDelayDeleteCount(self):
  153. return len(self._token2delayDeleteName)
  154. def acquireDelayDelete(self, name):
  155. # Also see DelayDelete.py
  156. if self.activeState not in (ESGenerating, ESGenerated):
  157. self.notify.error(
  158. 'cannot acquire DelayDelete "%s" on %s because it is in state %s' % (
  159. name, self.__class__.__name__, ESNum2Str[self.activeState]))
  160. if self.getDelayDeleteCount() == 0:
  161. self.cr._addDelayDeletedDO(self)
  162. token = DistributedObject.DelayDeleteSerialGen.next()
  163. self._token2delayDeleteName[token] = name
  164. assert self.notify.debug(
  165. "delayDelete count for doId %s now %s" %
  166. (self.doId, len(self._token2delayDeleteName)))
  167. # Return the token, user must pass token to releaseDelayDelete
  168. return token
  169. def releaseDelayDelete(self, token):
  170. name = self._token2delayDeleteName.pop(token)
  171. assert self.notify.debug("releasing delayDelete '%s'" % name)
  172. if len(self._token2delayDeleteName) == 0:
  173. assert self.notify.debug(
  174. "delayDelete count for doId %s now 0" % (self.doId))
  175. self.cr._removeDelayDeletedDO(self)
  176. if self.deleteImminent:
  177. assert self.notify.debug(
  178. "delayDelete count for doId %s -- deleteImminent" %
  179. (self.doId))
  180. self.disable()
  181. self.delete()
  182. self._destroyDO()
  183. def getDelayDeleteNames(self):
  184. return self._token2delayDeleteName.values()
  185. def disableAnnounceAndDelete(self):
  186. self.disableAndAnnounce()
  187. self.delete()
  188. self._destroyDO()
  189. def getDisableEvent(self):
  190. return self.uniqueName("disable")
  191. def disableAndAnnounce(self):
  192. """
  193. Inheritors should *not* redefine this function.
  194. """
  195. # We must send the disable announce message *before* we
  196. # actually disable the object. That way, the various cleanup
  197. # tasks can run first and take care of restoring the object to
  198. # a normal, nondisabled state; and *then* the disable function
  199. # can properly disable it (for instance, by parenting it to
  200. # hidden).
  201. if self.activeState != ESDisabled:
  202. self.activeState = ESDisabling
  203. messenger.send(self.getDisableEvent())
  204. self.disable()
  205. self.activeState = ESDisabled
  206. self._deactivate()
  207. def announceGenerate(self):
  208. """
  209. Sends a message to the world after the object has been
  210. generated and all of its required fields filled in.
  211. """
  212. assert self.notify.debug('announceGenerate(): %s' % (self.doId))
  213. def _deactivate(self):
  214. # after this is called, the object is no longer an active DistributedObject
  215. # and it may be placed in the cache
  216. self.__callbacks = {}
  217. self.cr.closeAutoInterests(self)
  218. self.setLocation(0,0)
  219. self.cr.deleteObjectLocation(self, self.parentId, self.zoneId)
  220. def _destroyDO(self):
  221. # after this is called, the object is no longer a DistributedObject
  222. # but may still be used as a DelayDeleted object
  223. self.cr = None
  224. self.dclass = None
  225. def disable(self):
  226. """
  227. Inheritors should redefine this to take appropriate action on disable
  228. """
  229. assert self.notify.debug('disable(): %s' % (self.doId))
  230. pass
  231. def isDisabled(self):
  232. """
  233. Returns true if the object has been disabled and/or deleted,
  234. or if it is brand new and hasn't yet been generated.
  235. """
  236. return (self.activeState < ESGenerating)
  237. def isGenerated(self):
  238. """
  239. Returns true if the object has been fully generated by now,
  240. and not yet disabled.
  241. """
  242. assert self.notify.debugStateCall(self)
  243. return (self.activeState == ESGenerated)
  244. def delete(self):
  245. """
  246. Inheritors should redefine this to take appropriate action on delete
  247. """
  248. assert self.notify.debug('delete(): %s' % (self.doId))
  249. try:
  250. self.DistributedObject_deleted
  251. except:
  252. self.DistributedObject_deleted = 1
  253. def generate(self):
  254. """
  255. Inheritors should redefine this to take appropriate action on generate
  256. """
  257. assert self.notify.debugStateCall(self)
  258. self.activeState = ESGenerating
  259. # this has already been set at this point
  260. #self.cr.storeObjectLocation(self, self.parentId, self.zoneId)
  261. # semi-hack: we seem to be calling generate() more than once for objects that multiply-inherit
  262. if not hasattr(self, '_autoInterestHandle'):
  263. self.cr.openAutoInterests(self)
  264. def generateInit(self):
  265. """
  266. This method is called when the DistributedObject is first introduced
  267. to the world... Not when it is pulled from the cache.
  268. """
  269. self.activeState = ESGenerating
  270. def getDoId(self):
  271. """
  272. Return the distributed object id
  273. """
  274. return self.doId
  275. #This message was moved out of announce generate
  276. #to avoid ordering issues.
  277. def postGenerateMessage(self):
  278. if self.activeState != ESGenerated:
  279. self.activeState = ESGenerated
  280. messenger.send(self.uniqueName("generate"), [self])
  281. def updateRequiredFields(self, dclass, di):
  282. dclass.receiveUpdateBroadcastRequired(self, di)
  283. self.announceGenerate()
  284. self.postGenerateMessage()
  285. def updateAllRequiredFields(self, dclass, di):
  286. dclass.receiveUpdateAllRequired(self, di)
  287. self.announceGenerate()
  288. self.postGenerateMessage()
  289. def updateRequiredOtherFields(self, dclass, di):
  290. # First, update the required fields
  291. dclass.receiveUpdateBroadcastRequired(self, di)
  292. # Announce generate after updating all the required fields,
  293. # but before we update the non-required fields.
  294. self.announceGenerate()
  295. self.postGenerateMessage()
  296. dclass.receiveUpdateOther(self, di)
  297. def sendUpdate(self, fieldName, args = [], sendToId = None):
  298. if self.cr:
  299. dg = self.dclass.clientFormatUpdate(
  300. fieldName, sendToId or self.doId, args)
  301. self.cr.send(dg)
  302. else:
  303. self.notify.warning("sendUpdate failed, because self.cr is not set")
  304. def sendDisableMsg(self):
  305. self.cr.sendDisableMsg(self.doId)
  306. def sendDeleteMsg(self):
  307. self.cr.sendDeleteMsg(self.doId)
  308. def taskName(self, taskString):
  309. return ("%s-%s" % (taskString, self.doId))
  310. def uniqueName(self, idString):
  311. return ("%s-%s" % (idString, self.doId))
  312. def getCallbackContext(self, callback, extraArgs = []):
  313. # Some objects implement a back-and-forth handshake operation
  314. # with the AI via an arbitrary context number. This method
  315. # (coupled with doCallbackContext(), below) maps a Python
  316. # callback onto that context number so that client code may
  317. # easily call the method and wait for a callback, rather than
  318. # having to negotiate context numbers.
  319. # This method generates a new context number and stores the
  320. # callback so that it may later be called when the response is
  321. # returned.
  322. # This is intended to be called within derivations of
  323. # DistributedObject, not directly by other objects.
  324. context = self.__nextContext
  325. self.__callbacks[context] = (callback, extraArgs)
  326. # We assume the context number is passed as a uint16.
  327. self.__nextContext = (self.__nextContext + 1) & 0xffff
  328. return context
  329. def getCurrentContexts(self):
  330. # Returns a list of the currently outstanding contexts created
  331. # by getCallbackContext().
  332. return self.__callbacks.keys()
  333. def getCallback(self, context):
  334. # Returns the callback that was passed in to the previous
  335. # call to getCallbackContext.
  336. return self.__callbacks[context][0]
  337. def getCallbackArgs(self, context):
  338. # Returns the extraArgs that were passed in to the previous
  339. # call to getCallbackContext.
  340. return self.__callbacks[context][1]
  341. def doCallbackContext(self, context, args):
  342. # This is called after the AI has responded to the message
  343. # sent via getCallbackContext(), above. The context number is
  344. # looked up in the table and the associated callback is
  345. # issued.
  346. # This is intended to be called within derivations of
  347. # DistributedObject, not directly by other objects.
  348. tuple = self.__callbacks.get(context)
  349. if tuple:
  350. callback, extraArgs = tuple
  351. completeArgs = args + extraArgs
  352. if callback != None:
  353. callback(*completeArgs)
  354. del self.__callbacks[context]
  355. else:
  356. self.notify.warning("Got unexpected context from AI: %s" % (context))
  357. def setBarrierData(self, data):
  358. # This message is sent by the AI to tell us the barriers and
  359. # avIds for which the AI is currently waiting. The client
  360. # needs to look up its pending context in the table (and
  361. # ignore the other contexts). When the client is done
  362. # handling whatever it should handle in its current state, it
  363. # should call doneBarrier(), which will send the context
  364. # number back to the AI.
  365. for context, name, avIds in data:
  366. if base.localAvatar.doId in avIds:
  367. # We found localToon's id; stop here.
  368. self.__barrierContext = (context, name)
  369. assert self.notify.debug('setBarrierData(%s, %s)' % (context, name))
  370. return
  371. assert self.notify.debug('setBarrierData(%s)' % (None))
  372. self.__barrierContext = None
  373. def doneBarrier(self, name = None):
  374. # Tells the AI we have finished handling our task. If the
  375. # optional name parameter is specified, it must match the
  376. # barrier name specified on the AI, or the barrier is ignored.
  377. # This is used to ensure we are not clearing the wrong
  378. # barrier.
  379. # If this is None, it either means we have called
  380. # doneBarrier() twice, or we have not received a barrier
  381. # context from the AI. I think in either case it's ok to
  382. # silently ignore the error.
  383. if self.__barrierContext != None:
  384. context, aiName = self.__barrierContext
  385. if name == None or name == aiName:
  386. assert self.notify.debug('doneBarrier(%s, %s)' % (context, aiName))
  387. self.sendUpdate("setBarrierReady", [context])
  388. self.__barrierContext = None
  389. else:
  390. assert self.notify.debug('doneBarrier(%s) ignored; current barrier is %s' % (name, aiName))
  391. else:
  392. assert self.notify.debug('doneBarrier(%s) ignored; no active barrier.' % (name))
  393. def addInterest(self, zoneId, note="", event=None):
  394. return self.cr.addInterest(self.getDoId(), zoneId, note, event)
  395. def removeInterest(self, handle, event=None):
  396. return self.cr.removeInterest(handle, event)
  397. def b_setLocation(self, parentId, zoneId):
  398. self.d_setLocation(parentId, zoneId)
  399. self.setLocation(parentId, zoneId)
  400. def d_setLocation(self, parentId, zoneId):
  401. self.cr.sendSetLocation(self.doId, parentId, zoneId)
  402. def setLocation(self, parentId, zoneId):
  403. self.cr.storeObjectLocation(self, parentId, zoneId)
  404. def getLocation(self):
  405. try:
  406. if self.parentId == 0 and self.zoneId == 0:
  407. return None
  408. # This is a -1 stuffed into a uint32
  409. if self.parentId == 0xffffffff and self.zoneId == 0xffffffff:
  410. return None
  411. return (self.parentId, self.zoneId)
  412. except AttributeError:
  413. return None
  414. def getParentObj(self):
  415. if self.parentId is None:
  416. return None
  417. return self.cr.doId2do.get(self.parentId)
  418. def isLocal(self):
  419. # This returns true if the distributed object is "local,"
  420. # which means the client created it instead of the AI, and it
  421. # gets some other special handling. Normally, only the local
  422. # avatar class overrides this to return true.
  423. return self.cr and self.cr.isLocalId(self.doId)
  424. def updateZone(self, zoneId):
  425. self.cr.sendUpdateZone(self, zoneId)
  426. def isGridParent(self):
  427. # If this distributed object is a DistributedGrid return 1. 0 by default
  428. return 0
  429. def execCommand(self, string, mwMgrId, avId, zoneId):
  430. pass