DistributedObjectAI.py 19 KB

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