Jelajahi Sumber

Merge pull request #109515 from precup/speedy-selections

Speed up large selections in the editor
Thaddeus Crews 3 minggu lalu
induk
melakukan
709226ad1c

+ 41 - 41
editor/docks/scene_tree_dock.cpp

@@ -563,7 +563,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
 			if (!profile_allow_editing) {
 				break;
 			}
-			if (editor_selection->get_top_selected_node_list().size() > 1) {
+			if (editor_selection->get_selection().size() > 1) {
 				if (!_validate_no_foreign()) {
 					break;
 				}
@@ -1153,7 +1153,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
 				break;
 			}
 
-			List<Node *> selection = editor_selection->get_top_selected_node_list();
+			const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 
 			if (selection.size() != 1) {
 				accept->set_text(vformat(TTR("Saving the branch as a scene requires selecting only one node, but you have selected %d nodes."), selection.size()));
@@ -1214,8 +1214,8 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
 			new_scene_from_dialog->popup_file_dialog();
 		} break;
 		case TOOL_COPY_NODE_PATH: {
-			List<Node *> selection = editor_selection->get_top_selected_node_list();
-			List<Node *>::Element *e = selection.front();
+			const List<Node *> &selection = editor_selection->get_top_selected_node_list();
+			const List<Node *>::Element *e = selection.front();
 			if (e) {
 				Node *node = e->get();
 				if (node) {
@@ -1226,8 +1226,8 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
 			}
 		} break;
 		case TOOL_SHOW_IN_FILE_SYSTEM: {
-			List<Node *> selection = editor_selection->get_top_selected_node_list();
-			List<Node *>::Element *e = selection.front();
+			const List<Node *> &selection = editor_selection->get_top_selected_node_list();
+			const List<Node *>::Element *e = selection.front();
 			if (e) {
 				const Node *node = e->get();
 				if (node) {
@@ -1236,7 +1236,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
 			}
 		} break;
 		case TOOL_OPEN_DOCUMENTATION: {
-			List<Node *> selection = editor_selection->get_top_selected_node_list();
+			const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 			for (const Node *node : selection) {
 				String class_name;
 				Ref<Script> script_base = node->get_script();
@@ -1272,12 +1272,12 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
 				break;
 			}
 
-			List<Node *> selection = editor_selection->get_top_selected_node_list();
+			const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 			if (selection.size() != 1) {
 				break;
 			}
 
-			List<Node *>::Element *e = selection.front();
+			const List<Node *>::Element *e = selection.front();
 			if (e) {
 				Node *node = e->get();
 				if (node) {
@@ -1307,8 +1307,8 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
 				break;
 			}
 
-			List<Node *> selection = editor_selection->get_top_selected_node_list();
-			List<Node *>::Element *e = selection.front();
+			const List<Node *> &selection = editor_selection->get_top_selected_node_list();
+			const List<Node *>::Element *e = selection.front();
 			if (e) {
 				Node *node = e->get();
 				if (node) {
@@ -1342,8 +1342,8 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
 				break;
 			}
 
-			List<Node *> selection = editor_selection->get_top_selected_node_list();
-			List<Node *>::Element *e = selection.front();
+			const List<Node *> &selection = editor_selection->get_top_selected_node_list();
+			const List<Node *>::Element *e = selection.front();
 			if (e) {
 				Node *node = e->get();
 				if (node) {
@@ -1367,8 +1367,8 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
 			}
 		} break;
 		case TOOL_SCENE_OPEN: {
-			List<Node *> selection = editor_selection->get_top_selected_node_list();
-			List<Node *>::Element *e = selection.front();
+			const List<Node *> &selection = editor_selection->get_top_selected_node_list();
+			const List<Node *>::Element *e = selection.front();
 			if (e) {
 				Node *node = e->get();
 				if (node) {
@@ -1388,8 +1388,8 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
 				break;
 			}
 
-			List<Node *> selection = editor_selection->get_top_selected_node_list();
-			List<Node *>::Element *e = selection.front();
+			const List<Node *> &selection = editor_selection->get_top_selected_node_list();
+			const List<Node *>::Element *e = selection.front();
 			if (e) {
 				Node *node = e->get();
 				if (node) {
@@ -1400,8 +1400,8 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
 			}
 		} break;
 		case TOOL_SCENE_OPEN_INHERITED: {
-			List<Node *> selection = editor_selection->get_top_selected_node_list();
-			List<Node *>::Element *e = selection.front();
+			const List<Node *> &selection = editor_selection->get_top_selected_node_list();
+			const List<Node *>::Element *e = selection.front();
 			if (e) {
 				Node *node = e->get();
 				if (node && node->get_scene_inherited_state().is_valid()) {
@@ -2295,7 +2295,7 @@ void SceneTreeDock::_node_prerenamed(Node *p_node, const String &p_new_name) {
 }
 
 bool SceneTreeDock::_validate_no_foreign() {
-	List<Node *> selection = editor_selection->get_top_selected_node_list();
+	const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 
 	for (Node *E : selection) {
 		if (E != edited_scene && E->get_owner() != edited_scene) {
@@ -2324,7 +2324,7 @@ bool SceneTreeDock::_validate_no_foreign() {
 }
 
 bool SceneTreeDock::_validate_no_instance() {
-	List<Node *> selection = editor_selection->get_top_selected_node_list();
+	const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 
 	for (Node *E : selection) {
 		if (E != edited_scene && !E->get_scene_file_path().is_empty()) {
@@ -2341,7 +2341,7 @@ void SceneTreeDock::_node_reparent(NodePath p_path, bool p_keep_global_xform) {
 	Node *new_parent = scene_root->get_node(p_path);
 	ERR_FAIL_NULL(new_parent);
 
-	List<Node *> selection = editor_selection->get_top_selected_node_list();
+	const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 
 	if (selection.is_empty()) {
 		return; // Nothing to reparent.
@@ -2563,7 +2563,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
 }
 
 void SceneTreeDock::_script_created(Ref<Script> p_script) {
-	List<Node *> selected = editor_selection->get_top_selected_node_list();
+	const List<Node *> &selected = editor_selection->get_top_selected_node_list();
 
 	if (selected.is_empty()) {
 		return;
@@ -2624,8 +2624,8 @@ void SceneTreeDock::_shader_creation_closed() {
 }
 
 void SceneTreeDock::_toggle_editable_children_from_selection() {
-	List<Node *> selection = editor_selection->get_top_selected_node_list();
-	List<Node *>::Element *e = selection.front();
+	const List<Node *> &selection = editor_selection->get_top_selected_node_list();
+	const List<Node *>::Element *e = selection.front();
 
 	if (e) {
 		_toggle_editable_children(e->get());
@@ -2633,8 +2633,8 @@ void SceneTreeDock::_toggle_editable_children_from_selection() {
 }
 
 void SceneTreeDock::_toggle_placeholder_from_selection() {
-	List<Node *> selection = editor_selection->get_top_selected_node_list();
-	List<Node *>::Element *e = selection.front();
+	const List<Node *> &selection = editor_selection->get_top_selected_node_list();
+	const List<Node *>::Element *e = selection.front();
 
 	if (e) {
 		Node *node = e->get();
@@ -3023,7 +3023,7 @@ void SceneTreeDock::_create() {
 		_do_create(parent);
 
 	} else if (current_option == TOOL_REPLACE) {
-		List<Node *> selection = editor_selection->get_top_selected_node_list();
+		const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 		ERR_FAIL_COND(selection.is_empty());
 
 		EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
@@ -3042,7 +3042,7 @@ void SceneTreeDock::_create() {
 
 		ur->commit_action(false);
 	} else if (current_option == TOOL_REPARENT_TO_NEW_NODE) {
-		List<Node *> selection = editor_selection->get_top_selected_node_list();
+		const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 		ERR_FAIL_COND(selection.is_empty());
 
 		// Find top level node in selection
@@ -3056,7 +3056,7 @@ void SceneTreeDock::_create() {
 		bool center_parent = EDITOR_GET("docks/scene_tree/center_node_on_reparent");
 		Vector<Node *> top_level_nodes;
 
-		for (List<Node *>::Element *E = selection.front()->next(); E; E = E->next()) {
+		for (const List<Node *>::Element *E = selection.front()->next(); E; E = E->next()) {
 			Node *n = E->get();
 			ERR_FAIL_NULL(n);
 
@@ -3363,12 +3363,12 @@ void SceneTreeDock::set_edited_scene(Node *p_scene) {
 	edited_scene = p_scene;
 }
 
-static bool _is_same_selection(const Vector<Node *> &p_first, const List<Node *> &p_second) {
+static bool _is_same_selection(const Vector<Node *> &p_first, const HashMap<Node *, Object *> &p_second) {
 	if (p_first.size() != p_second.size()) {
 		return false;
 	}
-	for (Node *node : p_second) {
-		if (!p_first.has(node)) {
+	for (Node *node : p_first) {
+		if (!p_second.has(node)) {
 			return false;
 		}
 	}
@@ -3387,7 +3387,7 @@ void SceneTreeDock::clear_previous_node_selection() {
 
 void SceneTreeDock::set_selection(const Vector<Node *> &p_nodes) {
 	// If the nodes selected are the same independently of order then return early.
-	if (_is_same_selection(p_nodes, editor_selection->get_full_selected_node_list())) {
+	if (_is_same_selection(p_nodes, editor_selection->get_selection())) {
 		return;
 	}
 	editor_selection->clear();
@@ -3401,7 +3401,7 @@ void SceneTreeDock::set_selected(Node *p_node, bool p_emit_selected) {
 }
 
 void SceneTreeDock::_new_scene_from(const String &p_file) {
-	List<Node *> selection = editor_selection->get_top_selected_node_list();
+	const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 
 	if (selection.size() != 1) {
 		accept->set_text(TTR("This operation requires a single selected node."));
@@ -3579,7 +3579,7 @@ void SceneTreeDock::_normalize_drop(Node *&to_node, int &to_pos, int p_type) {
 }
 
 Array SceneTreeDock::_get_selection_array() {
-	List<Node *> selection = editor_selection->get_top_selected_node_list();
+	const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 	TypedArray<Node> array;
 	array.resize(selection.size());
 
@@ -3728,7 +3728,7 @@ void SceneTreeDock::_nodes_dragged(const Array &p_nodes, NodePath p_to, int p_ty
 		return;
 	}
 
-	List<Node *> selection = editor_selection->get_top_selected_node_list();
+	const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 
 	if (selection.is_empty()) {
 		return; //nothing to reparent
@@ -3791,7 +3791,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
 	ERR_FAIL_COND(!EditorNode::get_singleton()->get_edited_scene());
 	menu->clear(false);
 
-	List<Node *> selection = editor_selection->get_top_selected_node_list();
+	const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 	List<Node *> full_selection = editor_selection->get_full_selected_node_list(); // Above method only returns nodes with common parent.
 
 	if (selection.is_empty()) {
@@ -4007,7 +4007,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
 
 	Vector<String> p_paths;
 	Node *root = EditorNode::get_singleton()->get_edited_scene();
-	for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
+	for (const List<Node *>::Element *E = selection.front(); E; E = E->next()) {
 		String node_path = String(root->get_path().rel_path_to(E->get()->get_path()));
 		p_paths.push_back(node_path);
 	}
@@ -4144,7 +4144,7 @@ void SceneTreeDock::attach_script_to_selected(bool p_extend) {
 		return;
 	}
 
-	List<Node *> selection = editor_selection->get_top_selected_node_list();
+	const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 	if (selection.is_empty()) {
 		return;
 	}
@@ -4274,7 +4274,7 @@ List<Node *> SceneTreeDock::paste_nodes(bool p_paste_as_sibling) {
 	Node *paste_parent = edited_scene;
 	Node *paste_sibling = nullptr;
 
-	List<Node *> selection = editor_selection->get_top_selected_node_list();
+	const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 	if (selection.size() > 0) {
 		paste_parent = selection.back()->get();
 	}

+ 1 - 1
editor/editor_data.cpp

@@ -1296,7 +1296,7 @@ void EditorSelection::_update_node_list() {
 		top_selected_node_list.push_back(E.key);
 	}
 
-	node_list_changed = true;
+	node_list_changed = false;
 }
 
 void EditorSelection::update() {

+ 6 - 1
editor/editor_node.cpp

@@ -2880,8 +2880,13 @@ void EditorNode::_edit_current(bool p_skip_foreign, bool p_skip_inspector_update
 					}
 					if (!multi_nodes.is_empty()) {
 						// Pick the top-most node.
-						multi_nodes.sort_custom<Node::Comparator>();
 						selected_node = multi_nodes[0];
+						Node::Comparator comparator;
+						for (Node *node : multi_nodes) {
+							if (comparator(node, selected_node)) {
+								selected_node = node;
+							}
+						}
 					}
 				}
 			}

+ 7 - 5
editor/inspector/multi_node_edit.cpp

@@ -148,18 +148,20 @@ void MultiNodeEdit::_get_property_list(List<PropertyInfo> *p_list) const {
 				F.name = F.name.replace_first("metadata/", "Metadata/"); // Trick to not get actual metadata edited from MultiNodeEdit.
 			}
 
-			if (!usage.has(F.name)) {
+			PLData *usage_data = usage.getptr(F.name);
+			if (!usage_data) {
 				PLData pld;
 				pld.uses = 0;
 				pld.info = F;
 				pld.info.name = F.name;
-				usage[F.name] = pld;
-				data_list.push_back(usage.getptr(F.name));
+				HashMap<String, MultiNodeEdit::PLData>::Iterator I = usage.insert(F.name, pld);
+				usage_data = &I->value;
+				data_list.push_back(usage_data);
 			}
 
 			// Make sure only properties with the same exact PropertyInfo data will appear.
-			if (usage[F.name].info == F) {
-				usage[F.name].uses++;
+			if (usage_data->info == F) {
+				usage_data->uses++;
 			}
 		}
 

+ 6 - 2
editor/inspector/multi_node_edit.h

@@ -74,8 +74,12 @@ public:
 		if (get_node_count() != p_other->get_node_count()) {
 			return false;
 		}
-		for (int i = 0; i < get_node_count(); i++) {
-			if (!nodes.has(p_other->get_node(i))) {
+		HashSet<NodePath> nodes_in_selection;
+		for (const NodePath &node : p_other->nodes) {
+			nodes_in_selection.insert(node);
+		}
+		for (const NodePath &node : nodes) {
+			if (!nodes_in_selection.has(node)) {
 				return false;
 			}
 		}

+ 19 - 17
editor/scene/2d/particles_2d_editor_plugin.cpp

@@ -230,36 +230,38 @@ void Particles2DEditorPlugin::_set_show_gizmos(Node *p_node, bool p_show) {
 }
 
 void Particles2DEditorPlugin::_selection_changed() {
-	List<Node *> current_selection = EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list();
+	const List<Node *> &current_selection = EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list();
 	if (selected_particles.is_empty() && current_selection.is_empty()) {
 		return;
 	}
 
+	// Turn gizmos on for nodes that are newly selected.
+	HashSet<const Node *> nodes_in_current_selection;
+	for (Node *node : current_selection) {
+		nodes_in_current_selection.insert(node);
+		if (!selected_particles.has(node)) {
+			_set_show_gizmos(node, true);
+			selected_particles.insert(node);
+		}
+	}
+
 	// Turn gizmos off for nodes that are no longer selected.
-	for (List<Node *>::Element *E = selected_particles.front(); E;) {
-		Node *node = E->get();
-		List<Node *>::Element *N = E->next();
-		if (current_selection.find(node) == nullptr) {
+	LocalVector<Node *> to_erase;
+	for (Node *node : selected_particles) {
+		if (!nodes_in_current_selection.has(node)) {
 			_set_show_gizmos(node, false);
-			selected_particles.erase(E);
+			to_erase.push_back(node);
 		}
-		E = N;
 	}
 
-	// Turn gizmos on for nodes that are newly selected.
-	for (Node *node : current_selection) {
-		if (selected_particles.find(node) == nullptr) {
-			_set_show_gizmos(node, true);
-			selected_particles.push_back(node);
-		}
+	for (Node *node : to_erase) {
+		selected_particles.erase(node);
 	}
 }
 
 void Particles2DEditorPlugin::_node_removed(Node *p_node) {
-	List<Node *>::Element *E = selected_particles.find(p_node);
-	if (E) {
-		_set_show_gizmos(E->get(), false);
-		selected_particles.erase(E);
+	if (selected_particles.erase(p_node)) {
+		_set_show_gizmos(p_node, false);
 	}
 }
 

+ 1 - 1
editor/scene/2d/particles_2d_editor_plugin.h

@@ -42,7 +42,7 @@ protected:
 		MENU_LOAD_EMISSION_MASK = 100,
 	};
 
-	List<Node *> selected_particles;
+	HashSet<Node *> selected_particles;
 
 	enum EmissionMode {
 		EMISSION_MODE_SOLID,

+ 1 - 1
editor/scene/3d/node_3d_editor_plugin.cpp

@@ -5225,7 +5225,7 @@ void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_
 		selected_files = d["files"];
 	}
 
-	List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list();
+	const List<Node *> &selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list();
 	Node *root_node = EditorNode::get_singleton()->get_edited_scene();
 	if (selected_nodes.size() > 0) {
 		Node *selected_node = selected_nodes.front()->get();

+ 12 - 8
editor/scene/canvas_item_editor_plugin.cpp

@@ -831,8 +831,12 @@ List<CanvasItem *> CanvasItemEditor::_get_edited_canvas_items(bool p_retrieve_lo
 
 	if (p_remove_canvas_item_if_parent_in_selection) {
 		List<CanvasItem *> filtered_selection;
+		HashSet<const Node *> nodes_in_selection;
 		for (CanvasItem *E : selection) {
-			if (!selection.find(E->get_parent())) {
+			nodes_in_selection.insert(E);
+		}
+		for (CanvasItem *E : selection) {
+			if (!nodes_in_selection.has(E->get_parent())) {
 				filtered_selection.push_back(E);
 			}
 		}
@@ -2616,7 +2620,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
 				}
 
 				_find_canvas_items_in_rect(Rect2(bsfrom, bsto - bsfrom), scene, &selitems);
-				if (selitems.size() == 1 && editor_selection->get_top_selected_node_list().is_empty()) {
+				if (selitems.size() == 1 && editor_selection->get_selection().is_empty()) {
 					EditorNode::get_singleton()->push_item(selitems.front()->get());
 				}
 				for (CanvasItem *E : selitems) {
@@ -2834,7 +2838,7 @@ void CanvasItemEditor::_update_lock_and_group_button() {
 	bool all_locked = true;
 	bool all_group = true;
 	bool has_canvas_item = false;
-	List<Node *> selection = editor_selection->get_top_selected_node_list();
+	const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 	if (selection.is_empty()) {
 		all_locked = false;
 		all_group = false;
@@ -4673,7 +4677,7 @@ void CanvasItemEditor::_popup_callback(int p_op) {
 		case LOCK_SELECTED: {
 			undo_redo->create_action(TTR("Lock Selected"));
 
-			List<Node *> selection = editor_selection->get_top_selected_node_list();
+			const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 			for (Node *E : selection) {
 				CanvasItem *ci = Object::cast_to<CanvasItem>(E);
 				if (!ci || !ci->is_inside_tree()) {
@@ -4692,7 +4696,7 @@ void CanvasItemEditor::_popup_callback(int p_op) {
 		case UNLOCK_SELECTED: {
 			undo_redo->create_action(TTR("Unlock Selected"));
 
-			List<Node *> selection = editor_selection->get_top_selected_node_list();
+			const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 			for (Node *E : selection) {
 				CanvasItem *ci = Object::cast_to<CanvasItem>(E);
 				if (!ci || !ci->is_inside_tree()) {
@@ -4711,7 +4715,7 @@ void CanvasItemEditor::_popup_callback(int p_op) {
 		case GROUP_SELECTED: {
 			undo_redo->create_action(TTR("Group Selected"));
 
-			List<Node *> selection = editor_selection->get_top_selected_node_list();
+			const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 			for (Node *E : selection) {
 				CanvasItem *ci = Object::cast_to<CanvasItem>(E);
 				if (!ci || !ci->is_inside_tree()) {
@@ -4730,7 +4734,7 @@ void CanvasItemEditor::_popup_callback(int p_op) {
 		case UNGROUP_SELECTED: {
 			undo_redo->create_action(TTR("Ungroup Selected"));
 
-			List<Node *> selection = editor_selection->get_top_selected_node_list();
+			const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 			for (Node *E : selection) {
 				CanvasItem *ci = Object::cast_to<CanvasItem>(E);
 				if (!ci || !ci->is_inside_tree()) {
@@ -6354,7 +6358,7 @@ void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p
 		return;
 	}
 
-	List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list();
+	const List<Node *> &selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list();
 	Node *root_node = EditorNode::get_singleton()->get_edited_scene();
 	if (selected_nodes.size() > 0) {
 		Node *selected_node = selected_nodes.front()->get();

+ 6 - 6
editor/scene/gui/control_editor_plugin.cpp

@@ -770,7 +770,7 @@ SizeFlagPresetPicker::SizeFlagPresetPicker(bool p_vertical) {
 
 void ControlEditorToolbar::_anchors_preset_selected(int p_preset) {
 	LayoutPreset preset = (LayoutPreset)p_preset;
-	List<Node *> selection = editor_selection->get_top_selected_node_list();
+	const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 
 	EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 	undo_redo->create_action(TTR("Change Anchors, Offsets, Grow Direction"));
@@ -791,7 +791,7 @@ void ControlEditorToolbar::_anchors_preset_selected(int p_preset) {
 }
 
 void ControlEditorToolbar::_anchors_to_current_ratio() {
-	List<Node *> selection = editor_selection->get_top_selected_node_list();
+	const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 
 	EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 	undo_redo->create_action(TTR("Change Anchors, Offsets (Keep Ratio)"));
@@ -842,7 +842,7 @@ void ControlEditorToolbar::_anchor_mode_toggled(bool p_status) {
 }
 
 void ControlEditorToolbar::_container_flags_selected(int p_flags, bool p_vertical) {
-	List<Node *> selection = editor_selection->get_top_selected_node_list();
+	const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 
 	EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 	if (p_vertical) {
@@ -869,7 +869,7 @@ void ControlEditorToolbar::_container_flags_selected(int p_flags, bool p_vertica
 }
 
 void ControlEditorToolbar::_expand_flag_toggled(bool p_expand, bool p_vertical) {
-	List<Node *> selection = editor_selection->get_top_selected_node_list();
+	const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 
 	EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 	if (p_vertical) {
@@ -1003,7 +1003,7 @@ void ControlEditorToolbar::_selection_changed() {
 		int nb_valid_controls = 0;
 		int nb_anchors_mode = 0;
 
-		List<Node *> selection = editor_selection->get_top_selected_node_list();
+		const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 		for (Node *E : selection) {
 			Control *control = Object::cast_to<Control>(E);
 			if (!control) {
@@ -1053,7 +1053,7 @@ void ControlEditorToolbar::_selection_changed() {
 		int nb_h_expand = 0;
 		int nb_v_expand = 0;
 
-		List<Node *> selection = editor_selection->get_top_selected_node_list();
+		const List<Node *> &selection = editor_selection->get_top_selected_node_list();
 		for (Node *E : selection) {
 			Control *control = Object::cast_to<Control>(E);
 			if (!control) {

+ 8 - 2
editor/scene/scene_tree_editor.cpp

@@ -1317,11 +1317,17 @@ void SceneTreeEditor::_cell_multi_selected(Object *p_object, int p_cell, bool p_
 	}
 
 	// Emitted "selected" in _selected_changed() when select single node, so select multiple node emit "changed".
-	if (editor_selection->get_selected_nodes().size() > 1) {
-		emit_signal(SNAME("node_changed"));
+	if (editor_selection->get_selection().size() > 1 && !pending_selection_update) {
+		pending_selection_update = true;
+		callable_mp(this, &SceneTreeEditor::_process_selection_update).call_deferred();
 	}
 }
 
+void SceneTreeEditor::_process_selection_update() {
+	pending_selection_update = false;
+	emit_signal(SNAME("node_changed"));
+}
+
 void SceneTreeEditor::_tree_scroll_to_item(ObjectID p_item_id) {
 	ERR_FAIL_NULL(tree);
 	TreeItem *item = ObjectDB::get_instance<TreeItem>(p_item_id);

+ 2 - 0
editor/scene/scene_tree_editor.h

@@ -184,6 +184,7 @@ class SceneTreeEditor : public Control {
 	bool display_foreign = false;
 	bool tree_dirty = true;
 	bool pending_test_update = false;
+	bool pending_selection_update = false;
 	Timer *update_node_tooltip_delay = nullptr;
 
 	static void _bind_methods();
@@ -191,6 +192,7 @@ class SceneTreeEditor : public Control {
 	void _cell_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button);
 	void _toggle_visible(Node *p_node);
 	void _cell_multi_selected(Object *p_object, int p_cell, bool p_selected);
+	void _process_selection_update();
 	void _update_selection(TreeItem *item);
 	void _node_script_changed(Node *p_node);
 	void _node_visibility_changed(Node *p_node);