test_pandanode.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. from panda3d.core import PandaNode, TransformState
  2. from contextlib import contextmanager
  3. import pytest
  4. import gc
  5. @contextmanager
  6. def gc_disabled():
  7. gc.disable()
  8. gc.collect()
  9. gc.freeze()
  10. gc.set_debug(gc.DEBUG_SAVEALL)
  11. gc.garbage.clear()
  12. try:
  13. yield
  14. finally:
  15. gc.set_debug(0)
  16. gc.garbage.clear()
  17. gc.unfreeze()
  18. gc.collect()
  19. gc.enable()
  20. def test_node_prev_transform():
  21. identity = TransformState.make_identity()
  22. t1 = TransformState.make_pos((1, 0, 0))
  23. t2 = TransformState.make_pos((2, 0, 0))
  24. t3 = TransformState.make_pos((3, 0, 0))
  25. node = PandaNode("node")
  26. assert node.transform == identity
  27. assert node.prev_transform == identity
  28. assert not node.has_dirty_prev_transform()
  29. node.transform = t1
  30. assert node.transform == t1
  31. assert node.prev_transform == identity
  32. assert node.has_dirty_prev_transform()
  33. node.transform = t2
  34. assert node.transform == t2
  35. assert node.prev_transform == identity
  36. assert node.has_dirty_prev_transform()
  37. node.reset_prev_transform()
  38. assert node.transform == t2
  39. assert node.prev_transform == t2
  40. assert not node.has_dirty_prev_transform()
  41. node.transform = t3
  42. assert node.prev_transform == t2
  43. assert node.has_dirty_prev_transform()
  44. PandaNode.reset_all_prev_transform()
  45. assert node.transform == t3
  46. assert node.prev_transform == t3
  47. assert not node.has_dirty_prev_transform()
  48. def test_node_tag_cycle():
  49. with gc_disabled():
  50. node = PandaNode('test_node_tag_cycle')
  51. node.set_python_tag('self', node)
  52. node.set_python_tag('self2', node)
  53. assert gc.is_tracked(node)
  54. node = None
  55. gc.collect()
  56. assert len(gc.garbage) > 0
  57. for g in gc.garbage:
  58. if isinstance(g, PandaNode) and g.name == 'test_node_tag_cycle':
  59. break
  60. else:
  61. assert False
  62. @pytest.mark.xfail
  63. def test_node_tag_cycle_multiple_wrappers():
  64. # Doesn't yet work since the traverse checks that the refcount is 1.
  65. with gc_disabled():
  66. node = PandaNode('test_node_tag_cycle_multiple_wrappers')
  67. node.set_python_tag('self', node)
  68. # Find another reference to the same node, we do this by temporarily
  69. # attaching a child.
  70. child = PandaNode('child')
  71. node.add_child(child)
  72. node2 = child.get_parent(0)
  73. node.remove_child(0)
  74. child = None
  75. assert node2 == node
  76. assert node2 is not node
  77. assert node2.this == node.this
  78. node.set_python_tag('self2', node2)
  79. node = None
  80. node2 = None
  81. gc.collect()
  82. assert len(gc.garbage) > 0
  83. for g in gc.garbage:
  84. if isinstance(g, PandaNode) and g.name == 'test_node_tag_cycle_multiple_wrappers':
  85. break
  86. else:
  87. pytest.fail('not found in garbage')
  88. def test_node_subclass_persistent():
  89. class NodeSubclass(PandaNode):
  90. pass
  91. node = NodeSubclass('test_node_subclass')
  92. assert isinstance(node, PandaNode)
  93. # Always GC-tracked
  94. assert gc.is_tracked(node)
  95. # Registered type handle
  96. type_handle = node.get_type()
  97. assert type_handle.name == 'NodeSubclass'
  98. assert type_handle != PandaNode.get_class_type()
  99. assert tuple(type_handle.parent_classes) == (PandaNode.get_class_type(), )
  100. # Persistent wrapper
  101. parent = PandaNode('parent')
  102. parent.add_child(node)
  103. child = parent.get_child(0)
  104. assert child.this == node.this
  105. assert child is node
  106. assert type(child) is NodeSubclass
  107. parent = None
  108. child = None
  109. def test_node_subclass_gc():
  110. class NodeSubclass(PandaNode):
  111. pass
  112. # Python wrapper destructs last
  113. with gc_disabled():
  114. node = NodeSubclass('test_node_subclass_gc1')
  115. assert node in gc.get_objects()
  116. node = None
  117. gc.collect()
  118. assert len(gc.garbage) == 1
  119. assert gc.garbage[0].name == 'test_node_subclass_gc1'
  120. # C++ reference destructs last
  121. with gc_disabled():
  122. node = NodeSubclass('test_node_subclass_gc2')
  123. parent = PandaNode('parent')
  124. parent.add_child(node)
  125. assert node in gc.get_objects()
  126. node = None
  127. # Hasn't been collected yet, since parent still holds a reference
  128. gc.collect()
  129. assert len(gc.garbage) == 0
  130. assert 'test_node_subclass_gc2' in [obj.name for obj in gc.get_objects() if isinstance(obj, PandaNode)]
  131. parent = None
  132. # Destructed without needing the GC
  133. assert 'test_node_subclass_gc2' not in [obj.name for obj in gc.get_objects() if isinstance(obj, PandaNode)]
  134. gc.collect()
  135. assert len(gc.garbage) == 0