DistributedObjectAI.py 22 KB

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