Merge pull request #106837 from reduz/unique-node-ids2

Add unique Node IDs to support base and instantiated scene refactorings
This commit is contained in:
Rémi Verschelde
2025-10-06 14:08:24 +02:00
6 changed files with 370 additions and 28 deletions

View File

@ -351,6 +351,7 @@ void ResourceUID::clear() {
unique_ids.clear();
changed = false;
}
void ResourceUID::_bind_methods() {
ClassDB::bind_method(D_METHOD("id_to_text", "id"), &ResourceUID::id_to_text);
ClassDB::bind_method(D_METHOD("text_to_id", "text_id"), &ResourceUID::text_to_id);

View File

@ -2082,6 +2082,14 @@ Node *Node::find_parent(const String &p_pattern) const {
return nullptr;
}
void Node::set_unique_scene_id(int32_t p_unique_id) {
data.unique_scene_id = p_unique_id;
}
int32_t Node::get_unique_scene_id() const {
return data.unique_scene_id;
}
Window *Node::get_window() const {
ERR_THREAD_GUARD_V(nullptr);
Viewport *vp = get_viewport();

View File

@ -132,6 +132,9 @@ public:
#ifdef DEBUG_ENABLED
static SafeNumeric<uint64_t> total_node_count;
#endif
enum {
UNIQUE_SCENE_ID_UNASSIGNED = 0
};
void _update_process(bool p_enable, bool p_for_children);
@ -281,6 +284,8 @@ private:
mutable bool is_translation_domain_inherited : 1;
mutable bool is_translation_domain_dirty : 1;
int32_t unique_scene_id = UNIQUE_SCENE_ID_UNASSIGNED;
mutable NodePath *path_cache = nullptr;
} data;
@ -527,6 +532,9 @@ public:
Node *get_parent() const;
Node *find_parent(const String &p_pattern) const;
void set_unique_scene_id(int32_t p_unique_id);
int32_t get_unique_scene_id() const;
Window *get_window() const;
Window *get_non_popup_window() const;
Window *get_last_exclusive_window() const;

View File

@ -124,6 +124,23 @@ Ref<Resource> SceneState::get_remap_resource(const Ref<Resource> &p_resource, Ha
return remap_resource;
}
static Node *_find_node_by_id(Node *p_owner, Node *p_node, int32_t p_id) {
if (p_owner == p_node || p_node->get_owner() == p_owner) {
if (p_node->get_unique_scene_id() == p_id) {
return p_node;
}
}
for (int i = 0; i < p_node->get_child_count(); i++) {
Node *found = _find_node_by_id(p_owner, p_node->get_child(i), p_id);
if (found) {
return found;
}
}
return nullptr;
}
Node *SceneState::instantiate(GenEditState p_edit_state) const {
// Nodes where instantiation failed (because something is missing.)
List<Node *> stray_instances;
@ -133,6 +150,9 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
if (p_id & FLAG_ID_IS_PATH) { \
NodePath np = node_paths[p_id & FLAG_MASK]; \
p_name = ret_nodes[0]->get_node_or_null(np); \
if (!p_name) { \
p_name = _recover_node_path_index(ret_nodes[0], p_id & FLAG_MASK); \
} \
} else { \
ERR_FAIL_INDEX_V(p_id & FLAG_MASK, nc, nullptr); \
p_name = ret_nodes[p_id & FLAG_MASK]; \
@ -165,6 +185,8 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
LocalVector<DeferredNodePathProperties> deferred_node_paths;
bool deep_search_warned = false;
for (int i = 0; i < nc; i++) {
const NodeData &n = nd[i];
@ -249,6 +271,30 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
// Get the node from somewhere, it likely already exists from another instance.
if (parent) {
node = parent->_get_child_by_name(snames[n.name]);
if (i < ids.size()) {
if (!node) {
// Can't get by name, try to fetch by ID. This is slow, but should be fixed after re-save.
int32_t id = ids[i];
if (id != Node::UNIQUE_SCENE_ID_UNASSIGNED) {
if (!deep_search_warned) {
WARN_PRINT(vformat("%sA node in the scene this one inherits from has been removed or moved, so a recovery process needs to take place. Please re-save this scene to avoid the cost of this process next time.", !get_path().is_empty() ? get_path() + ": " : ""));
deep_search_warned = true;
}
Node *base = parent;
while (base != ret_nodes[0] && !base->is_instance()) {
base = base->get_parent();
}
node = _find_node_by_id(base, base, id);
}
} else {
if (ids[i] != node->get_unique_scene_id()) {
// This may be a scene that did not originally have ids and
// was saved before the parent, so force the id to match the
// parent scene node id.
ids.write[i] = node->get_unique_scene_id();
}
}
}
#ifdef DEBUG_ENABLED
if (!node) {
WARN_PRINT(String("Node '" + String(ret_nodes[0]->get_path_to(parent)) + "/" + String(snames[n.name]) + "' was modified from inside an instance, but it has vanished.").ascii().get_data());
@ -297,6 +343,9 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
}
if (node) {
if (i < ids.size()) {
node->set_unique_scene_id(ids[i]);
}
// may not have found the node (part of instantiated scene and removed)
// if found all is good, otherwise ignore
@ -734,7 +783,7 @@ static int _vm_get_variant(const Variant &p_variant, HashMap<Variant, int, Varia
return idx;
}
Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, HashMap<StringName, int> &name_map, HashMap<Variant, int, VariantHasher, VariantComparator> &variant_map, HashMap<Node *, int> &node_map, HashMap<Node *, int> &nodepath_map) {
Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, HashMap<StringName, int> &name_map, HashMap<Variant, int, VariantHasher, VariantComparator> &variant_map, HashMap<Node *, int> &node_map, HashMap<Node *, int> &nodepath_map, HashSet<int32_t> &ids_saved) {
// this function handles all the work related to properly packing scenes, be it
// instantiated or inherited.
// given the complexity of this process, an attempt will be made to properly
@ -1014,14 +1063,14 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
// below condition is true for all nodes of the scene being saved, and ones in subscenes
// that hold changes
bool save_node = nd.properties.size() || nd.groups.size(); // some local properties or groups exist
save_node = save_node || p_node == p_owner; // owner is always saved
bool save_node = p_node == p_owner; // owner is always saved
save_node = save_node || (p_node->get_owner() == p_owner && instantiated_by_owner); //part of scene and not instanced
bool save_data = nd.properties.size() || nd.groups.size(); // some local properties or groups exist
int idx = nodes.size();
int parent_node = NO_PARENT_SAVED;
if (save_node) {
if (save_node || save_data) {
//don't save the node if nothing and subscene
node_map[p_node] = idx;
@ -1041,13 +1090,39 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
nd.parent = p_parent_idx;
}
int32_t unique_scene_id = p_node->get_unique_scene_id();
if (save_node && (unique_scene_id == Node::UNIQUE_SCENE_ID_UNASSIGNED || ids_saved.has(unique_scene_id))) {
// Unassigned or clash somehow.
// Clashes will always happen with instantiated scenes, so it is normal
// to expect them to be resolved.
while (true) {
uint32_t data = ResourceUID::get_singleton()->create_id();
unique_scene_id = data & 0x7FFFFFFF; // keep positive.
if (unique_scene_id == Node::UNIQUE_SCENE_ID_UNASSIGNED) {
unique_scene_id = 1;
}
if (ids_saved.has(unique_scene_id)) {
// While there is one in a four billion chance for a clash, the scenario where one scene is instantiated multiple times is common, so it must reassign the local id.
continue;
}
break;
}
p_node->set_unique_scene_id(unique_scene_id);
}
ids_saved.insert(unique_scene_id);
ids.push_back(unique_scene_id);
parent_node = idx;
nodes.push_back(nd);
}
for (int i = 0; i < p_node->get_child_count(); i++) {
Node *c = p_node->get_child(i);
Error err = _parse_node(p_owner, c, parent_node, name_map, variant_map, node_map, nodepath_map);
Error err = _parse_node(p_owner, c, parent_node, name_map, variant_map, node_map, nodepath_map, ids_saved);
if (err) {
return err;
}
@ -1273,6 +1348,7 @@ Error SceneState::pack(Node *p_scene) {
HashMap<Variant, int, VariantHasher, VariantComparator> variant_map;
HashMap<Node *, int> node_map;
HashMap<Node *, int> nodepath_map;
HashSet<int32_t> ids_saved;
// If using scene inheritance, pack the scene it inherits from.
if (scene->get_scene_inherited_state().is_valid()) {
@ -1284,7 +1360,7 @@ Error SceneState::pack(Node *p_scene) {
}
// Instanced, only direct sub-scenes are supported of course.
Error err = _parse_node(scene, scene, -1, name_map, variant_map, node_map, nodepath_map);
Error err = _parse_node(scene, scene, -1, name_map, variant_map, node_map, nodepath_map, ids_saved);
if (err) {
clear();
ERR_FAIL_V(err);
@ -1310,8 +1386,31 @@ Error SceneState::pack(Node *p_scene) {
}
node_paths.resize(nodepath_map.size());
id_paths.resize(nodepath_map.size());
for (const KeyValue<Node *, int> &E : nodepath_map) {
node_paths.write[E.value] = scene->get_path_to(E.key);
// Build a path of IDs to reach the node.
PackedInt32Array id_path;
bool id_path_valid = false;
Node *base = E.key;
while (base && base->get_unique_scene_id() != Node::UNIQUE_SCENE_ID_UNASSIGNED) {
id_path.push_back(base->get_unique_scene_id());
base = base->get_owner();
if (base == p_scene) {
id_path_valid = true;
break;
}
}
if (!id_path_valid) {
id_path.clear();
}
// Reverse it since we went from node to owner, and we seek from owner to node.
id_path.reverse();
id_paths.write[E.value] = id_path;
}
if (Engine::get_singleton()->is_editor_hint()) {
@ -1340,6 +1439,8 @@ void SceneState::clear() {
node_path_cache.clear();
node_paths.clear();
editable_instances.clear();
ids.clear();
id_paths.clear();
base_scene_idx = -1;
}
@ -1366,6 +1467,9 @@ Error SceneState::copy_from(const Ref<SceneState> &p_scene_state) {
for (const NodePath &E : p_scene_state->node_paths) {
node_paths.append(E);
}
for (const PackedInt32Array &E : p_scene_state->id_paths) {
id_paths.append(E);
}
for (const NodePath &E : p_scene_state->editable_instances) {
editable_instances.append(E);
}
@ -1389,6 +1493,7 @@ int SceneState::find_node_by_path(const NodePath &p_node) const {
ERR_FAIL_COND_V_MSG(node_path_cache.is_empty(), -1, "This operation requires the node cache to have been built.");
if (!node_path_cache.has(p_node)) {
// If not in this scene state, find node path by scene inheritance.
if (get_base_scene_state().is_valid()) {
int idx = get_base_scene_state()->find_node_by_path(p_node);
if (idx != -1) {
@ -1610,15 +1715,30 @@ void SceneState::set_bundled_scene(const Dictionary &p_dictionary) {
}
}
if (p_dictionary.has("node_ids")) {
ids = p_dictionary["node_ids"];
}
Array np;
if (p_dictionary.has("node_paths")) {
np = p_dictionary["node_paths"];
}
node_paths.resize(np.size());
for (int i = 0; i < np.size(); i++) {
node_paths.write[i] = np[i];
}
Array idp;
if (p_dictionary.has("id_paths") && ids.size()) {
idp = p_dictionary["id_paths"];
}
id_paths.resize(idp.size());
for (int i = 0; i < idp.size(); i++) {
id_paths.write[i] = idp[i];
}
Array ei;
if (p_dictionary.has("editable_instances")) {
ei = p_dictionary["editable_instances"];
@ -1678,6 +1798,7 @@ Dictionary SceneState::get_bundled_scene() const {
}
d["nodes"] = rnodes;
d["node_ids"] = ids;
Vector<int> rconns;
d["conn_count"] = connections.size();
@ -1705,6 +1826,13 @@ Dictionary SceneState::get_bundled_scene() const {
}
d["node_paths"] = rnode_paths;
Array rid_paths;
rid_paths.resize(id_paths.size());
for (int i = 0; i < id_paths.size(); i++) {
rid_paths[i] = id_paths[i];
}
d["id_paths"] = rid_paths;
Array reditable_instances;
reditable_instances.resize(editable_instances.size());
for (int i = 0; i < editable_instances.size(); i++) {
@ -1785,6 +1913,74 @@ Vector<StringName> SceneState::get_node_groups(int p_idx) const {
return groups;
}
Node *SceneState::_recover_node_path_index(Node *p_base, int p_idx) const {
// ID paths are only used for recovery, since they are slower to traverse.
// This function attempts to recover a node by using IDs in case the path
// has disappeared.
if (p_idx >= id_paths.size()) {
return nullptr;
}
const PackedInt32Array &id_path = id_paths[p_idx & FLAG_MASK];
Vector<StringName> full_path;
const SceneState *ss = this;
for (int i = 0; i < id_path.size(); i++) {
int idx = ss->ids.find(id_path[i]);
if (idx == -1) {
// Not found, but may belong to a base scene, so search.
while (ss && idx == -1 && ss->base_scene_idx >= 0) {
Ref<PackedScene> sdata = ss->variants[ss->base_scene_idx];
if (sdata.is_null()) {
return nullptr;
}
Ref<SceneState> ssd = sdata->get_state();
if (!ssd.is_valid()) {
return nullptr;
}
ss = ssd.ptr();
idx = ss->ids.find(id_path[i]);
if (idx != -1) {
break;
}
}
if (idx == -1) {
//No luck.
return nullptr;
}
}
ERR_FAIL_COND_V(idx >= ss->nodes.size(), nullptr); // Should be a node.
NodePath so_far = ss->get_node_path(idx);
for (int j = 0; j < so_far.get_name_count(); j++) {
full_path.push_back(so_far.get_name(j));
}
if (i == id_path.size() - 1) {
break; // Do not go further, we have the path.
}
const NodeData &nd = ss->nodes[idx];
// Get instance
ERR_FAIL_COND_V(nd.instance < 0, nullptr); // Not an instance, middle of path should be an instance.
ERR_FAIL_COND_V(nd.instance & FLAG_INSTANCE_IS_PLACEHOLDER, nullptr); // Instance is somehow a placeholder?!
Ref<PackedScene> sdata = ss->variants[nd.instance & FLAG_MASK];
ERR_FAIL_COND_V(sdata.is_null(), nullptr);
Ref<SceneState> sstate = sdata->get_state();
ss = sstate.ptr();
}
NodePath recovered_path(full_path, false);
return p_base->get_node_or_null(recovered_path);
}
int32_t SceneState::get_node_unique_id(int p_idx) const {
if (p_idx >= ids.size()) {
return Node::UNIQUE_SCENE_ID_UNASSIGNED;
}
return ids[p_idx];
}
NodePath SceneState::get_node_path(int p_idx, bool p_for_parent) const {
ERR_FAIL_INDEX_V(p_idx, nodes.size(), NodePath());
@ -1828,6 +2024,52 @@ NodePath SceneState::get_node_path(int p_idx, bool p_for_parent) const {
return NodePath(sub_path, false);
}
PackedInt32Array SceneState::get_node_id_path(int p_idx) const {
PackedInt32Array pp = get_node_parent_id_path(p_idx);
if (pp.is_empty()) {
return pp;
}
if (p_idx < ids.size()) {
pp.push_back(ids[p_idx]);
return pp;
}
return PackedInt32Array();
}
PackedInt32Array SceneState::get_node_parent_id_path(int p_idx) const {
if (nodes[p_idx].parent < 0 || nodes[p_idx].parent == NO_PARENT_SAVED) {
return PackedInt32Array();
}
if (nodes[p_idx].parent & FLAG_ID_IS_PATH) {
int id = nodes[p_idx].parent & FLAG_MASK;
if (id >= id_paths.size()) {
return PackedInt32Array();
}
return id_paths[id];
}
return PackedInt32Array();
}
PackedInt32Array SceneState::get_node_owner_id_path(int p_idx) const {
if (nodes[p_idx].owner < 0) {
return PackedInt32Array();
}
if (nodes[p_idx].owner & FLAG_ID_IS_PATH) {
int id = nodes[p_idx].owner & FLAG_MASK;
if (id >= id_paths.size()) {
return PackedInt32Array();
}
return id_paths[id];
}
return PackedInt32Array();
}
int SceneState::get_node_property_count(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, nodes.size(), -1);
return nodes[p_idx].properties.size();
@ -1909,6 +2151,24 @@ NodePath SceneState::get_connection_target(int p_idx) const {
}
}
PackedInt32Array SceneState::get_connection_target_id_path(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, connections.size(), PackedInt32Array());
if (connections[p_idx].to & FLAG_ID_IS_PATH && connections[p_idx].to < id_paths.size()) {
return id_paths[connections[p_idx].to];
} else {
return PackedInt32Array();
}
}
PackedInt32Array SceneState::get_connection_source_id_path(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, connections.size(), PackedInt32Array());
if (connections[p_idx].from & FLAG_ID_IS_PATH && connections[p_idx].from < id_paths.size()) {
return id_paths[connections[p_idx].from];
} else {
return PackedInt32Array();
}
}
StringName SceneState::get_connection_method(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, connections.size(), StringName());
return names[connections[p_idx].method];
@ -2013,12 +2273,13 @@ int SceneState::add_value(const Variant &p_value) {
return variants.size() - 1;
}
int SceneState::add_node_path(const NodePath &p_path) {
int SceneState::add_node_path(const NodePath &p_path, const PackedInt32Array &p_uid_path) {
node_paths.push_back(p_path);
id_paths.push_back(p_uid_path);
return (node_paths.size() - 1) | FLAG_ID_IS_PATH;
}
int SceneState::add_node(int p_parent, int p_owner, int p_type, int p_name, int p_instance, int p_index) {
int SceneState::add_node(int p_parent, int p_owner, int p_type, int p_name, int p_instance, int p_index, int32_t p_unique_id) {
NodeData nd;
nd.parent = p_parent;
nd.owner = p_owner;
@ -2029,6 +2290,8 @@ int SceneState::add_node(int p_parent, int p_owner, int p_type, int p_name, int
nodes.push_back(nd);
ids.push_back(p_unique_id);
return nodes.size() - 1;
}

View File

@ -39,6 +39,8 @@ class SceneState : public RefCounted {
Vector<StringName> names;
Vector<Variant> variants;
Vector<NodePath> node_paths;
Vector<PackedInt32Array> id_paths;
mutable PackedInt32Array ids;
Vector<NodePath> editable_instances;
mutable HashMap<NodePath, int> node_path_cache;
mutable HashMap<int, int> base_scene_node_remap;
@ -88,7 +90,7 @@ class SceneState : public RefCounted {
Vector<ConnectionData> connections;
Error _parse_node(Node *p_owner, Node *p_node, int p_parent_idx, HashMap<StringName, int> &name_map, HashMap<Variant, int, VariantHasher, VariantComparator> &variant_map, HashMap<Node *, int> &node_map, HashMap<Node *, int> &nodepath_map);
Error _parse_node(Node *p_owner, Node *p_node, int p_parent_idx, HashMap<StringName, int> &name_map, HashMap<Variant, int, VariantHasher, VariantComparator> &variant_map, HashMap<Node *, int> &node_map, HashMap<Node *, int> &nodepath_map, HashSet<int32_t> &ids_saved);
Error _parse_connections(Node *p_owner, Node *p_node, HashMap<StringName, int> &name_map, HashMap<Variant, int, VariantHasher, VariantComparator> &variant_map, HashMap<Node *, int> &node_map, HashMap<Node *, int> &nodepath_map);
String path;
@ -101,6 +103,8 @@ class SceneState : public RefCounted {
int _find_base_scene_node_remap_key(int p_idx) const;
Node *_recover_node_path_index(Node *p_base, int p_idx) const;
#ifdef TOOLS_ENABLED
public:
typedef void (*InstantiationWarningNotify)(const String &p_warning);
@ -171,7 +175,11 @@ public:
StringName get_node_type(int p_idx) const;
StringName get_node_name(int p_idx) const;
NodePath get_node_path(int p_idx, bool p_for_parent = false) const;
int32_t get_node_unique_id(int p_idx) const;
PackedInt32Array get_node_id_path(int p_idx) const;
PackedInt32Array get_node_parent_id_path(int p_idx) const;
NodePath get_node_owner_path(int p_idx) const;
PackedInt32Array get_node_owner_id_path(int p_idx) const;
Ref<PackedScene> get_node_instance(int p_idx) const;
String get_node_instance_placeholder(int p_idx) const;
bool is_node_instance_placeholder(int p_idx) const;
@ -188,6 +196,10 @@ public:
StringName get_connection_signal(int p_idx) const;
NodePath get_connection_target(int p_idx) const;
StringName get_connection_method(int p_idx) const;
PackedInt32Array get_connection_source_id_path(int p_idx) const;
PackedInt32Array get_connection_target_id_path(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;
@ -202,8 +214,8 @@ public:
int add_name(const StringName &p_name);
int add_value(const Variant &p_value);
int add_node_path(const NodePath &p_path);
int add_node(int p_parent, int p_owner, int p_type, int p_name, int p_instance, int p_index);
int add_node_path(const NodePath &p_path, const PackedInt32Array &p_uid_path);
int add_node(int p_parent, int p_owner, int p_type, int p_name, int p_instance, int p_index, int32_t p_unique_id);
void add_node_property(int p_node, int p_name, int p_value, bool p_deferred_node_path = false);
void add_node_group(int p_node, int p_group);
void set_base_scene(int p_idx);

View File

@ -194,6 +194,8 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars
int name = -1;
int instance = -1;
int index = -1;
int unique_id = Node::UNIQUE_SCENE_ID_UNASSIGNED;
//int base_scene=-1;
if (next_tag.fields.has("name")) {
@ -202,8 +204,14 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars
if (next_tag.fields.has("parent")) {
NodePath np = next_tag.fields["parent"];
np.prepend_period(); //compatible to how it manages paths internally
parent = packed_scene->get_state()->add_node_path(np);
PackedInt32Array np_id;
if (next_tag.fields.has("parent_id_path")) {
np_id = next_tag.fields["parent_id_path"];
}
parent = packed_scene->get_state()->add_node_path(np, np_id);
}
if (next_tag.fields.has("unique_id")) {
unique_id = next_tag.fields["unique_id"];
}
if (next_tag.fields.has("type")) {
@ -246,7 +254,11 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars
}
if (next_tag.fields.has("owner")) {
owner = packed_scene->get_state()->add_node_path(next_tag.fields["owner"]);
PackedInt32Array np_id;
if (next_tag.fields.has("owner_uid_path")) {
np_id = next_tag.fields["owner_uid_path"];
}
owner = packed_scene->get_state()->add_node_path(next_tag.fields["owner"], np_id);
} else {
if (parent != -1 && !(type == SceneState::TYPE_INSTANTIATED && instance == -1)) {
owner = 0; //if no owner, owner is root
@ -257,7 +269,7 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars
index = next_tag.fields["index"];
}
int node_id = packed_scene->get_state()->add_node(parent, owner, type, name, instance, index);
int node_id = packed_scene->get_state()->add_node(parent, owner, type, name, instance, index, unique_id);
if (next_tag.fields.has("groups")) {
Array groups = next_tag.fields["groups"];
@ -327,6 +339,16 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars
int unbinds = 0;
Array binds;
PackedInt32Array from_id;
if (next_tag.fields.has("from_uid_path")) {
from_id = next_tag.fields["from_uid_path"];
}
PackedInt32Array to_id;
if (next_tag.fields.has("to_uid_path")) {
to_id = next_tag.fields["to_uid_path"];
}
if (next_tag.fields.has("flags")) {
flags = next_tag.fields["flags"];
}
@ -345,8 +367,8 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars
}
packed_scene->get_state()->add_connection(
packed_scene->get_state()->add_node_path(from.simplified()),
packed_scene->get_state()->add_node_path(to.simplified()),
packed_scene->get_state()->add_node_path(from.simplified(), from_id),
packed_scene->get_state()->add_node_path(to.simplified(), to_id),
packed_scene->get_state()->add_name(signal),
packed_scene->get_state()->add_name(method),
flags,
@ -1963,7 +1985,10 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
StringName type = state->get_node_type(i);
StringName name = state->get_node_name(i);
int index = state->get_node_index(i);
NodePath path = state->get_node_path(i, true);
int unique_id = state->get_node_unique_id(i);
NodePath parent_path = state->get_node_path(i, true);
PackedInt32Array parent_id_path = state->get_node_parent_id_path(i);
PackedInt32Array owner_id_path = state->get_node_owner_id_path(i);
NodePath owner = state->get_node_owner_path(i);
Ref<PackedScene> instance = state->get_node_instance(i);
String instance_placeholder = state->get_node_instance_placeholder(i);
@ -1975,16 +2000,27 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
if (type != StringName()) {
header += " type=\"" + String(type) + "\"";
}
if (path != NodePath()) {
header += " parent=\"" + String(path.simplified()).c_escape() + "\"";
if (parent_path != NodePath()) {
header += " parent=\"" + String(parent_path.simplified()).c_escape() + "\"";
if (parent_id_path.size()) {
header += " parent_id_path=" + Variant(parent_id_path).get_construct_string();
}
}
if (owner != NodePath() && owner != NodePath(".")) {
header += " owner=\"" + String(owner.simplified()).c_escape() + "\"";
if (owner_id_path.size()) {
header += " owner_uid_path=" + Variant(owner_id_path).get_construct_string();
}
}
if (index >= 0) {
header += " index=\"" + itos(index) + "\"";
}
if (unique_id != Node::UNIQUE_SCENE_ID_UNASSIGNED) {
header += " unique_id=" + itos(unique_id) + "";
}
if (deferred_node_paths.size()) {
header += " node_paths=" + Variant(deferred_node_paths).get_construct_string();
}
@ -2050,6 +2086,20 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
connstr += " flags=" + itos(flags);
}
{
PackedInt32Array from_idp = state->get_connection_source_id_path(i);
if (from_idp.size()) {
connstr += " from_uid_path=" + Variant(from_idp).get_construct_string();
}
}
{
PackedInt32Array to_idp = state->get_connection_target_id_path(i);
if (to_idp.size()) {
connstr += " to_uid_path=" + Variant(to_idp).get_construct_string();
}
}
int unbinds = state->get_connection_unbinds(i);
if (unbinds > 0) {
connstr += " unbinds=" + itos(unbinds);