DistributedObjectUD.py 19 KB

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