From 1260e90b4cab045b065975b9faadeff301f9c8a9 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Wed, 30 Jul 2025 20:42:20 +0200 Subject: [PATCH] Refactor: Geometry Nodes: simplify detection of syncable nodes This moves most of the code to deal with syncable nodes (such as Combine/Separate Bundle) to the nodes module. Over time it might be possible to decentralize it more. This also changes the caching mechanism from storing a flag on the node to storing a map on the node editor runtime data. This simplifies the code quite significantly and also removes the need to store any of this data in DNA. The node tree update code now always clears this cache because before it was missing many cases, e.g. when creating links that would connect a Combine to a Separate Bundle node. Pull Request: https://projects.blender.org/blender/blender/pulls/143661 --- .../blenkernel/intern/node_tree_update.cc | 56 +- source/blender/editors/include/ED_node.hh | 32 +- .../blender/editors/space_node/node_draw.cc | 3 +- .../blender/editors/space_node/node_intern.hh | 8 + .../editors/space_node/node_sync_sockets.cc | 565 +--------------- .../blender/editors/space_node/space_node.cc | 3 + source/blender/makesdna/DNA_node_types.h | 28 +- source/blender/nodes/CMakeLists.txt | 2 + source/blender/nodes/NOD_sync_sockets.hh | 46 ++ .../nodes/geometry/nodes/node_geo_closure.cc | 35 +- .../geometry/nodes/node_geo_combine_bundle.cc | 35 +- .../nodes/node_geo_evaluate_closure.cc | 35 +- .../nodes/node_geo_separate_bundle.cc | 38 +- source/blender/nodes/intern/sync_sockets.cc | 616 ++++++++++++++++++ 14 files changed, 705 insertions(+), 797 deletions(-) create mode 100644 source/blender/nodes/NOD_sync_sockets.hh create mode 100644 source/blender/nodes/intern/sync_sockets.cc diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc index 1962ccac652..252a6ba3d4a 100644 --- a/source/blender/blenkernel/intern/node_tree_update.cc +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -33,13 +33,13 @@ #include "MOD_nodes.hh" -#include "NOD_geo_closure.hh" #include "NOD_geometry_nodes_dependencies.hh" #include "NOD_geometry_nodes_gizmos.hh" #include "NOD_geometry_nodes_lazy_function.hh" #include "NOD_node_declaration.hh" #include "NOD_socket.hh" #include "NOD_socket_declarations.hh" +#include "NOD_sync_sockets.hh" #include "NOD_texture.h" #include "DEG_depsgraph_build.hh" @@ -312,7 +312,6 @@ class NodeTreeMainUpdater { Map update_result_by_tree_; NodeTreeRelations relations_; bool needs_relations_update_ = false; - bool found_updated_sync_node_ = false; public: NodeTreeMainUpdater(Main *bmain, const NodeTreeUpdateExtraParams ¶ms) @@ -416,12 +415,8 @@ class NodeTreeMainUpdater { DEG_relations_tag_update(bmain_); } } - if (found_updated_sync_node_) { - for (bNodeTree *ntree : relations_.get_all_trees()) { - if (ID_IS_EDITABLE(&ntree->id)) { - this->tag_possibly_outdated_sync_nodes(*ntree); - } - } + if (bmain_) { + nodes::node_can_sync_cache_clear(*bmain_); } } @@ -524,7 +519,6 @@ class NodeTreeMainUpdater { this->update_internal_links(ntree); this->update_generic_callback(ntree); this->remove_unused_previews_when_necessary(ntree); - this->check_for_updated_sync_nodes(ntree); this->make_node_previews_dirty(ntree); this->propagate_runtime_flags(ntree); @@ -816,50 +810,6 @@ class NodeTreeMainUpdater { ntree.typeinfo->update(&ntree); } - /** - * Checks if any node has been updated that may be synced with other nodes. - */ - void check_for_updated_sync_nodes(const bNodeTree &ntree) - { - if (found_updated_sync_node_) { - return; - } - ntree.ensure_topology_cache(); - for (const StringRefNull idname : {"GeometryNodeClosureInput", - "GeometryNodeClosureOutput", - "GeometryNodeEvaluateClosure", - "GeometryNodeCombineBundle", - "GeometryNodeSeparateBundle"}) - { - for (const bNode *node : ntree.nodes_by_type(idname)) { - if (node->runtime->changed_flag & NTREE_CHANGED_NODE_PROPERTY) { - found_updated_sync_node_ = true; - break; - } - } - } - } - - void tag_possibly_outdated_sync_nodes(bNodeTree &ntree) - { - for (bNode *node : ntree.nodes_by_type("GeometryNodeClosureOutput")) { - auto &storage = *static_cast(node->storage); - storage.flag |= NODE_GEO_CLOSURE_FLAG_MAY_NEED_SYNC; - } - for (bNode *node : ntree.nodes_by_type("GeometryNodeEvaluateClosure")) { - auto &storage = *static_cast(node->storage); - storage.flag |= NODE_GEO_EVALUATE_CLOSURE_FLAG_MAY_NEED_SYNC; - } - for (bNode *node : ntree.nodes_by_type("GeometryNodeCombineBundle")) { - auto &storage = *static_cast(node->storage); - storage.flag |= NODE_GEO_COMBINE_BUNDLE_FLAG_MAY_NEED_SYNC; - } - for (bNode *node : ntree.nodes_by_type("GeometryNodeSeparateBundle")) { - auto &storage = *static_cast(node->storage); - storage.flag |= NODE_GEO_SEPARATE_BUNDLE_FLAG_MAY_NEED_SYNC; - } - } - void remove_unused_previews_when_necessary(bNodeTree &ntree) { /* Don't trigger preview removal when only those flags are set. */ diff --git a/source/blender/editors/include/ED_node.hh b/source/blender/editors/include/ED_node.hh index b58e16d53a4..5047638599a 100644 --- a/source/blender/editors/include/ED_node.hh +++ b/source/blender/editors/include/ED_node.hh @@ -162,35 +162,7 @@ void ui_template_node_asset_menu_items(uiLayout &layout, const bContext &C, StringRef catalog_path); -void sync_sockets_evaluate_closure(SpaceNode &snode, - bNode &evaluate_closure_node, - ReportList *reports); -void sync_sockets_separate_bundle(SpaceNode &snode, - bNode &separate_bundle_node, - ReportList *reports); -void sync_sockets_combine_bundle(SpaceNode &snode, - bNode &combine_bundle_node, - ReportList *reports); -void sync_sockets_closure(SpaceNode &snode, - bNode &closure_input_node, - bNode &closure_output_node, - const bool initialize_internal_links, - ReportList *reports); - -enum class NodeSyncState { - Synced, - CanBeSynced, - NoSyncSource, - ConflictingSyncSources, -}; - -NodeSyncState sync_sockets_state_separate_bundle(const SpaceNode &snode, - const bNode &separate_bundle_node); -NodeSyncState sync_sockets_state_combine_bundle(const SpaceNode &snode, - const bNode &combine_bundle_node); -NodeSyncState sync_sockets_state_closure_output(const SpaceNode &snode, - const bNode &closure_output_node); -NodeSyncState sync_sockets_state_evaluate_closure(const SpaceNode &snode, - const bNode &evaluate_closure_node); +/** See #SpaceNode_Runtime::node_can_sync_states. */ +Map &node_can_sync_cache_get(SpaceNode &snode); } // namespace blender::ed::space_node diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 1354bcf0085..1aea3ad06d2 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -94,6 +94,7 @@ #include "NOD_geometry_nodes_log.hh" #include "NOD_node_declaration.hh" #include "NOD_node_extra_info.hh" +#include "NOD_sync_sockets.hh" #include "GEO_fillet_curves.hh" @@ -2866,7 +2867,7 @@ static void node_draw_basis(const bContext &C, UI_block_emboss_set(&block, ui::EmbossType::Emboss); } - if (node.typeinfo->can_sync_sockets && node.typeinfo->can_sync_sockets(C, ntree, node)) { + if (nodes::node_can_sync_sockets(C, ntree, node)) { iconofs -= iconbutw; UI_block_emboss_set(&block, ui::EmbossType::None); uiBut *but = uiDefIconBut(&block, diff --git a/source/blender/editors/space_node/node_intern.hh b/source/blender/editors/space_node/node_intern.hh index c519d352420..4f021c50ade 100644 --- a/source/blender/editors/space_node/node_intern.hh +++ b/source/blender/editors/space_node/node_intern.hh @@ -117,6 +117,14 @@ struct SpaceNode_Runtime { * Stored with a shared pointer so that it can be forward declared. */ std::shared_ptr assets_for_menu; + + /** + * Caches the sockets of which nodes can be synced. This can occasionally be expensive to compute + * because it needs to traverse the tree. Also, we don't want to check whether syncing is + * necessary for all nodes eagerly but only if a relevant node is visible to the user. The cache + * is reset when something changes that may affect what nodes need to be synced. + */ + Map node_can_sync_states; }; enum NodeResizeDirection { diff --git a/source/blender/editors/space_node/node_sync_sockets.cc b/source/blender/editors/space_node/node_sync_sockets.cc index d36cd34bdb4..6d08070c1bd 100644 --- a/source/blender/editors/space_node/node_sync_sockets.cc +++ b/source/blender/editors/space_node/node_sync_sockets.cc @@ -13,389 +13,20 @@ #include "WM_api.hh" -#include "BKE_compute_context_cache.hh" #include "BKE_context.hh" #include "BKE_main_invariants.hh" -#include "BKE_node_legacy_types.hh" #include "BKE_node_runtime.hh" #include "BKE_node_tree_update.hh" -#include "BKE_report.hh" #include "ED_node.hh" #include "ED_screen.hh" -#include "BLI_listbase.h" - -#include "BLT_translation.hh" - -#include "NOD_geo_bundle.hh" -#include "NOD_geo_closure.hh" -#include "NOD_socket_items.hh" +#include "NOD_sync_sockets.hh" #include "node_intern.hh" namespace blender::ed::space_node { -struct BundleSyncState { - NodeSyncState state; - std::optional source_signature; -}; - -struct ClosureSyncState { - NodeSyncState state; - std::optional source_signature; -}; - -static BundleSyncState get_sync_state_separate_bundle(const SpaceNode &snode, - const bNode &separate_bundle_node) -{ - BLI_assert(separate_bundle_node.is_type("GeometryNodeSeparateBundle")); - snode.edittree->ensure_topology_cache(); - const bNodeSocket &bundle_socket = separate_bundle_node.input_socket(0); - - bke::ComputeContextCache compute_context_cache; - const ComputeContext *current_context = ed::space_node::compute_context_for_edittree_socket( - snode, compute_context_cache, bundle_socket); - const Vector source_signatures = - ed::space_node::gather_linked_origin_bundle_signatures( - current_context, bundle_socket, compute_context_cache); - if (source_signatures.is_empty()) { - return {NodeSyncState::NoSyncSource}; - } - if (!nodes::BundleSignature::all_matching_exactly(source_signatures)) { - return {NodeSyncState::ConflictingSyncSources}; - } - const nodes::BundleSignature &source_signature = source_signatures[0]; - const nodes::BundleSignature ¤t_signature = - nodes::BundleSignature::from_separate_bundle_node(separate_bundle_node); - if (!source_signature.matches_exactly(current_signature)) { - return {NodeSyncState::CanBeSynced, source_signature}; - } - return {NodeSyncState::Synced}; -} - -static BundleSyncState get_sync_state_combine_bundle(const SpaceNode &snode, - const bNode &combine_bundle_node) -{ - BLI_assert(combine_bundle_node.is_type("GeometryNodeCombineBundle")); - snode.edittree->ensure_topology_cache(); - const bNodeSocket &bundle_socket = combine_bundle_node.output_socket(0); - - bke::ComputeContextCache compute_context_cache; - const ComputeContext *current_context = ed::space_node::compute_context_for_edittree_socket( - snode, compute_context_cache, bundle_socket); - const Vector source_signatures = - ed::space_node::gather_linked_target_bundle_signatures( - current_context, bundle_socket, compute_context_cache); - if (source_signatures.is_empty()) { - return {NodeSyncState::NoSyncSource}; - } - if (!nodes::BundleSignature::all_matching_exactly(source_signatures)) { - return {NodeSyncState::ConflictingSyncSources}; - } - const nodes::BundleSignature &source_signature = source_signatures[0]; - const nodes::BundleSignature ¤t_signature = - nodes::BundleSignature::from_combine_bundle_node(combine_bundle_node); - if (!source_signature.matches_exactly(current_signature)) { - return {NodeSyncState::CanBeSynced, source_signature}; - } - return {NodeSyncState::Synced}; -} - -static ClosureSyncState get_sync_state_closure_output(const SpaceNode &snode, - const bNode &closure_output_node) -{ - snode.edittree->ensure_topology_cache(); - const bNodeSocket &closure_socket = closure_output_node.output_socket(0); - - bke::ComputeContextCache compute_context_cache; - const ComputeContext *current_context = ed::space_node::compute_context_for_edittree_socket( - snode, compute_context_cache, closure_socket); - const Vector source_signatures = - ed::space_node::gather_linked_target_closure_signatures( - current_context, closure_socket, compute_context_cache); - if (source_signatures.is_empty()) { - return {NodeSyncState::NoSyncSource}; - } - if (!nodes::ClosureSignature::all_matching_exactly(source_signatures)) { - return {NodeSyncState::ConflictingSyncSources}; - } - const nodes::ClosureSignature &source_signature = source_signatures[0]; - const nodes::ClosureSignature ¤t_signature = - nodes::ClosureSignature::from_closure_output_node(closure_output_node); - if (!source_signature.matches_exactly(current_signature)) { - return {NodeSyncState::CanBeSynced, source_signature}; - } - return {NodeSyncState::Synced}; -} - -static ClosureSyncState get_sync_state_evaluate_closure(const SpaceNode &snode, - const bNode &evaluate_closure_node) -{ - snode.edittree->ensure_topology_cache(); - const bNodeSocket &closure_socket = evaluate_closure_node.input_socket(0); - - bke::ComputeContextCache compute_context_cache; - const ComputeContext *current_context = ed::space_node::compute_context_for_edittree_socket( - snode, compute_context_cache, closure_socket); - const Vector source_signatures = - ed::space_node::gather_linked_origin_closure_signatures( - current_context, closure_socket, compute_context_cache); - if (source_signatures.is_empty()) { - return {NodeSyncState::NoSyncSource}; - } - if (!nodes::ClosureSignature::all_matching_exactly(source_signatures)) { - return {NodeSyncState::ConflictingSyncSources}; - } - const nodes::ClosureSignature &source_signature = source_signatures[0]; - const nodes::ClosureSignature ¤t_signature = - nodes::ClosureSignature::from_evaluate_closure_node(evaluate_closure_node); - if (!source_signature.matches_exactly(current_signature)) { - return {NodeSyncState::CanBeSynced, source_signature}; - } - return {NodeSyncState::Synced}; -} - -NodeSyncState sync_sockets_state_separate_bundle(const SpaceNode &snode, - const bNode &separate_bundle_node) -{ - return get_sync_state_separate_bundle(snode, separate_bundle_node).state; -} - -NodeSyncState sync_sockets_state_combine_bundle(const SpaceNode &snode, - const bNode &combine_bundle_node) -{ - return get_sync_state_combine_bundle(snode, combine_bundle_node).state; -} - -NodeSyncState sync_sockets_state_closure_output(const SpaceNode &snode, - const bNode &closure_output_node) -{ - return get_sync_state_closure_output(snode, closure_output_node).state; -} - -NodeSyncState sync_sockets_state_evaluate_closure(const SpaceNode &snode, - const bNode &evaluate_closure_node) -{ - return get_sync_state_evaluate_closure(snode, evaluate_closure_node).state; -} - -void sync_sockets_separate_bundle(SpaceNode &snode, - bNode &separate_bundle_node, - ReportList *reports) -{ - const BundleSyncState sync_state = get_sync_state_separate_bundle(snode, separate_bundle_node); - switch (sync_state.state) { - case NodeSyncState::Synced: - return; - case NodeSyncState::NoSyncSource: - BKE_report(reports, RPT_INFO, "No bundle signature found"); - return; - case NodeSyncState::ConflictingSyncSources: - BKE_report(reports, RPT_INFO, "Found conflicting bundle signatures"); - return; - case NodeSyncState::CanBeSynced: - break; - } - - auto &storage = *static_cast(separate_bundle_node.storage); - - Map old_identifiers; - for (const int i : IndexRange(storage.items_num)) { - const NodeGeometrySeparateBundleItem &item = storage.items[i]; - old_identifiers.add_new(StringRef(item.name), item.identifier); - } - - nodes::socket_items::clear(separate_bundle_node); - for (const nodes::BundleSignature::Item &item : sync_state.source_signature->items) { - NodeGeometrySeparateBundleItem &new_item = - *nodes::socket_items::add_item_with_socket_type_and_name< - nodes ::SeparateBundleItemsAccessor>( - separate_bundle_node, item.type->type, item.key.c_str()); - if (const std::optional old_identifier = old_identifiers.lookup_try(item.key)) { - new_item.identifier = *old_identifier; - } - } - BKE_ntree_update_tag_node_property(snode.edittree, &separate_bundle_node); -} - -void sync_sockets_combine_bundle(SpaceNode &snode, bNode &combine_bundle_node, ReportList *reports) -{ - const BundleSyncState sync_state = get_sync_state_combine_bundle(snode, combine_bundle_node); - switch (sync_state.state) { - case NodeSyncState::Synced: - return; - case NodeSyncState::NoSyncSource: - BKE_report(reports, RPT_INFO, "No bundle signature found"); - return; - case NodeSyncState::ConflictingSyncSources: - BKE_report(reports, RPT_INFO, "Found conflicting bundle signatures"); - return; - case NodeSyncState::CanBeSynced: - break; - } - - auto &storage = *static_cast(combine_bundle_node.storage); - - Map old_identifiers; - for (const int i : IndexRange(storage.items_num)) { - const NodeGeometryCombineBundleItem &item = storage.items[i]; - old_identifiers.add_new(StringRef(item.name), item.identifier); - } - - nodes::socket_items::clear(combine_bundle_node); - for (const nodes::BundleSignature::Item &item : sync_state.source_signature->items) { - NodeGeometryCombineBundleItem &new_item = - *nodes::socket_items::add_item_with_socket_type_and_name< - nodes ::CombineBundleItemsAccessor>( - combine_bundle_node, item.type->type, item.key.c_str()); - if (const std::optional old_identifier = old_identifiers.lookup_try(item.key)) { - new_item.identifier = *old_identifier; - } - } - - BKE_ntree_update_tag_node_property(snode.edittree, &combine_bundle_node); -} - -void sync_sockets_evaluate_closure(SpaceNode &snode, - bNode &evaluate_closure_node, - ReportList *reports) -{ - const ClosureSyncState sync_state = get_sync_state_evaluate_closure(snode, - evaluate_closure_node); - switch (sync_state.state) { - case NodeSyncState::Synced: - return; - case NodeSyncState::NoSyncSource: - BKE_report(reports, RPT_INFO, "No closure signature found"); - return; - case NodeSyncState::ConflictingSyncSources: - BKE_report(reports, RPT_INFO, "Found conflicting closure signatures"); - return; - case NodeSyncState::CanBeSynced: - break; - } - - auto &storage = *static_cast(evaluate_closure_node.storage); - - Map old_input_identifiers; - Map old_output_identifiers; - for (const int i : IndexRange(storage.input_items.items_num)) { - const NodeGeometryEvaluateClosureInputItem &item = storage.input_items.items[i]; - old_input_identifiers.add_new(StringRef(item.name), item.identifier); - } - for (const int i : IndexRange(storage.output_items.items_num)) { - const NodeGeometryEvaluateClosureOutputItem &item = storage.output_items.items[i]; - old_output_identifiers.add_new(StringRef(item.name), item.identifier); - } - - nodes::socket_items::clear(evaluate_closure_node); - nodes::socket_items::clear(evaluate_closure_node); - - for (const nodes::ClosureSignature::Item &item : sync_state.source_signature->inputs) { - NodeGeometryEvaluateClosureInputItem &new_item = - *nodes::socket_items::add_item_with_socket_type_and_name< - nodes::EvaluateClosureInputItemsAccessor>( - evaluate_closure_node, item.type->type, item.key.c_str()); - if (const std::optional old_identifier = old_input_identifiers.lookup_try(item.key)) { - new_item.identifier = *old_identifier; - } - } - for (const nodes::ClosureSignature::Item &item : sync_state.source_signature->outputs) { - NodeGeometryEvaluateClosureOutputItem &new_item = - *nodes::socket_items::add_item_with_socket_type_and_name< - nodes::EvaluateClosureOutputItemsAccessor>( - evaluate_closure_node, item.type->type, item.key.c_str()); - if (const std::optional old_identifier = old_output_identifiers.lookup_try(item.key)) { - new_item.identifier = *old_identifier; - } - } - BKE_ntree_update_tag_node_property(snode.edittree, &evaluate_closure_node); -} - -void sync_sockets_closure(SpaceNode &snode, - bNode &closure_input_node, - bNode &closure_output_node, - const bool initialize_internal_links, - ReportList *reports) -{ - const ClosureSyncState sync_state = get_sync_state_closure_output(snode, closure_output_node); - switch (sync_state.state) { - case NodeSyncState::Synced: - return; - case NodeSyncState::NoSyncSource: - BKE_report(reports, RPT_INFO, "No closure signature found"); - return; - case NodeSyncState::ConflictingSyncSources: - BKE_report(reports, RPT_INFO, "Found conflicting closure signatures"); - return; - case NodeSyncState::CanBeSynced: - break; - } - const nodes::ClosureSignature &signature = *sync_state.source_signature; - - auto &storage = *static_cast(closure_output_node.storage); - - Map old_input_identifiers; - Map old_output_identifiers; - for (const int i : IndexRange(storage.input_items.items_num)) { - const NodeGeometryClosureInputItem &item = storage.input_items.items[i]; - old_input_identifiers.add_new(StringRef(item.name), item.identifier); - } - for (const int i : IndexRange(storage.output_items.items_num)) { - const NodeGeometryClosureOutputItem &item = storage.output_items.items[i]; - old_output_identifiers.add_new(StringRef(item.name), item.identifier); - } - - nodes::socket_items::clear(closure_output_node); - nodes::socket_items::clear(closure_output_node); - - for (const nodes::ClosureSignature::Item &item : signature.inputs) { - NodeGeometryClosureInputItem &new_item = - *nodes::socket_items::add_item_with_socket_type_and_name( - closure_output_node, item.type->type, item.key.c_str()); - if (item.structure_type) { - new_item.structure_type = int(*item.structure_type); - } - if (const std::optional old_identifier = old_input_identifiers.lookup_try(item.key)) { - new_item.identifier = *old_identifier; - } - } - for (const nodes::ClosureSignature::Item &item : signature.outputs) { - NodeGeometryClosureOutputItem &new_item = - *nodes::socket_items::add_item_with_socket_type_and_name< - nodes::ClosureOutputItemsAccessor>( - closure_output_node, item.type->type, item.key.c_str()); - if (const std::optional old_identifier = old_output_identifiers.lookup_try(item.key)) { - new_item.identifier = *old_identifier; - } - } - BKE_ntree_update_tag_node_property(snode.edittree, &closure_input_node); - BKE_ntree_update_tag_node_property(snode.edittree, &closure_output_node); - - if (initialize_internal_links) { - nodes::update_node_declaration_and_sockets(*snode.edittree, closure_input_node); - nodes::update_node_declaration_and_sockets(*snode.edittree, closure_output_node); - - snode.edittree->ensure_topology_cache(); - Vector> internal_links; - for (const int input_i : signature.inputs.index_range()) { - const nodes::ClosureSignature::Item &input_item = signature.inputs[input_i]; - for (const int output_i : signature.outputs.index_range()) { - const nodes::ClosureSignature::Item &output_item = signature.outputs[output_i]; - if (input_item.key == output_item.key) { - internal_links.append({&closure_input_node.output_socket(input_i), - &closure_output_node.input_socket(output_i)}); - } - }; - } - for (auto &&[from_socket, to_socket] : internal_links) { - bke::node_add_link( - *snode.edittree, closure_input_node, *from_socket, closure_output_node, *to_socket); - } - } -} - static Vector get_nodes_to_sync(bContext &C, PointerRNA *ptr) { SpaceNode &snode = *CTX_wm_space_node(&C); @@ -434,206 +65,29 @@ static wmOperatorStatus sockets_sync_exec(bContext *C, wmOperator *op) if (!snode.edittree) { return OPERATOR_CANCELLED; } - Vector nodes_to_sync = get_nodes_to_sync(*C, op->ptr); if (nodes_to_sync.is_empty()) { return OPERATOR_CANCELLED; } - - bNodeTree &tree = *snode.edittree; - const bke::bNodeZoneType &closure_zone_type = *bke::zone_type_by_node_type( - GEO_NODE_CLOSURE_OUTPUT); - for (bNode *node : nodes_to_sync) { - if (node->is_type("GeometryNodeEvaluateClosure")) { - sync_sockets_evaluate_closure(snode, *node, op->reports); - } - else if (node->is_type("GeometryNodeSeparateBundle")) { - sync_sockets_separate_bundle(snode, *node, op->reports); - } - else if (node->is_type("GeometryNodeCombineBundle")) { - sync_sockets_combine_bundle(snode, *node, op->reports); - } - else if (node->is_type("GeometryNodeClosureInput")) { - bNode &closure_input_node = *node; - if (bNode *closure_output_node = closure_zone_type.get_corresponding_output( - tree, closure_input_node)) - { - sync_sockets_closure(snode, closure_input_node, *closure_output_node, false, op->reports); - } - } - else if (node->is_type("GeometryNodeClosureOutput")) { - bNode &closure_output_node = *node; - if (bNode *closure_input_node = closure_zone_type.get_corresponding_input( - tree, closure_output_node)) - { - sync_sockets_closure(snode, *closure_input_node, closure_output_node, false, op->reports); - } - } + nodes::sync_node(*C, *node, op->reports); } - BKE_main_ensure_invariants(bmain, tree.id); + BKE_main_ensure_invariants(bmain, snode.edittree->id); return OPERATOR_FINISHED; } -static std::string get_bundle_sync_tooltip(const nodes::BundleSignature &old_signature, - const nodes::BundleSignature &new_signature) -{ - Vector added_items; - Vector removed_items; - Vector changed_items; - - for (const nodes::BundleSignature::Item &new_item : new_signature.items) { - if (const nodes::BundleSignature::Item *old_item = old_signature.items.lookup_key_ptr_as( - new_item.key)) - { - if (new_item.type->type != old_item->type->type) { - changed_items.append(new_item.key); - } - } - else { - added_items.append(new_item.key); - } - } - for (const nodes::BundleSignature ::Item &old_item : old_signature.items) { - if (!new_signature.items.contains_as(old_item.key)) { - removed_items.append(old_item.key); - } - } - - fmt::memory_buffer string_buffer; - auto buf = fmt::appender(string_buffer); - if (!added_items.is_empty()) { - fmt::format_to(buf, "{}: {}\n", TIP_("Add"), fmt::join(added_items, ", ")); - } - if (!removed_items.is_empty()) { - fmt::format_to(buf, "{}: {}\n", TIP_("Remove"), fmt::join(removed_items, ", ")); - } - if (!changed_items.is_empty()) { - fmt::format_to(buf, "{}: {}\n", TIP_("Change"), fmt::join(changed_items, ", ")); - } - fmt::format_to(buf, TIP_("\nUpdate based on linked bundle signature")); - - return fmt::to_string(string_buffer); -} - -static std::string get_closure_sync_tooltip(const nodes::ClosureSignature &old_signature, - const nodes::ClosureSignature &new_signature) -{ - Vector added_inputs; - Vector removed_inputs; - Vector changed_inputs; - - Vector added_outputs; - Vector removed_outputs; - Vector changed_outputs; - - for (const nodes::ClosureSignature::Item &new_item : new_signature.inputs) { - if (const nodes::ClosureSignature::Item *old_item = old_signature.inputs.lookup_key_ptr_as( - new_item.key)) - { - if (new_item.type->type != old_item->type->type) { - changed_inputs.append(new_item.key); - } - } - else { - added_inputs.append(new_item.key); - } - } - for (const nodes::ClosureSignature::Item &old_item : old_signature.inputs) { - if (!new_signature.inputs.contains_as(old_item.key)) { - removed_inputs.append(old_item.key); - } - } - for (const nodes::ClosureSignature::Item &new_item : new_signature.outputs) { - if (const nodes::ClosureSignature::Item *old_item = old_signature.outputs.lookup_key_ptr_as( - new_item.key)) - { - if (new_item.type->type != old_item->type->type) { - changed_outputs.append(new_item.key); - } - } - else { - added_outputs.append(new_item.key); - } - } - for (const nodes::ClosureSignature::Item &old_item : old_signature.outputs) { - if (!new_signature.outputs.contains_as(old_item.key)) { - removed_outputs.append(old_item.key); - } - } - - fmt::memory_buffer string_buffer; - auto buf = fmt::appender(string_buffer); - if (!added_inputs.is_empty()) { - fmt::format_to(buf, "{}: {}\n", TIP_("Add Inputs"), fmt::join(added_inputs, ", ")); - } - if (!removed_inputs.is_empty()) { - fmt::format_to(buf, "{}: {}\n", TIP_("Remove Inputs"), fmt::join(removed_inputs, ", ")); - } - if (!changed_inputs.is_empty()) { - fmt::format_to(buf, "{}: {}\n", TIP_("Change Inputs"), fmt::join(changed_inputs, ", ")); - } - if (!added_outputs.is_empty()) { - fmt::format_to(buf, "{}: {}\n", TIP_("Add Outputs"), fmt::join(added_outputs, ", ")); - } - if (!removed_outputs.is_empty()) { - fmt::format_to(buf, "{}: {}\n", TIP_("Remove Outputs"), fmt::join(removed_outputs, ", ")); - } - if (!changed_outputs.is_empty()) { - fmt::format_to(buf, "{}: {}\n", TIP_("Change Outputs"), fmt::join(changed_outputs, ", ")); - } - fmt::format_to(buf, TIP_("\nUpdate based on linked closure signature")); - - return fmt::to_string(string_buffer); -} - static std::string sockets_sync_get_description(bContext *C, wmOperatorType *ot, PointerRNA *ptr) { - const SpaceNode &snode = *CTX_wm_space_node(C); Vector nodes_to_sync = get_nodes_to_sync(*C, ptr); if (nodes_to_sync.size() != 1) { return ot->description; } const bNode &node = *nodes_to_sync.first(); - - if (node.is_type("GeometryNodeSeparateBundle")) { - const nodes::BundleSignature old_signature = nodes::BundleSignature::from_separate_bundle_node( - node); - if (const std::optional new_signature = - get_sync_state_separate_bundle(snode, node).source_signature) - { - return get_bundle_sync_tooltip(old_signature, *new_signature); - } + std::string description = nodes::sync_node_description_get(*C, node); + if (description.empty()) { + return ot->description; } - else if (node.is_type("GeometryNodeCombineBundle")) { - const nodes::BundleSignature old_signature = nodes::BundleSignature::from_combine_bundle_node( - node); - if (const std::optional new_signature = - get_sync_state_combine_bundle(snode, node).source_signature) - { - return get_bundle_sync_tooltip(old_signature, *new_signature); - } - } - else if (node.is_type("GeometryNodeEvaluateClosure")) { - const nodes::ClosureSignature old_signature = - nodes::ClosureSignature::from_evaluate_closure_node(node); - if (const std::optional new_signature = - get_sync_state_evaluate_closure(snode, node).source_signature) - { - return get_closure_sync_tooltip(old_signature, *new_signature); - } - } - else if (node.is_type("GeometryNodeClosureOutput")) { - const nodes::ClosureSignature old_signature = - nodes::ClosureSignature::from_closure_output_node(node); - if (const std::optional new_signature = - get_sync_state_closure_output(snode, node).source_signature) - { - return get_closure_sync_tooltip(old_signature, *new_signature); - } - } - - return ot->description; + return description; } void NODE_OT_sockets_sync(wmOperatorType *ot) @@ -653,4 +107,9 @@ void NODE_OT_sockets_sync(wmOperatorType *ot) RNA_def_property_flag(prop, PROP_SKIP_SAVE); } +Map &node_can_sync_cache_get(SpaceNode &snode) +{ + return snode.runtime->node_can_sync_states; +} + } // namespace blender::ed::space_node diff --git a/source/blender/editors/space_node/space_node.cc b/source/blender/editors/space_node/space_node.cc index e3a596d9045..56a85b6e64a 100644 --- a/source/blender/editors/space_node/space_node.cc +++ b/source/blender/editors/space_node/space_node.cc @@ -112,6 +112,7 @@ void ED_node_tree_start(ARegion *region, SpaceNode *snode, bNodeTree *ntree, ID snode->from = from; ED_node_set_active_viewer_key(snode); + snode->runtime->node_can_sync_states.clear(); WM_main_add_notifier(NC_SCENE | ND_NODES, nullptr); } @@ -151,6 +152,7 @@ void ED_node_tree_push(ARegion *region, SpaceNode *snode, bNodeTree *ntree, bNod snode->edittree = ntree; ED_node_set_active_viewer_key(snode); + snode->runtime->node_can_sync_states.clear(); WM_main_add_notifier(NC_SCENE | ND_NODES, nullptr); } @@ -177,6 +179,7 @@ void ED_node_tree_pop(ARegion *region, SpaceNode *snode) } ED_node_set_active_viewer_key(snode); + snode->runtime->node_can_sync_states.clear(); WM_main_add_notifier(NC_SCENE | ND_NODES, nullptr); } diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 8cb955828d6..f43652a569a 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -2225,16 +2225,9 @@ typedef struct NodeGeometryClosureOutputItems { char _pad[4]; } NodeGeometryClosureOutputItems; -typedef enum NodeGeometryClosureFlag { - NODE_GEO_CLOSURE_FLAG_MAY_NEED_SYNC = 1 << 0, -} NodeGeometryClosureFlag; - typedef struct NodeGeometryClosureOutput { NodeGeometryClosureInputItems input_items; NodeGeometryClosureOutputItems output_items; - /** #NodeGeometryClosureFlag */ - uint32_t flag; - char _pad[4]; } NodeGeometryClosureOutput; typedef struct NodeGeometryEvaluateClosureInputItem { @@ -2273,16 +2266,9 @@ typedef struct NodeGeometryEvaluateClosureOutputItems { char _pad[4]; } NodeGeometryEvaluateClosureOutputItems; -typedef enum NodeGeometryEvaluateClosureFlag { - NODE_GEO_EVALUATE_CLOSURE_FLAG_MAY_NEED_SYNC = 1 << 0, -} NodeGeometryEvaluateClosureFlag; - typedef struct NodeGeometryEvaluateClosure { NodeGeometryEvaluateClosureInputItems input_items; NodeGeometryEvaluateClosureOutputItems output_items; - /** #NodeGeometryEvaluateClosureFlag */ - uint32_t flag; - char _pad[4]; } NodeGeometryEvaluateClosure; typedef struct IndexSwitchItem { @@ -2406,17 +2392,12 @@ typedef struct NodeGeometryCombineBundleItem { char _pad[2]; } NodeGeometryCombineBundleItem; -typedef enum NodeGeometryCombineBundleFlag { - NODE_GEO_COMBINE_BUNDLE_FLAG_MAY_NEED_SYNC = 1 << 0, -} NodeGeometryCombineBundleFlag; - typedef struct NodeGeometryCombineBundle { NodeGeometryCombineBundleItem *items; int items_num; int next_identifier; int active_index; - /** #NodeGeometryCombineBundleFlag */ - uint32_t flag; + char _pad[4]; } NodeGeometryCombineBundle; typedef struct NodeGeometrySeparateBundleItem { @@ -2426,17 +2407,12 @@ typedef struct NodeGeometrySeparateBundleItem { char _pad[2]; } NodeGeometrySeparateBundleItem; -typedef enum NodeGeometrySeparateBundleFlag { - NODE_GEO_SEPARATE_BUNDLE_FLAG_MAY_NEED_SYNC = 1 << 0, -} NodeGeometrySeparateBundleFlag; - typedef struct NodeGeometrySeparateBundle { NodeGeometrySeparateBundleItem *items; int items_num; int next_identifier; int active_index; - /** #NodeGeometrySeparateBundleFlag */ - uint32_t flag; + char _pad[4]; } NodeGeometrySeparateBundle; typedef struct NodeFunctionFormatStringItem { diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index da12b0e13de..4b7ed9a778b 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -98,6 +98,7 @@ set(SRC intern/partial_eval.cc intern/socket_search_link.cc intern/socket_usage_inference.cc + intern/sync_sockets.cc intern/value_elem.cc intern/volume_grid_function_eval.cc @@ -146,6 +147,7 @@ set(SRC NOD_socket_search_link.hh NOD_socket_usage_inference.hh NOD_socket_usage_inference_fwd.hh + NOD_sync_sockets.hh NOD_texture.h NOD_value_elem.hh NOD_value_elem_eval.hh diff --git a/source/blender/nodes/NOD_sync_sockets.hh b/source/blender/nodes/NOD_sync_sockets.hh new file mode 100644 index 00000000000..62baa6443d1 --- /dev/null +++ b/source/blender/nodes/NOD_sync_sockets.hh @@ -0,0 +1,46 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +struct SpaceNode; +struct bNode; +struct ReportList; +struct bContext; +struct bNodeTree; +struct Main; + +namespace blender::nodes { + +/** + * Sync the sockets of that node if possible. For example, a Separate Bundle node will be updated + * to match a linked Combine Bundle if one is found. + */ +void sync_node(bContext &C, bNode &node, ReportList *reports); + +/** + * Get a description of what syncing the node would do. This can be used as tooltip. + */ +std::string sync_node_description_get(const bContext &C, const bNode &node); + +/** Access (cached) information of whether a specific node can be synced currently. */ +bool node_can_sync_sockets(const bContext &C, const bNodeTree &tree, const bNode &node); +void node_can_sync_cache_clear(Main &bmain); + +void sync_sockets_evaluate_closure(SpaceNode &snode, + bNode &evaluate_closure_node, + ReportList *reports); +void sync_sockets_separate_bundle(SpaceNode &snode, + bNode &separate_bundle_node, + ReportList *reports); +void sync_sockets_combine_bundle(SpaceNode &snode, + bNode &combine_bundle_node, + ReportList *reports); +void sync_sockets_closure(SpaceNode &snode, + bNode &closure_input_node, + bNode &closure_output_node, + const bool initialize_internal_links, + ReportList *reports); + +} // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_closure.cc b/source/blender/nodes/geometry/nodes/node_geo_closure.cc index 98079827fa6..1ed8b49e9f5 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_closure.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_closure.cc @@ -13,6 +13,7 @@ #include "NOD_socket_items_ops.hh" #include "NOD_socket_items_ui.hh" #include "NOD_socket_search_link.hh" +#include "NOD_sync_sockets.hh" #include "BLO_read_write.hh" @@ -211,7 +212,7 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) params.connect_available_socket(output_node, "Closure"); SpaceNode &snode = *CTX_wm_space_node(¶ms.C); - ed::space_node::sync_sockets_closure(snode, input_node, output_node, true, nullptr); + sync_sockets_closure(snode, input_node, output_node, true, nullptr); }); } @@ -227,37 +228,6 @@ static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader & socket_items::blend_read_data(&reader, node); } -static bool node_can_sync_sockets(const bContext &C, - const bNodeTree & /*ntree*/, - const bNode &node) -{ - const SpaceNode *snode = CTX_wm_space_node(&C); - if (!snode) { - return false; - } - const NodeGeometryClosureOutput &storage = node_storage(node); - if (!(storage.flag & NODE_GEO_CLOSURE_FLAG_MAY_NEED_SYNC)) { - return false; - } - const ed::space_node::NodeSyncState state = ed::space_node::sync_sockets_state_closure_output( - *snode, node); - switch (state) { - case ed::space_node::NodeSyncState::NoSyncSource: - case ed::space_node::NodeSyncState::Synced: { - const_cast(storage).flag &= - ~NODE_GEO_CLOSURE_FLAG_MAY_NEED_SYNC; - break; - } - case ed::space_node::NodeSyncState::CanBeSynced: { - return true; - } - case ed::space_node::NodeSyncState::ConflictingSyncSources: { - break; - } - } - return false; -} - static void node_register() { static blender::bke::bNodeType ntype; @@ -272,7 +242,6 @@ static void node_register() ntype.gather_link_search_ops = node_gather_link_searches; ntype.insert_link = node_insert_link; ntype.draw_buttons_ex = node_layout_ex; - ntype.can_sync_sockets = node_can_sync_sockets; ntype.blend_write_storage_content = node_blend_write; ntype.blend_data_read_storage_content = node_blend_read; bke::node_type_storage(ntype, "NodeGeometryClosureOutput", node_free_storage, node_copy_storage); diff --git a/source/blender/nodes/geometry/nodes/node_geo_combine_bundle.cc b/source/blender/nodes/geometry/nodes/node_geo_combine_bundle.cc index f5c645d759e..1c2195e6c4c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_combine_bundle.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_combine_bundle.cc @@ -9,6 +9,7 @@ #include "NOD_socket_items_ops.hh" #include "NOD_socket_items_ui.hh" #include "NOD_socket_search_link.hh" +#include "NOD_sync_sockets.hh" #include "BKE_idprop.hh" @@ -139,7 +140,7 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) params.connect_available_socket(node, "Bundle"); SpaceNode &snode = *CTX_wm_space_node(¶ms.C); - ed::space_node::sync_sockets_combine_bundle(snode, node, nullptr); + sync_sockets_combine_bundle(snode, node, nullptr); }); } @@ -153,37 +154,6 @@ static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader & socket_items::blend_read_data(&reader, node); } -static bool node_can_sync_sockets(const bContext &C, - const bNodeTree & /*ntree*/, - const bNode &node) -{ - const SpaceNode *snode = CTX_wm_space_node(&C); - if (!snode) { - return false; - } - const NodeGeometryCombineBundle &storage = node_storage(node); - if (!(storage.flag & NODE_GEO_COMBINE_BUNDLE_FLAG_MAY_NEED_SYNC)) { - return false; - } - const ed::space_node::NodeSyncState state = ed::space_node::sync_sockets_state_combine_bundle( - *snode, node); - switch (state) { - case ed::space_node::NodeSyncState::NoSyncSource: - case ed::space_node::NodeSyncState::Synced: { - const_cast(storage).flag &= - ~NODE_GEO_COMBINE_BUNDLE_FLAG_MAY_NEED_SYNC; - break; - } - case ed::space_node::NodeSyncState::CanBeSynced: { - return true; - } - case ed::space_node::NodeSyncState::ConflictingSyncSources: { - break; - } - } - return false; -} - static void node_register() { static blender::bke::bNodeType ntype; @@ -201,7 +171,6 @@ static void node_register() ntype.register_operators = node_operators; ntype.blend_write_storage_content = node_blend_write; ntype.blend_data_read_storage_content = node_blend_read; - ntype.can_sync_sockets = node_can_sync_sockets; bke::node_type_storage(ntype, "NodeGeometryCombineBundle", node_free_storage, node_copy_storage); blender::bke::node_register_type(ntype); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_evaluate_closure.cc b/source/blender/nodes/geometry/nodes/node_geo_evaluate_closure.cc index ba144a807c5..a33d3f8303f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_evaluate_closure.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_evaluate_closure.cc @@ -10,6 +10,7 @@ #include "NOD_socket_items_ops.hh" #include "NOD_socket_items_ui.hh" #include "NOD_socket_search_link.hh" +#include "NOD_sync_sockets.hh" #include "BKE_idprop.hh" @@ -135,7 +136,7 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) params.connect_available_socket(node, "Closure"); SpaceNode &snode = *CTX_wm_space_node(¶ms.C); - ed::space_node::sync_sockets_evaluate_closure(snode, node, nullptr); + sync_sockets_evaluate_closure(snode, node, nullptr); }); } @@ -157,37 +158,6 @@ static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader & socket_items::blend_read_data(&reader, node); } -static bool node_can_sync_sockets(const bContext &C, - const bNodeTree & /*ntree*/, - const bNode &node) -{ - const SpaceNode *snode = CTX_wm_space_node(&C); - if (!snode) { - return false; - } - const NodeGeometryEvaluateClosure &storage = node_storage(node); - if (!(storage.flag & NODE_GEO_EVALUATE_CLOSURE_FLAG_MAY_NEED_SYNC)) { - return false; - } - const ed::space_node::NodeSyncState state = ed::space_node::sync_sockets_state_evaluate_closure( - *snode, node); - switch (state) { - case ed::space_node::NodeSyncState::NoSyncSource: - case ed::space_node::NodeSyncState::Synced: { - const_cast(storage).flag &= - ~NODE_GEO_EVALUATE_CLOSURE_FLAG_MAY_NEED_SYNC; - break; - } - case ed::space_node::NodeSyncState::CanBeSynced: { - return true; - } - case ed::space_node::NodeSyncState::ConflictingSyncSources: { - break; - } - } - return false; -} - static void node_register() { static blender::bke::bNodeType ntype; @@ -204,7 +174,6 @@ static void node_register() ntype.register_operators = node_operators; ntype.blend_write_storage_content = node_blend_write; ntype.blend_data_read_storage_content = node_blend_read; - ntype.can_sync_sockets = node_can_sync_sockets; bke::node_type_storage( ntype, "NodeGeometryEvaluateClosure", node_free_storage, node_copy_storage); blender::bke::node_register_type(ntype); diff --git a/source/blender/nodes/geometry/nodes/node_geo_separate_bundle.cc b/source/blender/nodes/geometry/nodes/node_geo_separate_bundle.cc index 8b99cc06f23..6aafc0a5930 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_separate_bundle.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_separate_bundle.cc @@ -7,17 +7,17 @@ #include "ED_screen.hh" #include "NOD_geo_bundle.hh" +#include "NOD_geometry_nodes_bundle.hh" #include "NOD_socket_items_blend.hh" #include "NOD_socket_items_ops.hh" #include "NOD_socket_items_ui.hh" #include "NOD_socket_search_link.hh" +#include "NOD_sync_sockets.hh" #include "BKE_idprop.hh" #include "BLO_read_write.hh" -#include "NOD_geometry_nodes_bundle.hh" - #include "UI_interface_layout.hh" #include @@ -186,7 +186,7 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms) params.connect_available_socket(node, "Bundle"); SpaceNode &snode = *CTX_wm_space_node(¶ms.C); - ed::space_node::sync_sockets_separate_bundle(snode, node, nullptr); + sync_sockets_separate_bundle(snode, node, nullptr); }); } @@ -200,37 +200,6 @@ static void node_blend_read(bNodeTree & /*tree*/, bNode &node, BlendDataReader & socket_items::blend_read_data(&reader, node); } -static bool node_can_sync_sockets(const bContext &C, - const bNodeTree & /*ntree*/, - const bNode &node) -{ - const SpaceNode *snode = CTX_wm_space_node(&C); - if (!snode) { - return false; - } - const NodeGeometrySeparateBundle &storage = node_storage(node); - if (!(storage.flag & NODE_GEO_SEPARATE_BUNDLE_FLAG_MAY_NEED_SYNC)) { - return false; - } - const ed::space_node::NodeSyncState state = ed::space_node::sync_sockets_state_separate_bundle( - *snode, node); - switch (state) { - case ed::space_node::NodeSyncState::NoSyncSource: - case ed::space_node::NodeSyncState::Synced: { - const_cast(storage).flag &= - ~NODE_GEO_SEPARATE_BUNDLE_FLAG_MAY_NEED_SYNC; - break; - } - case ed::space_node::NodeSyncState::CanBeSynced: { - return true; - } - case ed::space_node::NodeSyncState::ConflictingSyncSources: { - break; - } - } - return false; -} - static void node_register() { static blender::bke::bNodeType ntype; @@ -248,7 +217,6 @@ static void node_register() ntype.register_operators = node_operators; ntype.blend_write_storage_content = node_blend_write; ntype.blend_data_read_storage_content = node_blend_read; - ntype.can_sync_sockets = node_can_sync_sockets; bke::node_type_storage( ntype, "NodeGeometrySeparateBundle", node_free_storage, node_copy_storage); blender::bke::node_register_type(ntype); diff --git a/source/blender/nodes/intern/sync_sockets.cc b/source/blender/nodes/intern/sync_sockets.cc new file mode 100644 index 00000000000..52806ead885 --- /dev/null +++ b/source/blender/nodes/intern/sync_sockets.cc @@ -0,0 +1,616 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#include "DNA_node_types.h" +#include "DNA_space_types.h" + +#include "RNA_access.hh" + +#include "WM_api.hh" + +#include "BKE_compute_context_cache.hh" +#include "BKE_context.hh" +#include "BKE_main.hh" +#include "BKE_main_invariants.hh" +#include "BKE_node_legacy_types.hh" +#include "BKE_node_runtime.hh" +#include "BKE_node_tree_update.hh" +#include "BKE_report.hh" +#include "BKE_workspace.hh" + +#include "ED_node.hh" +#include "ED_screen.hh" + +#include "BLI_listbase.h" + +#include "BLT_translation.hh" + +#include "NOD_geo_bundle.hh" +#include "NOD_geo_closure.hh" +#include "NOD_socket_items.hh" +#include "NOD_sync_sockets.hh" + +namespace blender::nodes { + +enum class NodeSyncState { + Synced, + CanBeSynced, + NoSyncSource, + ConflictingSyncSources, +}; + +struct BundleSyncState { + NodeSyncState state; + std::optional source_signature; +}; + +struct ClosureSyncState { + NodeSyncState state; + std::optional source_signature; +}; + +static BundleSyncState get_sync_state_separate_bundle(const SpaceNode &snode, + const bNode &separate_bundle_node) +{ + BLI_assert(separate_bundle_node.is_type("GeometryNodeSeparateBundle")); + snode.edittree->ensure_topology_cache(); + const bNodeSocket &bundle_socket = separate_bundle_node.input_socket(0); + + bke::ComputeContextCache compute_context_cache; + const ComputeContext *current_context = ed::space_node::compute_context_for_edittree_socket( + snode, compute_context_cache, bundle_socket); + const Vector source_signatures = + ed::space_node::gather_linked_origin_bundle_signatures( + current_context, bundle_socket, compute_context_cache); + if (source_signatures.is_empty()) { + return {NodeSyncState::NoSyncSource}; + } + if (!nodes::BundleSignature::all_matching_exactly(source_signatures)) { + return {NodeSyncState::ConflictingSyncSources}; + } + const nodes::BundleSignature &source_signature = source_signatures[0]; + const nodes::BundleSignature ¤t_signature = + nodes::BundleSignature::from_separate_bundle_node(separate_bundle_node); + if (!source_signature.matches_exactly(current_signature)) { + return {NodeSyncState::CanBeSynced, source_signature}; + } + return {NodeSyncState::Synced}; +} + +static BundleSyncState get_sync_state_combine_bundle(const SpaceNode &snode, + const bNode &combine_bundle_node) +{ + BLI_assert(combine_bundle_node.is_type("GeometryNodeCombineBundle")); + snode.edittree->ensure_topology_cache(); + const bNodeSocket &bundle_socket = combine_bundle_node.output_socket(0); + + bke::ComputeContextCache compute_context_cache; + const ComputeContext *current_context = ed::space_node::compute_context_for_edittree_socket( + snode, compute_context_cache, bundle_socket); + const Vector source_signatures = + ed::space_node::gather_linked_target_bundle_signatures( + current_context, bundle_socket, compute_context_cache); + if (source_signatures.is_empty()) { + return {NodeSyncState::NoSyncSource}; + } + if (!nodes::BundleSignature::all_matching_exactly(source_signatures)) { + return {NodeSyncState::ConflictingSyncSources}; + } + const nodes::BundleSignature &source_signature = source_signatures[0]; + const nodes::BundleSignature ¤t_signature = + nodes::BundleSignature::from_combine_bundle_node(combine_bundle_node); + if (!source_signature.matches_exactly(current_signature)) { + return {NodeSyncState::CanBeSynced, source_signature}; + } + return {NodeSyncState::Synced}; +} + +static ClosureSyncState get_sync_state_closure_output(const SpaceNode &snode, + const bNode &closure_output_node) +{ + snode.edittree->ensure_topology_cache(); + const bNodeSocket &closure_socket = closure_output_node.output_socket(0); + + bke::ComputeContextCache compute_context_cache; + const ComputeContext *current_context = ed::space_node::compute_context_for_edittree_socket( + snode, compute_context_cache, closure_socket); + const Vector source_signatures = + ed::space_node::gather_linked_target_closure_signatures( + current_context, closure_socket, compute_context_cache); + if (source_signatures.is_empty()) { + return {NodeSyncState::NoSyncSource}; + } + if (!nodes::ClosureSignature::all_matching_exactly(source_signatures)) { + return {NodeSyncState::ConflictingSyncSources}; + } + const nodes::ClosureSignature &source_signature = source_signatures[0]; + const nodes::ClosureSignature ¤t_signature = + nodes::ClosureSignature::from_closure_output_node(closure_output_node); + if (!source_signature.matches_exactly(current_signature)) { + return {NodeSyncState::CanBeSynced, source_signature}; + } + return {NodeSyncState::Synced}; +} + +static ClosureSyncState get_sync_state_evaluate_closure(const SpaceNode &snode, + const bNode &evaluate_closure_node) +{ + snode.edittree->ensure_topology_cache(); + const bNodeSocket &closure_socket = evaluate_closure_node.input_socket(0); + + bke::ComputeContextCache compute_context_cache; + const ComputeContext *current_context = ed::space_node::compute_context_for_edittree_socket( + snode, compute_context_cache, closure_socket); + const Vector source_signatures = + ed::space_node::gather_linked_origin_closure_signatures( + current_context, closure_socket, compute_context_cache); + if (source_signatures.is_empty()) { + return {NodeSyncState::NoSyncSource}; + } + if (!nodes::ClosureSignature::all_matching_exactly(source_signatures)) { + return {NodeSyncState::ConflictingSyncSources}; + } + const nodes::ClosureSignature &source_signature = source_signatures[0]; + const nodes::ClosureSignature ¤t_signature = + nodes::ClosureSignature::from_evaluate_closure_node(evaluate_closure_node); + if (!source_signature.matches_exactly(current_signature)) { + return {NodeSyncState::CanBeSynced, source_signature}; + } + return {NodeSyncState::Synced}; +} + +void sync_sockets_separate_bundle(SpaceNode &snode, + bNode &separate_bundle_node, + ReportList *reports) +{ + const BundleSyncState sync_state = get_sync_state_separate_bundle(snode, separate_bundle_node); + switch (sync_state.state) { + case NodeSyncState::Synced: + return; + case NodeSyncState::NoSyncSource: + BKE_report(reports, RPT_INFO, "No bundle signature found"); + return; + case NodeSyncState::ConflictingSyncSources: + BKE_report(reports, RPT_INFO, "Found conflicting bundle signatures"); + return; + case NodeSyncState::CanBeSynced: + break; + } + + auto &storage = *static_cast(separate_bundle_node.storage); + + Map old_identifiers; + for (const int i : IndexRange(storage.items_num)) { + const NodeGeometrySeparateBundleItem &item = storage.items[i]; + old_identifiers.add_new(StringRef(item.name), item.identifier); + } + + nodes::socket_items::clear(separate_bundle_node); + for (const nodes::BundleSignature::Item &item : sync_state.source_signature->items) { + NodeGeometrySeparateBundleItem &new_item = + *nodes::socket_items::add_item_with_socket_type_and_name< + nodes ::SeparateBundleItemsAccessor>( + separate_bundle_node, item.type->type, item.key.c_str()); + if (const std::optional old_identifier = old_identifiers.lookup_try(item.key)) { + new_item.identifier = *old_identifier; + } + } + BKE_ntree_update_tag_node_property(snode.edittree, &separate_bundle_node); +} + +void sync_sockets_combine_bundle(SpaceNode &snode, bNode &combine_bundle_node, ReportList *reports) +{ + const BundleSyncState sync_state = get_sync_state_combine_bundle(snode, combine_bundle_node); + switch (sync_state.state) { + case NodeSyncState::Synced: + return; + case NodeSyncState::NoSyncSource: + BKE_report(reports, RPT_INFO, "No bundle signature found"); + return; + case NodeSyncState::ConflictingSyncSources: + BKE_report(reports, RPT_INFO, "Found conflicting bundle signatures"); + return; + case NodeSyncState::CanBeSynced: + break; + } + + auto &storage = *static_cast(combine_bundle_node.storage); + + Map old_identifiers; + for (const int i : IndexRange(storage.items_num)) { + const NodeGeometryCombineBundleItem &item = storage.items[i]; + old_identifiers.add_new(StringRef(item.name), item.identifier); + } + + nodes::socket_items::clear(combine_bundle_node); + for (const nodes::BundleSignature::Item &item : sync_state.source_signature->items) { + NodeGeometryCombineBundleItem &new_item = + *nodes::socket_items::add_item_with_socket_type_and_name< + nodes ::CombineBundleItemsAccessor>( + combine_bundle_node, item.type->type, item.key.c_str()); + if (const std::optional old_identifier = old_identifiers.lookup_try(item.key)) { + new_item.identifier = *old_identifier; + } + } + + BKE_ntree_update_tag_node_property(snode.edittree, &combine_bundle_node); +} + +void sync_sockets_evaluate_closure(SpaceNode &snode, + bNode &evaluate_closure_node, + ReportList *reports) +{ + const ClosureSyncState sync_state = get_sync_state_evaluate_closure(snode, + evaluate_closure_node); + switch (sync_state.state) { + case NodeSyncState::Synced: + return; + case NodeSyncState::NoSyncSource: + BKE_report(reports, RPT_INFO, "No closure signature found"); + return; + case NodeSyncState::ConflictingSyncSources: + BKE_report(reports, RPT_INFO, "Found conflicting closure signatures"); + return; + case NodeSyncState::CanBeSynced: + break; + } + + auto &storage = *static_cast(evaluate_closure_node.storage); + + Map old_input_identifiers; + Map old_output_identifiers; + for (const int i : IndexRange(storage.input_items.items_num)) { + const NodeGeometryEvaluateClosureInputItem &item = storage.input_items.items[i]; + old_input_identifiers.add_new(StringRef(item.name), item.identifier); + } + for (const int i : IndexRange(storage.output_items.items_num)) { + const NodeGeometryEvaluateClosureOutputItem &item = storage.output_items.items[i]; + old_output_identifiers.add_new(StringRef(item.name), item.identifier); + } + + nodes::socket_items::clear(evaluate_closure_node); + nodes::socket_items::clear(evaluate_closure_node); + + for (const nodes::ClosureSignature::Item &item : sync_state.source_signature->inputs) { + NodeGeometryEvaluateClosureInputItem &new_item = + *nodes::socket_items::add_item_with_socket_type_and_name< + nodes::EvaluateClosureInputItemsAccessor>( + evaluate_closure_node, item.type->type, item.key.c_str()); + if (const std::optional old_identifier = old_input_identifiers.lookup_try(item.key)) { + new_item.identifier = *old_identifier; + } + } + for (const nodes::ClosureSignature::Item &item : sync_state.source_signature->outputs) { + NodeGeometryEvaluateClosureOutputItem &new_item = + *nodes::socket_items::add_item_with_socket_type_and_name< + nodes::EvaluateClosureOutputItemsAccessor>( + evaluate_closure_node, item.type->type, item.key.c_str()); + if (const std::optional old_identifier = old_output_identifiers.lookup_try(item.key)) { + new_item.identifier = *old_identifier; + } + } + BKE_ntree_update_tag_node_property(snode.edittree, &evaluate_closure_node); +} + +void sync_sockets_closure(SpaceNode &snode, + bNode &closure_input_node, + bNode &closure_output_node, + const bool initialize_internal_links, + ReportList *reports) +{ + const ClosureSyncState sync_state = get_sync_state_closure_output(snode, closure_output_node); + switch (sync_state.state) { + case NodeSyncState::Synced: + return; + case NodeSyncState::NoSyncSource: + BKE_report(reports, RPT_INFO, "No closure signature found"); + return; + case NodeSyncState::ConflictingSyncSources: + BKE_report(reports, RPT_INFO, "Found conflicting closure signatures"); + return; + case NodeSyncState::CanBeSynced: + break; + } + const nodes::ClosureSignature &signature = *sync_state.source_signature; + + auto &storage = *static_cast(closure_output_node.storage); + + Map old_input_identifiers; + Map old_output_identifiers; + for (const int i : IndexRange(storage.input_items.items_num)) { + const NodeGeometryClosureInputItem &item = storage.input_items.items[i]; + old_input_identifiers.add_new(StringRef(item.name), item.identifier); + } + for (const int i : IndexRange(storage.output_items.items_num)) { + const NodeGeometryClosureOutputItem &item = storage.output_items.items[i]; + old_output_identifiers.add_new(StringRef(item.name), item.identifier); + } + + nodes::socket_items::clear(closure_output_node); + nodes::socket_items::clear(closure_output_node); + + for (const nodes::ClosureSignature::Item &item : signature.inputs) { + NodeGeometryClosureInputItem &new_item = + *nodes::socket_items::add_item_with_socket_type_and_name( + closure_output_node, item.type->type, item.key.c_str()); + if (item.structure_type) { + new_item.structure_type = int(*item.structure_type); + } + if (const std::optional old_identifier = old_input_identifiers.lookup_try(item.key)) { + new_item.identifier = *old_identifier; + } + } + for (const nodes::ClosureSignature::Item &item : signature.outputs) { + NodeGeometryClosureOutputItem &new_item = + *nodes::socket_items::add_item_with_socket_type_and_name< + nodes::ClosureOutputItemsAccessor>( + closure_output_node, item.type->type, item.key.c_str()); + if (const std::optional old_identifier = old_output_identifiers.lookup_try(item.key)) { + new_item.identifier = *old_identifier; + } + } + BKE_ntree_update_tag_node_property(snode.edittree, &closure_input_node); + BKE_ntree_update_tag_node_property(snode.edittree, &closure_output_node); + + if (initialize_internal_links) { + nodes::update_node_declaration_and_sockets(*snode.edittree, closure_input_node); + nodes::update_node_declaration_and_sockets(*snode.edittree, closure_output_node); + + snode.edittree->ensure_topology_cache(); + Vector> internal_links; + for (const int input_i : signature.inputs.index_range()) { + const nodes::ClosureSignature::Item &input_item = signature.inputs[input_i]; + for (const int output_i : signature.outputs.index_range()) { + const nodes::ClosureSignature::Item &output_item = signature.outputs[output_i]; + if (input_item.key == output_item.key) { + internal_links.append({&closure_input_node.output_socket(input_i), + &closure_output_node.input_socket(output_i)}); + } + }; + } + for (auto &&[from_socket, to_socket] : internal_links) { + bke::node_add_link( + *snode.edittree, closure_input_node, *from_socket, closure_output_node, *to_socket); + } + } +} + +static std::string get_bundle_sync_tooltip(const nodes::BundleSignature &old_signature, + const nodes::BundleSignature &new_signature) +{ + Vector added_items; + Vector removed_items; + Vector changed_items; + + for (const nodes::BundleSignature::Item &new_item : new_signature.items) { + if (const nodes::BundleSignature::Item *old_item = old_signature.items.lookup_key_ptr_as( + new_item.key)) + { + if (new_item.type->type != old_item->type->type) { + changed_items.append(new_item.key); + } + } + else { + added_items.append(new_item.key); + } + } + for (const nodes::BundleSignature ::Item &old_item : old_signature.items) { + if (!new_signature.items.contains_as(old_item.key)) { + removed_items.append(old_item.key); + } + } + + fmt::memory_buffer string_buffer; + auto buf = fmt::appender(string_buffer); + if (!added_items.is_empty()) { + fmt::format_to(buf, "{}: {}\n", TIP_("Add"), fmt::join(added_items, ", ")); + } + if (!removed_items.is_empty()) { + fmt::format_to(buf, "{}: {}\n", TIP_("Remove"), fmt::join(removed_items, ", ")); + } + if (!changed_items.is_empty()) { + fmt::format_to(buf, "{}: {}\n", TIP_("Change"), fmt::join(changed_items, ", ")); + } + fmt::format_to(buf, TIP_("\nUpdate based on linked bundle signature")); + + return fmt::to_string(string_buffer); +} + +static std::string get_closure_sync_tooltip(const nodes::ClosureSignature &old_signature, + const nodes::ClosureSignature &new_signature) +{ + Vector added_inputs; + Vector removed_inputs; + Vector changed_inputs; + + Vector added_outputs; + Vector removed_outputs; + Vector changed_outputs; + + for (const nodes::ClosureSignature::Item &new_item : new_signature.inputs) { + if (const nodes::ClosureSignature::Item *old_item = old_signature.inputs.lookup_key_ptr_as( + new_item.key)) + { + if (new_item.type->type != old_item->type->type) { + changed_inputs.append(new_item.key); + } + } + else { + added_inputs.append(new_item.key); + } + } + for (const nodes::ClosureSignature::Item &old_item : old_signature.inputs) { + if (!new_signature.inputs.contains_as(old_item.key)) { + removed_inputs.append(old_item.key); + } + } + for (const nodes::ClosureSignature::Item &new_item : new_signature.outputs) { + if (const nodes::ClosureSignature::Item *old_item = old_signature.outputs.lookup_key_ptr_as( + new_item.key)) + { + if (new_item.type->type != old_item->type->type) { + changed_outputs.append(new_item.key); + } + } + else { + added_outputs.append(new_item.key); + } + } + for (const nodes::ClosureSignature::Item &old_item : old_signature.outputs) { + if (!new_signature.outputs.contains_as(old_item.key)) { + removed_outputs.append(old_item.key); + } + } + + fmt::memory_buffer string_buffer; + auto buf = fmt::appender(string_buffer); + if (!added_inputs.is_empty()) { + fmt::format_to(buf, "{}: {}\n", TIP_("Add Inputs"), fmt::join(added_inputs, ", ")); + } + if (!removed_inputs.is_empty()) { + fmt::format_to(buf, "{}: {}\n", TIP_("Remove Inputs"), fmt::join(removed_inputs, ", ")); + } + if (!changed_inputs.is_empty()) { + fmt::format_to(buf, "{}: {}\n", TIP_("Change Inputs"), fmt::join(changed_inputs, ", ")); + } + if (!added_outputs.is_empty()) { + fmt::format_to(buf, "{}: {}\n", TIP_("Add Outputs"), fmt::join(added_outputs, ", ")); + } + if (!removed_outputs.is_empty()) { + fmt::format_to(buf, "{}: {}\n", TIP_("Remove Outputs"), fmt::join(removed_outputs, ", ")); + } + if (!changed_outputs.is_empty()) { + fmt::format_to(buf, "{}: {}\n", TIP_("Change Outputs"), fmt::join(changed_outputs, ", ")); + } + fmt::format_to(buf, TIP_("\nUpdate based on linked closure signature")); + + return fmt::to_string(string_buffer); +} + +void sync_node(bContext &C, bNode &node, ReportList *reports) +{ + const bke::bNodeZoneType &closure_zone_type = *bke::zone_type_by_node_type( + GEO_NODE_CLOSURE_OUTPUT); + SpaceNode &snode = *CTX_wm_space_node(&C); + if (node.is_type("GeometryNodeEvaluateClosure")) { + sync_sockets_evaluate_closure(snode, node, reports); + } + else if (node.is_type("GeometryNodeSeparateBundle")) { + sync_sockets_separate_bundle(snode, node, reports); + } + else if (node.is_type("GeometryNodeCombineBundle")) { + sync_sockets_combine_bundle(snode, node, reports); + } + else if (node.is_type("GeometryNodeClosureInput")) { + bNode &closure_input_node = node; + if (bNode *closure_output_node = closure_zone_type.get_corresponding_output( + *snode.edittree, closure_input_node)) + { + sync_sockets_closure(snode, closure_input_node, *closure_output_node, false, reports); + } + } + else if (node.is_type("GeometryNodeClosureOutput")) { + bNode &closure_output_node = node; + if (bNode *closure_input_node = closure_zone_type.get_corresponding_input(*snode.edittree, + closure_output_node)) + { + sync_sockets_closure(snode, *closure_input_node, closure_output_node, false, reports); + } + } +} + +std::string sync_node_description_get(const bContext &C, const bNode &node) +{ + const SpaceNode *snode = CTX_wm_space_node(&C); + if (!snode) { + return ""; + } + + if (node.is_type("GeometryNodeSeparateBundle")) { + const nodes::BundleSignature old_signature = nodes::BundleSignature::from_separate_bundle_node( + node); + if (const std::optional new_signature = + get_sync_state_separate_bundle(*snode, node).source_signature) + { + return get_bundle_sync_tooltip(old_signature, *new_signature); + } + } + else if (node.is_type("GeometryNodeCombineBundle")) { + const nodes::BundleSignature old_signature = nodes::BundleSignature::from_combine_bundle_node( + node); + if (const std::optional new_signature = + get_sync_state_combine_bundle(*snode, node).source_signature) + { + return get_bundle_sync_tooltip(old_signature, *new_signature); + } + } + else if (node.is_type("GeometryNodeEvaluateClosure")) { + const nodes::ClosureSignature old_signature = + nodes::ClosureSignature::from_evaluate_closure_node(node); + if (const std::optional new_signature = + get_sync_state_evaluate_closure(*snode, node).source_signature) + { + return get_closure_sync_tooltip(old_signature, *new_signature); + } + } + else if (node.is_type("GeometryNodeClosureOutput")) { + const nodes::ClosureSignature old_signature = + nodes::ClosureSignature::from_closure_output_node(node); + if (const std::optional new_signature = + get_sync_state_closure_output(*snode, node).source_signature) + { + return get_closure_sync_tooltip(old_signature, *new_signature); + } + } + return ""; +} + +bool node_can_sync_sockets(const bContext &C, const bNodeTree & /*tree*/, const bNode &node) +{ + SpaceNode *snode = CTX_wm_space_node(&C); + if (!snode) { + return false; + } + Map &cache = ed::space_node::node_can_sync_cache_get(*snode); + const bool can_sync = cache.lookup_or_add_cb(node.identifier, [&]() { + if (node.is_type("GeometryNodeEvaluateClosure")) { + return get_sync_state_evaluate_closure(*snode, node).source_signature.has_value(); + } + if (node.is_type("GeometryNodeClosureOutput")) { + return get_sync_state_closure_output(*snode, node).source_signature.has_value(); + } + if (node.is_type("GeometryNodeCombineBundle")) { + return get_sync_state_combine_bundle(*snode, node).source_signature.has_value(); + } + if (node.is_type("GeometryNodeSeparateBundle")) { + return get_sync_state_separate_bundle(*snode, node).source_signature.has_value(); + } + return false; + }); + return can_sync; +} + +void node_can_sync_cache_clear(Main &bmain) +{ + if (wmWindowManager *wm = static_cast(bmain.wm.first)) { + LISTBASE_FOREACH (wmWindow *, window, &wm->windows) { + bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook); + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + SpaceLink *sl = static_cast(area->spacedata.first); + if (sl->spacetype == SPACE_NODE) { + SpaceNode *snode = reinterpret_cast(sl); + /* This may be called before runtime data is initialized currently. */ + if (snode->runtime) { + Map &cache = ed::space_node::node_can_sync_cache_get(*snode); + cache.clear(); + } + } + } + } + } +} + +} // namespace blender::nodes