Fix #143551: Cache invalidation causes crash when changing node tree item properties

Changing a node tree item property (such as the default value) was using a very
broad and generic "tag" function which invalidates the runtime items cache.
This also invalidates any python iterators due to the API using the runtime
cache. Changing node tree items in a loop will then crash.

It's not necessary to invalidate the runtime items cache when the actual item
pointers have not changed. Most RNA updates only change superficial properties,
or at most require a recursive node tree update due to change of identifiers or
types.

This PR introduces a simpler "tag" function to only tag for tree updates by not
rebuild the entire runtime items cache. It also renames existing functions and
docstrings to better explain what each of them does and should be used for.

The `NodeTreeInterfaceChangedFlag` is removed completely because it is only ever
used as a simple boolean indicator of "item changes" that require a cache
rebuild. It is replaced with an atomic bool like flags used for runtime caches.

Pull Request: https://projects.blender.org/blender/blender/pulls/143932
This commit is contained in:
Lukas Tönne
2025-08-12 11:00:10 +02:00
parent c3ae3926da
commit dab6b45336
9 changed files with 103 additions and 67 deletions

View File

@@ -469,6 +469,37 @@ class CompositorNodeGroupInterfaceTest(AbstractNodeGroupInterfaceTest, NodeGroup
self.do_test_remove("NodeSocketFloat")
class NodeTreeItemsIteratorTest(AbstractNodeGroupInterfaceTest, NodeGroupInterfaceTests):
tree_type = "ShaderNodeTree"
group_node_type = "ShaderNodeGroup"
def setUp(self):
super().setUp()
self.material = bpy.data.materials.new("test")
self.material.use_nodes = True
self.main_tree = self.material.node_tree
# Regression test for changes while iterating over tree interface items (#143551).
# The iterator should remain valid when changing properties of a tree item.
def test_items_iterator(self):
tree, group_node = self.make_group_and_instance()
tree.interface.new_socket("Input 0", socket_type="NodeSocketFloat", in_out='INPUT')
tree.interface.new_socket("Input 1", socket_type="NodeSocketBool", in_out='INPUT')
# The cache vector has a fixed buffer for small sizes, add enough sockets to force reallocation.
for i in range(20):
tree.interface.new_socket(f"Input {2+i}", socket_type="NodeSocketColor", in_out='INPUT')
# Iterate over items and change properties. The loop iterator must remain valid.
for item in tree.interface.items_tree:
if item.socket_type == "NodeSocketFloat":
item.default_value = 500.0
elif item.socket_type == "NodeSocketColor":
item.default_value = (1, 0, 0, 1)
elif item.socket_type == "NodeSocketBool":
item.default_value = True
def main():
global args
import argparse