DoCollectionManager.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. from direct.distributed import DoHierarchy
  2. import re
  3. #hack:
  4. BAD_DO_ID = BAD_ZONE_ID = 0 # 0xFFFFFFFF
  5. BAD_CHANNEL_ID = 0 # 0xFFFFFFFFFFFFFFFF
  6. class DoCollectionManager:
  7. def __init__(self):
  8. # Dict of {DistributedObject ids: DistributedObjects}
  9. self.doId2do = {}
  10. # (parentId, zoneId) to dict of doId->DistributedObjectAI
  11. ## self.zoneId2doIds={}
  12. if self.hasOwnerView():
  13. # Dict of {DistributedObject ids: DistributedObjects}
  14. # for 'owner' views of objects
  15. self.doId2ownerView = {}
  16. # Dict of {
  17. # parent DistributedObject id:
  18. # { zoneIds: [child DistributedObject ids] }}
  19. self._doHierarchy = DoHierarchy.DoHierarchy()
  20. def getDo(self, doId):
  21. return self.doId2do.get(doId)
  22. def getGameDoId(self):
  23. return self.GameGlobalsId
  24. def callbackWithDo(self, doId, callback):
  25. do = self.doId2do.get(doId)
  26. if do is not None:
  27. callback(do)
  28. else:
  29. relatedObjectMgr(doId, allCallback=callback)
  30. def getOwnerView(self, doId):
  31. assert self.hasOwnerView()
  32. return self.doId2ownerView.get(doId)
  33. def callbackWithOwnerView(self, doId, callback):
  34. assert self.hasOwnerView()
  35. do = self.doId2ownerView.get(doId)
  36. if do is not None:
  37. callback(do)
  38. else:
  39. pass #relatedObjectMgr(doId, allCallback=callback)
  40. def getDoTable(self, ownerView):
  41. if ownerView:
  42. assert self.hasOwnerView()
  43. return self.doId2ownerView
  44. else:
  45. return self.doId2do
  46. def doFind(self, str):
  47. """
  48. Returns list of distributed objects with matching str in value.
  49. """
  50. for value in self.doId2do.values():
  51. if repr(value).find(str) >= 0:
  52. return value
  53. def doFindAll(self, str):
  54. """
  55. Returns list of distributed objects with matching str in value.
  56. """
  57. matches = []
  58. for value in self.doId2do.values():
  59. if repr(value).find(str) >= 0:
  60. matches.append(value)
  61. return matches
  62. def doFindAllMatching(self, str):
  63. """
  64. Returns list of distributed objects with matching str in value.
  65. """
  66. matches = []
  67. for value in self.doId2do.values():
  68. if re.search(str,repr(value)):
  69. matches.append(value)
  70. return matches
  71. def doFindAllOfType(self, query):
  72. """
  73. Useful method for searching through the Distributed Object collection
  74. for objects of a particular type
  75. """
  76. matches = []
  77. for value in self.doId2do.values():
  78. if query in str(value.__class__):
  79. matches.append(value)
  80. return matches, len(matches)
  81. def doFindAllInstances(self, cls):
  82. matches = []
  83. for value in self.doId2do.values():
  84. if isinstance(value, cls):
  85. matches.append(value)
  86. return matches
  87. def _getDistanceFromLA(self, do):
  88. if hasattr(do, 'getPos'):
  89. return do.getPos(localAvatar).length()
  90. return None
  91. def _compareDistance(self, do1, do2):
  92. dist1 = self._getDistanceFromLA(do1)
  93. dist2 = self._getDistanceFromLA(do2)
  94. if dist1 is None and dist2 is None:
  95. return 0
  96. if dist1 is None:
  97. return 1
  98. if dist2 is None:
  99. return -1
  100. if (dist1 < dist2):
  101. return -1
  102. return 1
  103. def dosByDistance(self):
  104. objs = self.doId2do.values()
  105. objs.sort(cmp=self._compareDistance)
  106. return objs
  107. def doByDistance(self):
  108. objs = self.dosByDistance()
  109. for obj in objs:
  110. print '%s\t%s\t%s' % (obj.doId, self._getDistanceFromLA(obj),
  111. obj.dclass.getName())
  112. if __debug__:
  113. def printObjects(self):
  114. format="%10s %10s %10s %30s %20s"
  115. title=format%("parentId", "zoneId", "doId", "dclass", "name")
  116. print title
  117. print '-'*len(title)
  118. for distObj in self.doId2do.values():
  119. print format%(
  120. distObj.__dict__.get("parentId"),
  121. distObj.__dict__.get("zoneId"),
  122. distObj.__dict__.get("doId"),
  123. distObj.dclass.getName(),
  124. distObj.__dict__.get("name"))
  125. def _printObjects(self, table):
  126. class2count = {}
  127. for obj in self.getDoTable(ownerView=False).values():
  128. className = obj.__class__.__name__
  129. class2count.setdefault(className, 0)
  130. class2count[className] += 1
  131. count2classes = invertDictLossless(class2count)
  132. counts = count2classes.keys()
  133. counts.sort()
  134. counts.reverse()
  135. for count in counts:
  136. count2classes[count].sort()
  137. for name in count2classes[count]:
  138. print '%s %s' % (count, name)
  139. print ''
  140. def _returnObjects(self, table):
  141. class2count = {}
  142. stringToReturn = ''
  143. for obj in self.getDoTable(ownerView=False).values():
  144. className = obj.__class__.__name__
  145. class2count.setdefault(className, 0)
  146. class2count[className] += 1
  147. count2classes = invertDictLossless(class2count)
  148. counts = count2classes.keys()
  149. counts.sort()
  150. counts.reverse()
  151. for count in counts:
  152. count2classes[count].sort()
  153. for name in count2classes[count]:
  154. # print '%s %s' % (count, name)
  155. stringToReturn = '%s%s %s\n' % (stringToReturn, count, name)
  156. # print ''
  157. return stringToReturn
  158. def webPrintObjectCount(self):
  159. strToReturn = '==== OBJECT COUNT ====\n'
  160. if self.hasOwnerView():
  161. strToReturn = '%s == doId2do\n' % (strToReturn)
  162. strToReturn = '%s%s' % (strToReturn, self._returnObjects(self.getDoTable(ownerView=False)))
  163. if self.hasOwnerView():
  164. strToReturn = '%s\n== doId2ownerView\n' % (strToReturn)
  165. strToReturn = '%s%s' % (strToReturn, self._returnObjects(self.getDoTable(ownerView=False)))
  166. return strToReturn
  167. def printObjectCount(self):
  168. # print object counts by distributed object type
  169. print '==== OBJECT COUNT ===='
  170. if self.hasOwnerView():
  171. print '== doId2do'
  172. self._printObjects(self.getDoTable(ownerView=False))
  173. if self.hasOwnerView():
  174. print '== doId2ownerView'
  175. self._printObjects(self.getDoTable(ownerView=True))
  176. def getDoList(self, parentId, zoneId=None, classType=None):
  177. """
  178. parentId is any distributed object id.
  179. zoneId is a uint32, defaults to None (all zones). Try zone 2 if
  180. you're not sure which zone to use (0 is a bad/null zone and
  181. 1 has had reserved use in the past as a no messages zone, while
  182. 2 has traditionally been a global, uber, misc stuff zone).
  183. dclassType is a distributed class type filter, defaults
  184. to None (no filter).
  185. If dclassName is None then all objects in the zone are returned;
  186. otherwise the list is filtered to only include objects of that type.
  187. """
  188. return [self.doId2do.get(i)
  189. for i in self.getDoIdList(parentId, zoneId, classType)]
  190. def getDoIdList(self, parentId, zoneId=None, classType=None):
  191. return self._doHierarchy.getDoIds(self.getDo,
  192. parentId, zoneId, classType)
  193. def hasOwnerViewDoId(self, doId):
  194. assert self.hasOwnerView()
  195. return doId in self.doId2ownerView
  196. def getOwnerViewDoList(self, classType):
  197. assert self.hasOwnerView()
  198. l = []
  199. for obj in self.doId2ownerView.values():
  200. if isinstance(obj, classType):
  201. l.append(obj)
  202. return l
  203. def getOwnerViewDoIdList(self, classType):
  204. assert self.hasOwnerView()
  205. l = []
  206. for doId, obj in self.doId2ownerView.items():
  207. if isinstance(obj, classType):
  208. l.append(doId)
  209. return l
  210. def countObjects(self, classType):
  211. """
  212. Counts the number of objects of the given type in the
  213. repository (for testing purposes)
  214. """
  215. count = 0
  216. for dobj in self.doId2do.values():
  217. if isinstance(dobj, classType):
  218. count += 1
  219. return count
  220. def getAllOfType(self, type):
  221. # Returns a list of all DistributedObjects in the repository
  222. # of a particular type.
  223. result = []
  224. for obj in self.doId2do.values():
  225. if isinstance(obj, type):
  226. result.append(obj)
  227. return result
  228. def findAnyOfType(self, type):
  229. # Searches the repository for any object of the given type.
  230. for obj in self.doId2do.values():
  231. if isinstance(obj, type):
  232. return obj
  233. return None
  234. #----------------------------------
  235. def deleteDistributedObjects(self):
  236. # Get rid of all the distributed objects
  237. for doId in self.doId2do.keys():
  238. # Look up the object
  239. do = self.doId2do[doId]
  240. self.deleteDistObject(do)
  241. # Get rid of everything that manages distributed objects
  242. self.deleteObjects()
  243. # the zoneId2doIds table should be empty now
  244. if not self._doHierarchy.isEmpty():
  245. self.notify.warning(
  246. '_doHierarchy table not empty: %s' % self._doHierarchy)
  247. self._doHierarchy.clear()
  248. def handleObjectLocation(self, di):
  249. # CLIENT_OBJECT_LOCATION
  250. doId = di.getUint32()
  251. parentId = di.getUint32()
  252. zoneId = di.getUint32()
  253. obj = self.doId2do.get(doId)
  254. if obj is not None:
  255. self.notify.debug(
  256. "handleObjectLocation: doId: %s parentId: %s zoneId: %s"%
  257. (doId, parentId, zoneId))
  258. # Let the object finish the job
  259. # calls storeObjectLocation()
  260. obj.setLocation(parentId, zoneId)
  261. else:
  262. self.notify.warning(
  263. "handleObjectLocation: Asked to update non-existent obj: %s" % (doId))
  264. def handleSetLocation(self, di):
  265. # This was initially added because creating a distributed quest
  266. # object would cause a message like this to be generated.
  267. assert self.notify.debugStateCall(self)
  268. parentId = di.getUint32()
  269. zoneId = di.getUint32()
  270. distObj = self.doId2do.get(self.getMsgChannel())
  271. if distObj is not None:
  272. distObj.setLocation(parentId, zoneId)
  273. else:
  274. self.notify.warning('handleSetLocation: object %s not present' % self.getMsgChannel())
  275. @exceptionLogged()
  276. def storeObjectLocation(self, object, parentId, zoneId):
  277. oldParentId = object.parentId
  278. oldZoneId = object.zoneId
  279. if (oldParentId != parentId):
  280. # notify any existing parent that we're moving away
  281. oldParentObj = self.doId2do.get(oldParentId)
  282. if oldParentObj is not None:
  283. oldParentObj.handleChildLeave(object, oldZoneId)
  284. self.deleteObjectLocation(object, oldParentId, oldZoneId)
  285. elif (oldZoneId != zoneId):
  286. # Remove old location
  287. oldParentObj = self.doId2do.get(oldParentId)
  288. if oldParentObj is not None:
  289. oldParentObj.handleChildLeaveZone(object, oldZoneId)
  290. self.deleteObjectLocation(object, oldParentId, oldZoneId)
  291. else:
  292. # object is already at that parent and zone
  293. return
  294. if ((parentId is None) or (zoneId is None) or
  295. (parentId == zoneId == 0)):
  296. # Do not store null values
  297. object.parentId = None
  298. object.zoneId = None
  299. else:
  300. # Add to new location
  301. self._doHierarchy.storeObjectLocation(object, parentId, zoneId)
  302. # this check doesn't work because of global UD objects;
  303. # should they have a location?
  304. #assert len(self._doHierarchy) == len(self.doId2do)
  305. # Set the new parent and zone on the object
  306. object.parentId = parentId
  307. object.zoneId = zoneId
  308. if oldParentId != parentId:
  309. # Give the parent a chance to run code when a new child
  310. # sets location to it. For example, the parent may want to
  311. # scene graph reparent the child to some subnode it owns.
  312. parentObj = self.doId2do.get(parentId)
  313. if parentObj is not None:
  314. parentObj.handleChildArrive(object, zoneId)
  315. elif parentId not in (None, 0, self.getGameDoId()):
  316. self.notify.warning('storeObjectLocation(%s): parent %s not present' %
  317. (object.doId, parentId))
  318. elif oldZoneId != zoneId:
  319. parentObj = self.doId2do.get(parentId)
  320. if parentObj is not None:
  321. parentObj.handleChildArriveZone(object, zoneId)
  322. elif parentId not in (None, 0, self.getGameDoId()):
  323. self.notify.warning('storeObjectLocation(%s): parent %s not present' %
  324. (object.doId, parentId))
  325. def deleteObjectLocation(self, object, parentId, zoneId):
  326. # Do not worry about null values
  327. if ((parentId is None) or (zoneId is None) or
  328. (parentId == zoneId == 0)):
  329. return
  330. self._doHierarchy.deleteObjectLocation(object, parentId, zoneId)
  331. def addDOToTables(self, do, location=None, ownerView=False):
  332. assert self.notify.debugStateCall(self)
  333. #assert not hasattr(do, "isQueryAllResponse") or not do.isQueryAllResponse
  334. if not ownerView:
  335. if location is None:
  336. location = (do.parentId, do.zoneId)
  337. doTable = self.getDoTable(ownerView)
  338. # make sure the object is not already present
  339. if do.doId in doTable:
  340. if ownerView:
  341. tableName = 'doId2ownerView'
  342. else:
  343. tableName = 'doId2do'
  344. self.notify.error('doId %s already in %s [%s stomping %s]' % (
  345. do.doId, tableName, do.__class__.__name__,
  346. doTable[do.doId].__class__.__name__))
  347. doTable[do.doId]=do
  348. if not ownerView:
  349. if self.isValidLocationTuple(location):
  350. self.storeObjectLocation(do, location[0], location[1])
  351. ##assert do.doId not in self.zoneId2doIds.get(location, {})
  352. ##self.zoneId2doIds.setdefault(location, {})
  353. ##self.zoneId2doIds[location][do.doId]=do
  354. def isValidLocationTuple(self, location):
  355. return (location is not None
  356. and location != (0xffffffff, 0xffffffff)
  357. and location != (0, 0))
  358. if __debug__:
  359. def isInDoTables(self, doId):
  360. assert self.notify.debugStateCall(self)
  361. return doId in self.doId2do
  362. def removeDOFromTables(self, do):
  363. assert self.notify.debugStateCall(self)
  364. #assert not hasattr(do, "isQueryAllResponse") or not do.isQueryAllResponse
  365. #assert do.doId in self.doId2do
  366. location = do.getLocation()
  367. if location:
  368. oldParentId, oldZoneId = location
  369. oldParentObj = self.doId2do.get(oldParentId)
  370. if oldParentObj:
  371. oldParentObj.handleChildLeave(do, oldZoneId)
  372. self.deleteObjectLocation(do, do.parentId, do.zoneId)
  373. ## location = do.getLocation()
  374. ## if location is not None:
  375. ## if location not in self.zoneId2doIds:
  376. ## self.notify.warning(
  377. ## 'dobj %s (%s) has invalid location: %s' %
  378. ## (do, do.doId, location))
  379. ## else:
  380. ## assert do.doId in self.zoneId2doIds[location]
  381. ## del self.zoneId2doIds[location][do.doId]
  382. ## if len(self.zoneId2doIds[location]) == 0:
  383. ## del self.zoneId2doIds[location]
  384. if do.doId in self.doId2do:
  385. del self.doId2do[do.doId]
  386. ## def changeDOZoneInTables(self, do, newParentId, newZoneId, oldParentId, oldZoneId):
  387. ## if 1:
  388. ## self.storeObjectLocation(do.doId, newParentId, newZoneId)
  389. ## else:
  390. ## #assert not hasattr(do, "isQueryAllResponse") or not do.isQueryAllResponse
  391. ## oldLocation = (oldParentId, oldZoneId)
  392. ## newLocation = (newParentId, newZoneId)
  393. ## # HACK: DistributedGuildMemberUD starts in -1, -1, which isnt ever put in the
  394. ## # zoneId2doIds table
  395. ## if self.isValidLocationTuple(oldLocation):
  396. ## assert self.notify.debugStateCall(self)
  397. ## assert oldLocation in self.zoneId2doIds
  398. ## assert do.doId in self.zoneId2doIds[oldLocation]
  399. ## assert do.doId not in self.zoneId2doIds.get(newLocation, {})
  400. ## # remove from old zone
  401. ## del(self.zoneId2doIds[oldLocation][do.doId])
  402. ## if len(self.zoneId2doIds[oldLocation]) == 0:
  403. ## del self.zoneId2doIds[oldLocation]
  404. ## if self.isValidLocationTuple(newLocation):
  405. ## # add to new zone
  406. ## self.zoneId2doIds.setdefault(newLocation, {})
  407. ## self.zoneId2doIds[newLocation][do.doId]=do
  408. def getObjectsInZone(self, parentId, zoneId):
  409. """
  410. returns dict of doId:distObj for a zone.
  411. returned dict is safely mutable.
  412. """
  413. assert self.notify.debugStateCall(self)
  414. doDict = {}
  415. for doId in self.getDoIdList(parentId, zoneId):
  416. doDict[doId] = self.getDo(doId)
  417. return doDict
  418. def getObjectsOfClassInZone(self, parentId, zoneId, objClass):
  419. """
  420. returns dict of doId:object for a zone, containing all objects
  421. that inherit from 'class'. returned dict is safely mutable.
  422. """
  423. assert self.notify.debugStateCall(self)
  424. doDict = {}
  425. for doId in self.getDoIdList(parentId, zoneId, objClass):
  426. doDict[doId] = self.getDo(doId)
  427. return doDict