Browse Source

Merge pull request #53313 from KoBeWi/debinded_konnekt

Rémi Verschelde 3 years ago
parent
commit
42312f066b

+ 6 - 0
core/variant/callable_bind.h

@@ -51,6 +51,9 @@ public:
 	virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const;
 	virtual const Callable *get_base_comparator() const;
 
+	Callable get_callable() { return callable; }
+	Vector<Variant> get_binds() { return binds; }
+
 	CallableCustomBind(const Callable &p_callable, const Vector<Variant> &p_binds);
 	virtual ~CallableCustomBind();
 };
@@ -72,6 +75,9 @@ public:
 	virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const;
 	virtual const Callable *get_base_comparator() const;
 
+	Callable get_callable() { return callable; }
+	int get_unbinds() { return argcount; }
+
 	CallableCustomUnbind(const Callable &p_callable, int p_argcount);
 	virtual ~CallableCustomUnbind();
 };

+ 7 - 0
doc/classes/SceneState.xml

@@ -59,6 +59,13 @@
 				Returns the path to the node that owns the method connected to the signal at [code]idx[/code], relative to the root node.
 			</description>
 		</method>
+		<method name="get_connection_unbinds" qualifiers="const">
+			<return type="int" />
+			<argument index="0" name="idx" type="int" />
+			<description>
+				Returns the number of unbound parameters for the signal at [code]idx[/code].
+			</description>
+		</method>
 		<method name="get_node_count" qualifiers="const">
 			<return type="int" />
 			<description>

+ 66 - 32
editor/connections_dialog.cpp

@@ -38,6 +38,7 @@
 #include "plugins/script_editor_plugin.h"
 #include "scene/gui/label.h"
 #include "scene/gui/popup_menu.h"
