DistributedObjectAI.py 20 KB

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