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