+#include "scene/gui/spin_box.h"
 
 static Node *_find_first_script(Node *p_root, Node *p_node) {
 	if (p_node != p_root && p_node->get_owner() != p_root) {
@@ -164,6 +165,20 @@ void ConnectDialog::_tree_node_selected() {
 	_update_ok_enabled();
 }
 
+void ConnectDialog::_unbind_count_changed(double p_count) {
+	for (Control *control : bind_controls) {
+		BaseButton *b = Object::cast_to<BaseButton>(control);
+		if (b) {
+			b->set_disabled(p_count > 0);
+		}
+
+		EditorInspector *e = Object::cast_to<EditorInspector>(control);
+		if (e) {
+			e->set_read_only(p_count > 0);
+		}
+	}
+}
+
 /*
  * Adds a new parameter bind to connection.
  */
@@ -305,6 +320,10 @@ void ConnectDialog::set_dst_method(const StringName &p_method) {
 	dst_method->set_text(p_method);
 }
 
+int ConnectDialog::get_unbinds() const {
+	return int(unbind_count->get_value());
+}
+
 Vector<Variant> ConnectDialog::get_binds() const {
 	return cdbinds->params;
 }
@@ -350,6 +369,8 @@ void ConnectDialog::init(ConnectionData c, bool bEdit) {
 
 	deferred->set_pressed(bDeferred);
 	oneshot->set_pressed(bOneshot);
+	unbind_count->set_value(c.unbinds);
+	_unbind_count_changed(c.unbinds);
 
 	cdbinds->params.clear();
 	cdbinds->params = c.binds;
@@ -449,23 +470,33 @@ ConnectDialog::ConnectDialog() {
 	type_list->add_item("Transform3D", Variant::TRANSFORM3D);
 	type_list->add_item("Color", Variant::COLOR);
 	type_list->select(0);
+	bind_controls.push_back(type_list);
 
 	Button *add_bind = memnew(Button);
 	add_bind->set_text(TTR("Add"));
 	add_bind_hb->add_child(add_bind);
 	add_bind->connect("pressed", callable_mp(this, &ConnectDialog::_add_bind));
+	bind_controls.push_back(add_bind);
 
 	Button *del_bind = memnew(Button);
 	del_bind->set_text(TTR("Remove"));
 	add_bind_hb->add_child(del_bind);
 	del_bind->connect("pressed", callable_mp(this, &ConnectDialog::_remove_bind));
+	bind_controls.push_back(del_bind);
 
 	vbc_right->add_margin_child(TTR("Add Extra Call Argument:"), add_bind_hb);
 
 	bind_editor = memnew(EditorInspector);
+	bind_controls.push_back(bind_editor);
 
 	vbc_right->add_margin_child(TTR("Extra Call Arguments:"), bind_editor, true);
 
+	unbind_count = memnew(SpinBox);
+	unbind_count->set_tooltip(TTR("Allows to drop arguments sent by signal emitter."));
+	unbind_count->connect("value_changed", callable_mp(this, &ConnectDialog::_unbind_count_changed));
+
+	vbc_right->add_margin_child(TTR("Unbind Signal Arguments:"), unbind_count);
+
 	HBoxContainer *dstm_hb = memnew(HBoxContainer);
 	vbc_left->add_margin_child(TTR("Receiver Method:"), dstm_hb);
 
@@ -541,26 +572,29 @@ void ConnectionsDock::_make_or_edit_connection() {
 	Node *target = selectedNode->get_node(dst_path);
 	ERR_FAIL_COND(!target);
 
-	ConnectDialog::ConnectionData cToMake;
-	cToMake.source = connect_dialog->get_source();
-	cToMake.target = target;
-	cToMake.signal = connect_dialog->get_signal_name();
-	cToMake.method = connect_dialog->get_dst_method_name();
-	cToMake.binds = connect_dialog->get_binds();
+	ConnectDialog::ConnectionData connection;
+	connection.source = connect_dialog->get_source();
+	connection.target = target;
+	connection.signal = connect_dialog->get_signal_name();
+	connection.method = connect_dialog->get_dst_method_name();
+	connection.unbinds = connect_dialog->get_unbinds();
+	if (connection.unbinds == 0) {
+		connection.binds = connect_dialog->get_binds();
+	}
 	bool defer = connect_dialog->get_deferred();
 	bool oshot = connect_dialog->get_oneshot();
-	cToMake.flags = CONNECT_PERSIST | (defer ? CONNECT_DEFERRED : 0) | (oshot ? CONNECT_ONESHOT : 0);
+	connection.flags = CONNECT_PERSIST | (defer ? CONNECT_DEFERRED : 0) | (oshot ? CONNECT_ONESHOT : 0);
 
 	// Conditions to add function: must have a script and must not have the method already
 	// (in the class, the script itself, or inherited).
 	bool add_script_function = false;
 	Ref<Script> script = target->get_script();
-	if (!target->get_script().is_null() && !ClassDB::has_method(target->get_class(), cToMake.method)) {
+	if (!target->get_script().is_null() && !ClassDB::has_method(target->get_class(), connection.method)) {
 		// There is a chance that the method is inherited from another script.
 		bool found_inherited_function = false;
 		Ref<Script> inherited_script = script->get_base_script();
 		while (!inherited_script.is_null()) {
-			int line = inherited_script->get_language()->find_function(cToMake.method, inherited_script->get_source_code());
+			int line = inherited_script->get_language()->find_function(connection.method, inherited_script->get_source_code());
 			if (line != -1) {
 				found_inherited_function = true;
 				break;
@@ -575,23 +609,23 @@ void ConnectionsDock::_make_or_edit_connection() {
 	if (add_script_function) {
 		// Pick up args here before "it" is deleted by update_tree.
 		script_function_args = it->get_metadata(0).operator Dictionary()["args"];
-		for (int i = 0; i < cToMake.binds.size(); i++) {
-			script_function_args.push_back("extra_arg_" + itos(i) + ":" + Variant::get_type_name(cToMake.binds[i].get_type()));
+		for (int i = 0; i < connection.binds.size(); i++) {
+			script_function_args.push_back("extra_arg_" + itos(i) + ":" + Variant::get_type_name(connection.binds[i].get_type()));
 		}
 	}
 
 	if (connect_dialog->is_editing()) {
 		_disconnect(*it);
-		_connect(cToMake);
+		_connect(connection);
 	} else {
-		_connect(cToMake);
+		_connect(connection);
 	}
 
 	// IMPORTANT NOTE: _disconnect and _connect cause an update_tree, which will delete the object "it" is pointing to.
 	it = nullptr;
 
 	if (add_script_function) {
-		editor->emit_signal(SNAME("script_add_function_request"), target, cToMake.method, script_function_args);
+		editor->emit_signal(SNAME("script_add_function_request"), target, connection.method, script_function_args);
 		hide();
 	}
 
@@ -601,20 +635,18 @@ void ConnectionsDock::_make_or_edit_connection() {
 /*
  * Creates single connection w/ undo-redo functionality.
  */
-void ConnectionsDock::_connect(ConnectDialog::ConnectionData cToMake) {
-	Node *source = static_cast<Node *>(cToMake.source);
-	Node *target = static_cast<Node *>(cToMake.target);
+void ConnectionsDock::_connect(ConnectDialog::ConnectionData p_connection) {
+	Node *source = Object::cast_to<Node>(p_connection.source);
+	Node *target = Object::cast_to<Node>(p_connection.target);
 
 	if (!source || !target) {
 		return;
 	}
 
-	undo_redo->create_action(vformat(TTR("Connect '%s' to '%s'"), String(cToMake.signal), String(cToMake.method)));
-
-	Callable c(target, cToMake.method);
-
-	undo_redo->add_do_method(source, "connect", cToMake.signal, c, cToMake.binds, cToMake.flags);
-	undo_redo->add_undo_method(source, "disconnect", cToMake.signal, c);
+	Callable callable = p_connection.get_callable();
+	undo_redo->create_action(vformat(TTR("Connect '%s' to '%s'"), String(p_connection.signal), String(p_connection.method)));
+	undo_redo->add_do_method(source, "connect", p_connection.signal, callable, varray(), p_connection.flags);
+	undo_redo->add_undo_method(source, "disconnect", p_connection.signal, callable);
 	undo_redo->add_do_method(this, "update_tree");
 	undo_redo->add_undo_method(this, "update_tree");
 	undo_redo->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree"); //to force redraw of scene tree
@@ -634,8 +666,9 @@ void ConnectionsDock::_disconnect(TreeItem &item) {
 
 	undo_redo->create_action(vformat(TTR("Disconnect '%s' from '%s'"), c.signal, c.method));
 
-	undo_redo->add_do_method(selectedNode, "disconnect", c.signal, Callable(c.target, c.method));
-	undo_redo->add_undo_method(selectedNode, "connect", c.signal, Callable(c.target, c.method), c.binds, c.flags);
+	Callable callable = c.get_callable();
+	undo_redo->add_do_method(selectedNode, "disconnect", c.signal, callable);
+	undo_redo->add_undo_method(selectedNode, "connect", c.signal, callable, c.binds, c.flags);
 	undo_redo->add_do_method(this, "update_tree");
 	undo_redo->add_undo_method(this, "update_tree");
 	undo_redo->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree"); // To force redraw of scene tree.
@@ -662,8 +695,8 @@ void ConnectionsDock::_disconnect_all() {
 	while (child) {
 		Connection cd = child->get_metadata(0);
 		ConnectDialog::ConnectionData c = cd;
-		undo_redo->add_do_method(selectedNode, "disconnect", c.signal, Callable(c.target, c.method));
-		undo_redo->add_undo_method(selectedNode, "connect", c.signal, Callable(c.target, c.method), c.binds, c.flags);
+		undo_redo->add_do_method(selectedNode, "disconnect", c.signal, c.get_callable());
+		undo_redo->add_undo_method(selectedNode, "connect", c.signal, c.get_callable(), c.binds, c.flags);
 		child = child->get_next();
 	}
 
@@ -752,8 +785,8 @@ void ConnectionsDock::_open_connection_dialog(TreeItem &item) {
  * Open connection dialog with Connection data to EDIT an existing connection.
  */
 void ConnectionsDock::_open_connection_dialog(ConnectDialog::ConnectionData cToEdit) {
-	Node *src = static_cast<Node *>(cToEdit.source);
-	Node *dst = static_cast<Node *>(cToEdit.target);
+	Node *src = Object::cast_to<Node>(cToEdit.source);
+	Node *dst = Object::cast_to<Node>(cToEdit.target);
 
 	if (src && dst) {
 		const String &signalname = cToEdit.signal;
@@ -1044,7 +1077,9 @@ void ConnectionsDock::update_tree() {
 				if (c.flags & CONNECT_ONESHOT) {
 					path += " (oneshot)";
 				}
-				if (c.binds.size()) {
+				if (c.unbinds > 0) {
+					path += " unbinds(" + itos(c.unbinds) + ")";
+				} else if (!c.binds.is_empty()) {
 					path += " binds(";
 					for (int i = 0; i < c.binds.size(); i++) {
 						if (i > 0) {
@@ -1057,8 +1092,7 @@ void ConnectionsDock::update_tree() {
 
 				TreeItem *connection_item = tree->create_item(signal_item);
 				connection_item->set_text(0, path);
-				Connection cd = c;
-				connection_item->set_metadata(0, cd);
+				connection_item->set_metadata(0, cn);
 				connection_item->set_icon(0, get_theme_icon(SNAME("Slot"), SNAME("EditorIcons")));
 			}
 		}

+ 37 - 12
editor/connections_dialog.h

@@ -48,6 +48,7 @@
 
 class PopupMenu;
 class ConnectDialogBinds;
+class SpinBox;
 
 class ConnectDialog : public ConfirmationDialog {
 	GDCLASS(ConnectDialog, ConfirmationDialog);
@@ -59,25 +60,45 @@ public:
 		StringName signal;
 		StringName method;
 		uint32_t flags = 0;
+		int unbinds = 0;
 		Vector<Variant> binds;
 
-		ConnectionData() {
-		}
+		ConnectionData() {}
+
 		ConnectionData(const Connection &p_connection) {
 			source = Object::cast_to<Node>(p_connection.signal.get_object());
 			signal = p_connection.signal.get_name();
 			target = Object::cast_to<Node>(p_connection.callable.get_object());
-			method = p_connection.callable.get_method();
 			flags = p_connection.flags;
-			binds = p_connection.binds;
+
+			Callable base_callable;
+			if (p_connection.callable.is_custom()) {
+				CallableCustomBind *ccb = dynamic_cast<CallableCustomBind *>(p_connection.callable.get_custom());
+				if (ccb) {
+					binds = ccb->get_binds();
+					base_callable = ccb->get_callable();
+				}
+
+				CallableCustomUnbind *ccu = dynamic_cast<CallableCustomUnbind *>(p_connection.callable.get_custom());
+				if (ccu) {
+					unbinds = ccu->get_unbinds();
+					base_callable = ccu->get_callable();
+				}
+			} else {
+				base_callable = p_connection.callable;
+			}
+			method = base_callable.get_method();
 		}
-		operator Connection() {
-			Connection c;
-			c.signal = ::Signal(source, signal);
-			c.callable = Callable(target, method);
-			c.flags = flags;
-			c.binds = binds;
-			return c;
+
+		Callable get_callable() {
+			if (unbinds > 0) {
+				return Callable(target, method).unbind(unbinds);
+			} else if (!binds.is_empty()) {
+				const Variant *args = binds.ptr();
+				return Callable(target, method).bind(&args, binds.size());
+			} else {
+				return Callable(target, method);
+			}
 		}
 	};
 
@@ -94,11 +115,13 @@ private:
 
 	SceneTreeEditor *tree;
 	AcceptDialog *error;
+	SpinBox *unbind_count;
 	EditorInspector *bind_editor;
 	OptionButton *type_list;
 	CheckBox *deferred;
 	CheckBox *oneshot;
 	CheckButton *advanced;
+	Vector<Control *> bind_controls;
 
 	Label *error_label;
 
@@ -107,6 +130,7 @@ private:
 	void _item_activated();
 	void _text_submitted(const String &_text);
 	void _tree_node_selected();
+	void _unbind_count_changed(double p_count);
 	void _add_bind();
 	void _remove_bind();
 	void _advanced_pressed();
@@ -123,6 +147,7 @@ public:
 	void set_dst_node(Node *p_node);
 	StringName get_dst_method_name() const;
 	void set_dst_method(const StringName &p_method);
+	int get_unbinds() const;
 	Vector<Variant> get_binds() const;
 
 	bool get_deferred() const;
@@ -176,7 +201,7 @@ class ConnectionsDock : public VBoxContainer {
 	void _filter_changed(const String &p_text);
 
 	void _make_or_edit_connection();
-	void _connect(ConnectDialog::ConnectionData cToMake);
+	void _connect(ConnectDialog::ConnectionData p_connection);
 	void _disconnect(TreeItem &item);
 	void _disconnect_all();
 

+ 0 - 1
editor/inspector_dock.h

@@ -32,7 +32,6 @@
 #define INSPECTOR_DOCK_H
 
 #include "editor/animation_track_editor.h"
-#include "editor/connections_dialog.h"
 #include "editor/create_dialog.h"
 #include "editor/editor_data.h"
 #include "editor/editor_inspector.h"

+ 1 - 0
editor/node_dock.cpp

@@ -30,6 +30,7 @@
 
 #include "node_dock.h"
 
+#include "connections_dialog.h"
 #include "editor_node.h"
 #include "editor_scale.h"
 

+ 2 - 1
editor/node_dock.h

@@ -31,9 +31,10 @@
 #ifndef NODE_DOCK_H
 #define NODE_DOCK_H
 
-#include "connections_dialog.h"
 #include "groups_editor.h"
 
+class ConnectionsDock;
+
 class NodeDock : public VBoxContainer {
 	GDCLASS(NodeDock, VBoxContainer);
 

+ 0 - 1
editor/scene_tree_dock.h

@@ -31,7 +31,6 @@
 #ifndef SCENE_TREE_DOCK_H
 #define SCENE_TREE_DOCK_H
 
-#include "editor/connections_dialog.h"
 #include "editor/create_dialog.h"
 #include "editor/editor_data.h"
 #include "editor/groups_editor.h"

+ 51 - 12
scene/resources/packed_scene.cpp

@@ -353,15 +353,23 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
 			continue;
 		}
 
-		Vector<Variant> binds;
-		if (c.binds.size()) {
-			binds.resize(c.binds.size());
-			for (int j = 0; j < c.binds.size(); j++) {
-				binds.write[j] = props[c.binds[j]];
+		Callable callable(cto, snames[c.method]);
+		if (c.unbinds > 0) {
+			callable = callable.unbind(c.unbinds);
+		} else if (!c.binds.is_empty()) {
+			Vector<Variant> binds;
+			if (c.binds.size()) {
+				binds.resize(c.binds.size());
+				for (int j = 0; j < c.binds.size(); j++) {
+					binds.write[j] = props[c.binds[j]];
+				}
 			}
+
+			const Variant *args = binds.ptr();
+			callable = callable.bind(&args, binds.size());
 		}
 
-		cfrom->connect(snames[c.signal], Callable(cto, snames[c.method]), binds, CONNECT_PERSIST | c.flags);
+		cfrom->connect(snames[c.signal], callable, varray(), CONNECT_PERSIST | c.flags);
 	}
 
 	//Node *s = ret_nodes[0];
@@ -652,6 +660,26 @@ Error SceneState::_parse_connections(Node *p_owner, Node *p_node, Map<StringName
 				continue;
 			}
 
+			Vector<Variant> binds;
+			int unbinds = 0;
+			Callable base_callable;
+
+			if (c.callable.is_custom()) {
+				CallableCustomBind *ccb = dynamic_cast<CallableCustomBind *>(c.callable.get_custom());
+				if (ccb) {
+					binds = ccb->get_binds();
+					base_callable = ccb->get_callable();
+				}
+
+				CallableCustomUnbind *ccu = dynamic_cast<CallableCustomUnbind *>(c.callable.get_custom());
+				if (ccu) {
+					unbinds = ccu->get_unbinds();
+					base_callable = ccu->get_callable();
+				}
+			} else {
+				base_callable = c.callable;
+			}
+
 			//find if this connection already exists
 			Node *common_parent = target->find_common_parent_with(p_node);
 
@@ -677,7 +705,7 @@ Error SceneState::_parse_connections(Node *p_owner, Node *p_node, Map<StringName
 					NodePath signal_from = common_parent->get_path_to(p_node);
 					NodePath signal_to = common_parent->get_path_to(target);
 
-					if (ps->has_connection(signal_from, c.signal.get_name(), signal_to, c.callable.get_method())) {
+					if (ps->has_connection(signal_from, c.signal.get_name(), signal_to, base_callable.get_method())) {
 						exists = true;
 						break;
 					}
@@ -708,7 +736,7 @@ Error SceneState::_parse_connections(Node *p_owner, Node *p_node, Map<StringName
 
 							if (from_node >= 0 && to_node >= 0) {
 								//this one has state for this node, save
-								if (state->is_connection(from_node, c.signal.get_name(), to_node, c.callable.get_method())) {
+								if (state->is_connection(from_node, c.signal.get_name(), to_node, base_callable.get_method())) {
 									exists2 = true;
 									break;
 								}
@@ -726,7 +754,7 @@ Error SceneState::_parse_connections(Node *p_owner, Node *p_node, Map<StringName
 
 								if (from_node >= 0 && to_node >= 0) {
 									//this one has state for this node, save
-									if (state->is_connection(from_node, c.signal.get_name(), to_node, c.callable.get_method())) {
+									if (state->is_connection(from_node, c.signal.get_name(), to_node, base_callable.get_method())) {
 										exists2 = true;
 										break;
 									}
@@ -773,12 +801,16 @@ Error SceneState::_parse_connections(Node *p_owner, Node *p_node, Map<StringName
 			ConnectionData cd;
 			cd.from = src_id;
 			cd.to = target_id;
-			cd.method = _nm_get_string(c.callable.get_method(), name_map);
+			cd.method = _nm_get_string(base_callable.get_method(), name_map);
 			cd.signal = _nm_get_string(c.signal.get_name(), name_map);
 			cd.flags = c.flags;
-			for (int i = 0; i < c.binds.size(); i++) {
+			cd.unbinds = unbinds;
+			for (int i = 0; i < c.binds.size(); i++) { // TODO: This could be removed now.
 				cd.binds.push_back(_vm_get_variant(c.binds[i], variant_map));
 			}
+			for (int i = 0; i < binds.size(); i++) {
+				cd.binds.push_back(_vm_get_variant(binds[i], variant_map));
+			}
 			connections.push_back(cd);
 		}
 	}
@@ -1390,6 +1422,11 @@ int SceneState::get_connection_flags(int p_idx) const {
 	return connections[p_idx].flags;
 }
 
+int SceneState::get_connection_unbinds(int p_idx) const {
+	ERR_FAIL_INDEX_V(p_idx, connections.size(), -1);
+	return connections[p_idx].unbinds;
+}
+
 Array SceneState::get_connection_binds(int p_idx) const {
 	ERR_FAIL_INDEX_V(p_idx, connections.size(), Array());
 	Array binds;
@@ -1494,7 +1531,7 @@ void SceneState::set_base_scene(int p_idx) {
 	base_scene_idx = p_idx;
 }
 
-void SceneState::add_connection(int p_from, int p_to, int p_signal, int p_method, int p_flags, const Vector<int> &p_binds) {
+void SceneState::add_connection(int p_from, int p_to, int p_signal, int p_method, int p_flags, int p_unbinds, const Vector<int> &p_binds) {
 	ERR_FAIL_INDEX(p_signal, names.size());
 	ERR_FAIL_INDEX(p_method, names.size());
 
@@ -1507,6 +1544,7 @@ void SceneState::add_connection(int p_from, int p_to, int p_signal, int p_method
 	c.signal = p_signal;
 	c.method = p_method;
 	c.flags = p_flags;
+	c.unbinds = p_unbinds;
 	c.binds = p_binds;
 	connections.push_back(c);
 }
@@ -1549,6 +1587,7 @@ void SceneState::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_connection_method", "idx"), &SceneState::get_connection_method);
 	ClassDB::bind_method(D_METHOD("get_connection_flags", "idx"), &SceneState::get_connection_flags);
 	ClassDB::bind_method(D_METHOD("get_connection_binds", "idx"), &SceneState::get_connection_binds);
+	ClassDB::bind_method(D_METHOD("get_connection_unbinds", "idx"), &SceneState::get_connection_unbinds);
 
 	BIND_ENUM_CONSTANT(GEN_EDIT_STATE_DISABLED);
 	BIND_ENUM_CONSTANT(GEN_EDIT_STATE_INSTANCE);

+ 3 - 1
scene/resources/packed_scene.h

@@ -77,6 +77,7 @@ class SceneState : public RefCounted {
 		int signal = 0;
 		int method = 0;
 		int flags = 0;
+		int unbinds = 0;
 		Vector<int> binds;
 	};
 
@@ -163,6 +164,7 @@ public:
 	NodePath get_connection_target(int p_idx) const;
 	StringName get_connection_method(int p_idx) const;
 	int get_connection_flags(int p_idx) const;
+	int get_connection_unbinds(int p_idx) const;
 	Array get_connection_binds(int p_idx) const;
 
 	bool has_connection(const NodePath &p_node_from, const StringName &p_signal, const NodePath &p_node_to, const StringName &p_method);
@@ -178,7 +180,7 @@ public:
 	void add_node_property(int p_node, int p_name, int p_value);
 	void add_node_group(int p_node, int p_group);
 	void set_base_scene(int p_idx);
-	void add_connection(int p_from, int p_to, int p_signal, int p_method, int p_flags, const Vector<int> &p_binds);
+	void add_connection(int p_from, int p_to, int p_signal, int p_method, int p_flags, int p_unbinds, const Vector<int> &p_binds);
 	void add_editable_instance(const NodePath &p_path);
 
 	virtual void set_last_modified_time(uint64_t p_time) { last_modified_time = p_time; }

+ 11 - 0
scene/resources/resource_format_text.cpp

@@ -313,6 +313,7 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars
 			StringName method = next_tag.fields["method"];
 			StringName signal = next_tag.fields["signal"];
 			int flags = Object::CONNECT_PERSIST;
+			int unbinds = 0;
 			Array binds;
 
 			if (next_tag.fields.has("flags")) {
@@ -323,6 +324,10 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars
 				binds = next_tag.fields["binds"];
 			}
 
+			if (next_tag.fields.has("unbinds")) {
+				unbinds = next_tag.fields["unbinds"];
+			}
+
 			Vector<int> bind_ints;
 			for (int i = 0; i < binds.size(); i++) {
 				bind_ints.push_back(packed_scene->get_state()->add_value(binds[i]));
@@ -334,6 +339,7 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars
 					packed_scene->get_state()->add_name(signal),
 					packed_scene->get_state()->add_name(method),
 					flags,
+					unbinds,
 					bind_ints);
 
 			error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &parser);
@@ -1909,6 +1915,11 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const RES &p_r
 				connstr += " flags=" + itos(flags);
 			}
 
+			int unbinds = state->get_connection_unbinds(i);
+			if (unbinds > 0) {
+				connstr += " unbinds=" + itos(unbinds);
+			}
+
 			Array binds = state->get_connection_binds(i);
 			f->store_string(connstr);
 			if (binds.size()) {