DistributedObjectAI.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. """DistributedObjectAI module: contains the DistributedObjectAI class"""
  2. from direct.directnotify.DirectNotifyGlobal import directNotify
  3. from direct.distributed.DistributedObjectBase import DistributedObjectBase
  4. from direct.showbase import PythonUtil
  5. from pandac.PandaModules import *
  6. #from PyDatagram import PyDatagram
  7. #from PyDatagramIterator import PyDatagramIterator
  8. class DistributedObjectAI(DistributedObjectBase):
  9. notify = directNotify.newCategory("DistributedObjectAI")
  10. QuietZone = 1
  11. def __init__(self, air):
  12. try:
  13. self.DistributedObjectAI_initialized
  14. except:
  15. self.DistributedObjectAI_initialized = 1
  16. DistributedObjectBase.__init__(self, air)
  17. self.accountName=''
  18. # Record the repository
  19. self.air = air
  20. # Record our distributed class
  21. className = self.__class__.__name__
  22. self.dclass = self.air.dclassesByName[className]
  23. # init doId pre-allocated flag
  24. self.__preallocDoId = 0
  25. # used to track zone changes across the quiet zone
  26. # NOTE: the quiet zone is defined in OTP, but we need it
  27. # here.
  28. self.lastNonQuietZone = None
  29. self._DOAI_requestedDelete = False
  30. # These are used to implement beginBarrier().
  31. self.__nextBarrierContext = 0
  32. self.__barriers = {}
  33. self.__generated = False
  34. # reference count for multiple inheritance
  35. self.__generates = 0
  36. self._zoneData = None
  37. # Uncomment if you want to debug DO leaks
  38. #def __del__(self):
  39. # """
  40. # For debugging purposes, this just prints out what got deleted
  41. # """
  42. # print ("Destructing: " + self.__class__.__name__)
  43. if __debug__:
  44. def status(self, indent=0):
  45. """
  46. print out doId(parentId, zoneId) className
  47. and conditionally show generated, disabled, neverDisable,
  48. or cachable
  49. """
  50. spaces=' '*(indent+2)
  51. try:
  52. print "%s%s:"%(
  53. ' '*indent, self.__class__.__name__)
  54. print "%sfrom DistributedObject doId:%s, parent:%s, zone:%s"%(
  55. spaces,
  56. self.doId, self.parentId, self.zoneId),
  57. flags=[]
  58. if self.__generated:
  59. flags.append("generated")
  60. if self.air == None:
  61. flags.append("deleted")
  62. if len(flags):
  63. print "(%s)"%(" ".join(flags),),
  64. print
  65. except Exception, e: print "%serror printing status"%(spaces,), e
  66. def getDeleteEvent(self):
  67. # this is sent just before we get deleted
  68. if hasattr(self, 'doId'):
  69. return 'distObjDelete-%s' % self.doId
  70. return None
  71. def sendDeleteEvent(self):
  72. # this is called just before we get deleted
  73. delEvent = self.getDeleteEvent()
  74. if delEvent:
  75. messenger.send(delEvent)
  76. def getCacheable(self):
  77. """ This method exists only to mirror the similar method on
  78. DistributedObject. AI objects aren't cacheable. """
  79. return False
  80. def deleteOrDelay(self):
  81. """ This method exists only to mirror the similar method on
  82. DistributedObject. AI objects don't have delayDelete, they
  83. just get deleted immediately. """
  84. self.delete()
  85. def getDelayDeleteCount(self):
  86. return 0
  87. def delete(self):
  88. """
  89. Inheritors should redefine this to take appropriate action on delete
  90. Note that this may be called multiple times if a class inherits
  91. from DistributedObjectAI more than once.
  92. """
  93. self.__generates -= 1
  94. if self.__generates < 0:
  95. self.notify.debug('DistributedObjectAI: delete() called more times than generate()')
  96. if self.__generates == 0:
  97. # prevent this code from executing multiple times
  98. if self.air is not None:
  99. # self.doId may not exist. The __dict__ syntax works around that.
  100. assert self.notify.debug('delete(): %s' % (self.__dict__.get("doId")))
  101. if not self._DOAI_requestedDelete:
  102. # this logs every delete that was not requested by us.
  103. # TODO: this currently prints warnings for deletes of objects
  104. # that we did not create. We need to add a 'locally created'
  105. # flag to every object to filter these out.
  106. """
  107. DistributedObjectAI.notify.warning(
  108. 'delete() called but requestDelete never called for %s: %s'
  109. % (self.__dict__.get('doId'), self.__class__.__name__))
  110. """
  111. """
  112. # print a stack trace so we can detect whether this is the
  113. # result of a network msg.
  114. # this is slow.
  115. from direct.showbase.PythonUtil import StackTrace
  116. DistributedObjectAI.notify.warning(
  117. 'stack trace: %s' % StackTrace())
  118. """
  119. self._DOAI_requestedDelete = False
  120. if self._zoneData is not None:
  121. self._zoneData.destroy()
  122. self._zoneData = None
  123. # Clean up all the pending barriers.
  124. for barrier in self.__barriers.values():
  125. barrier.cleanup()
  126. self.__barriers = {}
  127. self.air.stopTrackRequestDeletedDO(self)
  128. # DCR: I've re-enabled this block of code so that Toontown's
  129. # AI won't leak channels.
  130. # Let me know if it causes trouble.
  131. ### Asad: As per Roger's suggestion, turn off the following
  132. ### block until a solution is thought out of how to prevent
  133. ### this delete message or to handle this message better
  134. # TODO: do we still need this check?
  135. if not hasattr(self, "doNotDeallocateChannel"):
  136. if self.air and not hasattr(self.air, "doNotDeallocateChannel"):
  137. if self.air.minChannel <= self.doId <= self.air.maxChannel:
  138. self.air.deallocateChannel(self.doId)
  139. self.air = None
  140. self.parentId = None
  141. self.zoneId = None
  142. self.__generated = False
  143. def isDeleted(self):
  144. """
  145. Returns true if the object has been deleted,
  146. or if it is brand new and hasnt yet been generated.
  147. """
  148. return self.air == None
  149. def isGenerated(self):
  150. """
  151. Returns true if the object has been generated
  152. """
  153. return self.__generated
  154. def getDoId(self):
  155. """
  156. Return the distributed object id
  157. """
  158. return self.doId
  159. def preAllocateDoId(self):
  160. """
  161. objects that need to have a doId before they are generated
  162. can call this to pre-allocate a doId for the object
  163. """
  164. assert not self.__preallocDoId
  165. self.doId = self.air.allocateChannel()
  166. self.__preallocDoId = 1
  167. def announceGenerate(self):
  168. """
  169. Called after the object has been generated and all
  170. of its required fields filled in. Overwrite when needed.
  171. """
  172. pass
  173. def addInterest(self, zoneId, note="", event=None):
  174. self.air.addInterest(self.doId, zoneId, note, event)
  175. def b_setLocation(self, parentId, zoneId):
  176. self.d_setLocation(parentId, zoneId)
  177. self.setLocation(parentId, zoneId)
  178. def d_setLocation(self, parentId, zoneId):
  179. self.air.sendSetLocation(self, parentId, zoneId)
  180. def setLocation(self, parentId, zoneId):
  181. # Prevent Duplicate SetLocations for being Called
  182. if (self.parentId == parentId) and (self.zoneId == zoneId):
  183. return
  184. oldParentId = self.parentId
  185. oldZoneId = self.zoneId
  186. self.air.storeObjectLocation(self, parentId, zoneId)
  187. if ((oldParentId != parentId) or
  188. (oldZoneId != zoneId)):
  189. if self._zoneData is not None:
  190. self._zoneData.destroy()
  191. self._zoneData = None
  192. messenger.send(self.getZoneChangeEvent(), [zoneId, oldZoneId])
  193. # if we are not going into the quiet zone, send a 'logical' zone
  194. # change message
  195. if zoneId != DistributedObjectAI.QuietZone:
  196. lastLogicalZone = oldZoneId
  197. if oldZoneId == DistributedObjectAI.QuietZone:
  198. lastLogicalZone = self.lastNonQuietZone
  199. self.handleLogicalZoneChange(zoneId, lastLogicalZone)
  200. self.lastNonQuietZone = zoneId
  201. def getLocation(self):
  202. try:
  203. if self.parentId <= 0 and self.zoneId <= 0:
  204. return None
  205. # This is a -1 stuffed into a uint32
  206. if self.parentId == 0xffffffff and self.zoneId == 0xffffffff:
  207. return None
  208. return (self.parentId, self.zoneId)
  209. except AttributeError:
  210. return None
  211. def postGenerateMessage(self):
  212. self.__generated = True
  213. messenger.send(self.uniqueName("generate"), [self])
  214. def updateRequiredFields(self, dclass, di):
  215. dclass.receiveUpdateBroadcastRequired(self, di)
  216. self.announceGenerate()
  217. self.postGenerateMessage()
  218. def updateAllRequiredFields(self, dclass, di):
  219. dclass.receiveUpdateAllRequired(self, di)
  220. self.announceGenerate()
  221. self.postGenerateMessage()
  222. def updateRequiredOtherFields(self, dclass, di):
  223. dclass.receiveUpdateBroadcastRequired(self, di)
  224. # Announce generate after updating all the required fields,
  225. # but before we update the non-required fields.
  226. self.announceGenerate()
  227. self.postGenerateMessage()
  228. dclass.receiveUpdateOther(self, di)
  229. def updateAllRequiredOtherFields(self, dclass, di):
  230. dclass.receiveUpdateAllRequired(self, di)
  231. # Announce generate after updating all the required fields,
  232. # but before we update the non-required fields.
  233. self.announceGenerate()
  234. self.postGenerateMessage()
  235. dclass.receiveUpdateOther(self, di)
  236. def sendSetZone(self, zoneId):
  237. self.air.sendSetZone(self, zoneId)
  238. def startMessageBundle(self, name):
  239. self.air.startMessageBundle(name)
  240. def sendMessageBundle(self):
  241. self.air.sendMessageBundle(self.doId)
  242. def getZoneChangeEvent(self):
  243. # this event is generated whenever this object changes zones.
  244. # arguments are newZoneId, oldZoneId
  245. # includes the quiet zone.
  246. return DistributedObjectAI.staticGetZoneChangeEvent(self.doId)
  247. def getLogicalZoneChangeEvent(self):
  248. # this event is generated whenever this object changes to a
  249. # non-quiet-zone zone.
  250. # arguments are newZoneId, oldZoneId
  251. # does not include the quiet zone.
  252. return DistributedObjectAI.staticGetLogicalZoneChangeEvent(self.doId)
  253. @staticmethod
  254. def staticGetZoneChangeEvent(doId):
  255. return 'DOChangeZone-%s' % doId
  256. @staticmethod
  257. def staticGetLogicalZoneChangeEvent(doId):
  258. return 'DOLogicalChangeZone-%s' % doId
  259. def handleLogicalZoneChange(self, newZoneId, oldZoneId):
  260. """this function gets called as if we never go through the
  261. quiet zone. Note that it is called once you reach the newZone,
  262. and not at the time that you leave the oldZone."""
  263. messenger.send(self.getLogicalZoneChangeEvent(),
  264. [newZoneId, oldZoneId])
  265. def getZoneData(self):
  266. # Call this to get an AIZoneData object for the current zone.
  267. # This class will hold onto it as self._zoneData
  268. # setLocation destroys self._zoneData if we move away to
  269. # a different zone
  270. if self._zoneData is None:
  271. from otp.ai.AIZoneData import AIZoneData
  272. self._zoneData = AIZoneData(self.air, self.parentId, self.zoneId)
  273. return self._zoneData
  274. def releaseZoneData(self):
  275. # You can call this to release any AIZoneData object that we might be
  276. # holding onto. If we're the last one for the current zone, the data
  277. # will be destroyed (render, collision traverser, etc.)
  278. # Note that the AIZoneData object that we're holding will be destroyed
  279. # automatically when we move away or are destroyed.
  280. if self._zoneData is not None:
  281. self._zoneData.destroy()
  282. self._zoneData = None
  283. def getRender(self):
  284. # note that this will return a different node if we change zones
  285. #return self.air.getRender(self.zoneId)
  286. return self.getZoneData().getRender()
  287. def getNonCollidableParent(self):
  288. return self.getZoneData().getNonCollidableParent()
  289. def getParentMgr(self):
  290. #return self.air.getParentMgr(self.zoneId)
  291. return self.getZoneData().getParentMgr()
  292. def getCollTrav(self, *args, **kArgs):
  293. return self.getZoneData().getCollTrav(*args, **kArgs)
  294. def sendUpdate(self, fieldName, args = []):
  295. assert self.notify.debugStateCall(self)
  296. if self.air:
  297. self.air.sendUpdate(self, fieldName, args)
  298. def GetPuppetConnectionChannel(self, doId):
  299. return doId + (1L << 32)
  300. def GetAccountConnectionChannel(self, doId):
  301. return doId + (3L << 32)
  302. def GetAccountIDFromChannelCode(self, channel):
  303. return channel >> 32
  304. def GetAvatarIDFromChannelCode(self, channel):
  305. return channel & 0xffffffffL
  306. def sendUpdateToAvatarId(self, avId, fieldName, args):
  307. assert self.notify.debugStateCall(self)
  308. channelId = self.GetPuppetConnectionChannel(avId)
  309. self.sendUpdateToChannel(channelId, fieldName, args)
  310. def sendUpdateToAccountId(self, accountId, fieldName, args):
  311. assert self.notify.debugStateCall(self)
  312. channelId = self.GetAccountConnectionChannel(accountId)
  313. self.sendUpdateToChannel(channelId, fieldName, args)
  314. def sendUpdateToChannel(self, channelId, fieldName, args):
  315. assert self.notify.debugStateCall(self)
  316. if self.air:
  317. self.air.sendUpdateToChannel(self, channelId, fieldName, args)
  318. def generateWithRequired(self, zoneId, optionalFields=[]):
  319. assert self.notify.debugStateCall(self)
  320. # have we already allocated a doId?
  321. if self.__preallocDoId:
  322. self.__preallocDoId = 0
  323. return self.generateWithRequiredAndId(
  324. self.doId, zoneId, optionalFields)
  325. # The repository is the one that really does the work
  326. parentId = self.air.districtId
  327. self.air.generateWithRequired(self, parentId, zoneId, optionalFields)
  328. self.generate()
  329. self.announceGenerate()
  330. self.postGenerateMessage()
  331. # this is a special generate used for estates, or anything else that
  332. # needs to have a hard coded doId as assigned by the server
  333. def generateWithRequiredAndId(self, doId, parentId, zoneId, optionalFields=[]):
  334. assert self.notify.debugStateCall(self)
  335. # have we already allocated a doId?
  336. if self.__preallocDoId:
  337. assert doId == self.doId
  338. self.__preallocDoId = 0
  339. # The repository is the one that really does the work
  340. self.air.generateWithRequiredAndId(self, doId, parentId, zoneId, optionalFields)
  341. self.generate()
  342. self.announceGenerate()
  343. self.postGenerateMessage()
  344. def generateOtpObject(self, parentId, zoneId, optionalFields=[], doId=None):
  345. assert self.notify.debugStateCall(self)
  346. # have we already allocated a doId?
  347. if self.__preallocDoId:
  348. assert doId is None or doId == self.doId
  349. doId=self.doId
  350. self.__preallocDoId = 0
  351. # Assign it an id
  352. if doId is None:
  353. self.doId = self.air.allocateChannel()
  354. else:
  355. self.doId = doId
  356. # Put the new DO in the dictionaries
  357. self.air.addDOToTables(self, location=(parentId, zoneId))
  358. # Send a generate message
  359. self.sendGenerateWithRequired(self.air, parentId, zoneId, optionalFields)
  360. self.generate()
  361. self.announceGenerate()
  362. self.postGenerateMessage()
  363. def generate(self):
  364. """
  365. Inheritors should put functions that require self.zoneId or
  366. other networked info in this function.
  367. """
  368. assert self.notify.debugStateCall(self)
  369. self.__generates += 1
  370. def generateInit(self, repository=None):
  371. """
  372. First generate (not from cache).
  373. """
  374. assert self.notify.debugStateCall(self)
  375. def generateTargetChannel(self, repository):
  376. """
  377. Who to send this to for generate messages
  378. """
  379. if hasattr(self, "dbObject"):
  380. return self.doId
  381. return repository.serverId
  382. def sendGenerateWithRequired(self, repository, parentId, zoneId, optionalFields=[]):
  383. assert self.notify.debugStateCall(self)
  384. dg = self.dclass.aiFormatGenerate(
  385. self, self.doId, parentId, zoneId,
  386. #repository.serverId,
  387. self.generateTargetChannel(repository),
  388. repository.ourChannel,
  389. optionalFields)
  390. repository.send(dg)
  391. def initFromServerResponse(self, valDict):
  392. assert self.notify.debugStateCall(self)
  393. # This is a special method used for estates, etc., which get
  394. # their fields set from the database indirectly by way of the
  395. # AI. The input parameter is a dictionary of field names to
  396. # datagrams that describes the initial field values from the
  397. # database.
  398. dclass = self.dclass
  399. for key, value in valDict.items():
  400. # Update the field
  401. dclass.directUpdate(self, key, value)
  402. def requestDelete(self):
  403. assert self.notify.debugStateCall(self)
  404. if not self.air:
  405. doId = "none"
  406. if hasattr(self, "doId"):
  407. doId = self.doId
  408. self.notify.warning(
  409. "Tried to delete a %s (doId %s) that is already deleted" %
  410. (self.__class__, doId))
  411. return
  412. self.air.requestDelete(self)
  413. self.air.startTrackRequestDeletedDO(self)
  414. self._DOAI_requestedDelete = True
  415. def taskName(self, taskString):
  416. return ("%s-%s" % (taskString, self.doId))
  417. def uniqueName(self, idString):
  418. return ("%s-%s" % (idString, self.doId))
  419. def validate(self, avId, bool, msg):
  420. if not bool:
  421. self.air.writeServerEvent('suspicious', avId, msg)
  422. self.notify.warning('validate error: avId: %s -- %s' % (avId, msg))
  423. return bool
  424. def beginBarrier(self, name, avIds, timeout, callback):
  425. # Begins waiting for a set of avatars. When all avatars in
  426. # the list have reported back in or the callback has expired,
  427. # calls the indicated callback with the list of avatars that
  428. # made it through. There may be multiple barriers waiting
  429. # simultaneously on different lists of avatars, although they
  430. # should have different names.
  431. from otp.ai import Barrier
  432. context = self.__nextBarrierContext
  433. # We assume the context number is passed as a uint16.
  434. self.__nextBarrierContext = (self.__nextBarrierContext + 1) & 0xffff
  435. assert self.notify.debug('beginBarrier(%s, %s, %s, %s)' % (context, name, avIds, timeout))
  436. if avIds:
  437. barrier = Barrier.Barrier(
  438. name, self.uniqueName(name), avIds, timeout,
  439. doneFunc = PythonUtil.Functor(
  440. self.__barrierCallback, context, callback))
  441. self.__barriers[context] = barrier
  442. # Send the context number to each involved client.
  443. self.sendUpdate("setBarrierData", [self.getBarrierData()])
  444. else:
  445. # No avatars; just call the callback immediately.
  446. callback(avIds)
  447. return context
  448. def getBarrierData(self):
  449. # Returns the barrier data formatted for sending to the
  450. # clients. This lists all of the current outstanding barriers
  451. # and the avIds waiting for them.
  452. data = []
  453. for context, barrier in self.__barriers.items():
  454. avatars = barrier.pendingAvatars
  455. if avatars:
  456. data.append((context, barrier.name, avatars))
  457. return data
  458. def ignoreBarrier(self, context):
  459. # Aborts a previously-set barrier. The context is the return
  460. # value from the previous call to beginBarrier().
  461. barrier = self.__barriers.get(context)
  462. if barrier:
  463. barrier.cleanup()
  464. del self.__barriers[context]
  465. def setBarrierReady(self, context):
  466. # Generated by the clients to check in after a beginBarrier()
  467. # call.
  468. avId = self.air.getAvatarIdFromSender()
  469. assert self.notify.debug('setBarrierReady(%s, %s)' % (context, avId))
  470. barrier = self.__barriers.get(context)
  471. if barrier == None:
  472. # This may be None if a client was slow and missed an
  473. # earlier timeout. Too bad.
  474. return
  475. barrier.clear(avId)
  476. def __barrierCallback(self, context, callback, avIds):
  477. assert self.notify.debug('barrierCallback(%s, %s)' % (context, avIds))
  478. # The callback that is generated when a barrier is completed.
  479. barrier = self.__barriers.get(context)
  480. if barrier:
  481. barrier.cleanup()
  482. del self.__barriers[context]
  483. callback(avIds)
  484. else:
  485. self.notify.warning("Unexpected completion from barrier %s" % (context))
  486. def isGridParent(self):
  487. # If this distributed object is a DistributedGrid return 1. 0 by default
  488. return 0
  489. def execCommand(self, string, mwMgrId, avId, zoneId):
  490. pass
  491. def _retrieveCachedData(self):
  492. """ This is a no-op on the AI. """
  493. pass