DoHierarchy.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. from direct.directnotify.DirectNotifyGlobal import directNotify
  2. class DoHierarchy:
  3. """
  4. This table has been a source of memory leaks, with DoIds getting left in the table indefinitely.
  5. DoHierarchy guards access to the table and ensures correctness.
  6. """
  7. notify = directNotify.newCategory("DoHierarchy")
  8. def __init__(self):
  9. # parentId->zoneId->set(child DoIds)
  10. self._table = {}
  11. self._allDoIds = set()
  12. def isEmpty(self):
  13. assert ((len(self._table) == 0) == (len(self._allDoIds) == 0))
  14. return len(self._table) == 0 and len(self._allDoIds) == 0
  15. def __len__(self):
  16. return len(self._allDoIds)
  17. def clear(self):
  18. assert self.notify.debugCall()
  19. self._table = {}
  20. self._allDoIds = set()
  21. def getDoIds(self, getDo, parentId, zoneId=None, classType=None):
  22. """
  23. Moved from DoCollectionManager
  24. ==============================
  25. parentId is any distributed object id.
  26. zoneId is a uint32, defaults to None (all zones). Try zone 2 if
  27. you're not sure which zone to use (0 is a bad/null zone and
  28. 1 has had reserved use in the past as a no messages zone, while
  29. 2 has traditionally been a global, uber, misc stuff zone).
  30. dclassType is a distributed class type filter, defaults
  31. to None (no filter).
  32. If dclassName is None then all objects in the zone are returned;
  33. otherwise the list is filtered to only include objects of that type.
  34. """
  35. parent=self._table.get(parentId)
  36. if parent is None:
  37. return []
  38. if zoneId is None:
  39. r = []
  40. for zone in parent.values():
  41. for obj in zone:
  42. r.append(obj)
  43. else:
  44. r = parent.get(zoneId, [])
  45. if classType is not None:
  46. a = []
  47. for doId in r:
  48. obj = getDo(doId)
  49. if isinstance(obj, classType):
  50. a.append(doId)
  51. r = a
  52. return r
  53. def storeObjectLocation(self, do, parentId, zoneId):
  54. doId = do.doId
  55. if doId in self._allDoIds:
  56. self.notify.error(
  57. 'storeObjectLocation(%s %s) already in _allDoIds; duplicate generate()? or didn\'t clean up previous instance of DO?' % (
  58. do.__class__.__name__, do.doId))
  59. parentZoneDict = self._table.setdefault(parentId, {})
  60. zoneDoSet = parentZoneDict.setdefault(zoneId, set())
  61. zoneDoSet.add(doId)
  62. self._allDoIds.add(doId)
  63. self.notify.debug('storeObjectLocation: %s(%s) @ (%s, %s)' % (
  64. do.__class__.__name__, doId, parentId, zoneId))
  65. def deleteObjectLocation(self, do, parentId, zoneId):
  66. doId = do.doId
  67. if doId not in self._allDoIds:
  68. self.notify.error(
  69. 'deleteObjectLocation(%s %s) not in _allDoIds; duplicate delete()? or invalid previous location on a new object?' % (
  70. do.__class__.__name__, do.doId))
  71. # jbutler: temp hack to get by the assert, this will be fixed soon
  72. if (doId not in self._allDoIds):
  73. return
  74. parentZoneDict = self._table.get(parentId)
  75. if parentZoneDict is not None:
  76. zoneDoSet = parentZoneDict.get(zoneId)
  77. if zoneDoSet is not None:
  78. if doId in zoneDoSet:
  79. zoneDoSet.remove(doId)
  80. self._allDoIds.remove(doId)
  81. self.notify.debug('deleteObjectLocation: %s(%s) @ (%s, %s)' % (
  82. do.__class__.__name__, doId, parentId, zoneId))
  83. if len(zoneDoSet) == 0:
  84. del parentZoneDict[zoneId]
  85. if len(parentZoneDict) == 0:
  86. del self._table[parentId]
  87. else:
  88. self.notify.error(
  89. "deleteObjectLocation: objId: %s not found" % doId)
  90. else:
  91. self.notify.error(
  92. "deleteObjectLocation: zoneId: %s not found" % zoneId)
  93. else:
  94. self.notify.error(
  95. "deleteObjectLocation: parentId: %s not found" % parentId)