Nodes: optimize freeing all nodes

We generally expect `bNodeTreeRuntime::nodes_by_id` to be valid at all times, so
it also has to have the same order `bNodeTree.nodes`. When freeing a node, the
entire vector set was rebuild currently to ensure that invariant. This leads
O(n^2) behavior when all nodes are freed as is commonly the case with depsgraph
copies etc.

This patch implements an optimization where `nodes_by_id` is not rebuild if only
the last node was removed. In this case, that not can just be popped from
`nodes_by_id` without affecting the order of the other nodes. To use this
optimization, the node tree freeing code now frees nodes in reverse order.

Pull Request: https://projects.blender.org/blender/blender/pulls/143831
This commit is contained in:
Jacques Lucke
2025-08-02 12:35:16 +02:00
parent 9fd877e174
commit 99984b3b05

View File

@@ -271,7 +271,9 @@ static void ntree_free_data(ID *id)
BLI_freelistN(&ntree->links);
LISTBASE_FOREACH_MUTABLE (bNode *, node, &ntree->nodes) {
/* Iterate backwards because this allows for more efficient node deletion while keeping
* bNodeTreeRuntime::nodes_by_id valid. */
LISTBASE_FOREACH_BACKWARD_MUTABLE (bNode *, node, &ntree->nodes) {
node_free_node(ntree, *node);
}
@@ -4094,8 +4096,17 @@ void node_free_node(bNodeTree *ntree, bNode &node)
/* can be called for nodes outside a node tree (e.g. clipboard) */
if (ntree) {
BLI_remlink(&ntree->nodes, &node);
/* Rebuild nodes #VectorSet which must have the same order as the list. */
node_rebuild_id_vector(*ntree);
const bool was_last = ntree->runtime->nodes_by_id.as_span().last() == &node;
if (was_last) {
/* No need to rebuild the entire bNodeTreeRuntime::nodes_by_id when the removed node is the
* last one. */
ntree->runtime->nodes_by_id.pop();
}
else {
/* Rebuild nodes #VectorSet which must have the same order as the list. */
node_rebuild_id_vector(*ntree);
}
/* texture node has bad habit of keeping exec data around */
if (ntree->type == NTREE_TEXTURE && ntree->runtime->execdata) {