Merge pull request #53313 from KoBeWi/debinded_konnekt

This commit is contained in:
Rémi Verschelde 2022-01-04 12:22:46 +01:00 committed by GitHub
commit 42312f066b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 184 additions and 60 deletions

View file

@ -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();
};

View file

@ -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>

View file

@ -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")));
}
}

View file

@ -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();

View file

@ -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"

View file

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

View file

@ -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);

View file

@ -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"

View file

@ -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);

View file

@ -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; }

View file

@ -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()) {