From faddd60c40e0842a140c3243bf0ba4fa7a5a85f2 Mon Sep 17 00:00:00 2001 From: Juan Date: Mon, 26 May 2025 20:01:34 +0200 Subject: [PATCH] Add unique Node IDs to support base and instantiated scene refactorings The main goal of this PR is to safeguard when a base or instantiated scene changes (nodes renamed, moved or readded), that the hierarchy is still maintained and the node and its overridden properties can be preserved. What it does: * Implements unique node IDs. * These IDs act as a fallback to names when saving. * The IDs are **USED AS A FALLBACK**, so they are just an addition. It should not break any current existing scene. * If a scene renames or moves a node, inherited or instantiated scenes will no longer lose reference to it. Unlike the previous approach, this one is intended to be a fallback, only used if the node is not found. This makes it safer to implement and ensure that, at worst case, we fail to find the node, but nothing breaks. --- core/io/resource_uid.cpp | 1 + scene/main/node.cpp | 8 + scene/main/node.h | 8 + scene/resources/packed_scene.cpp | 295 +++++++++++++++++++++-- scene/resources/packed_scene.h | 18 +- scene/resources/resource_format_text.cpp | 68 +++++- 6 files changed, 370 insertions(+), 28 deletions(-) diff --git a/core/io/resource_uid.cpp b/core/io/resource_uid.cpp index 7b3fd0948df..7a855dc16cc 100644 --- a/core/io/resource_uid.cpp +++ b/core/io/resource_uid.cpp @@ -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); diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 09b5ebd285c..3b514daad29 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -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(); diff --git a/scene/main/node.h b/scene/main/node.h index ba4a2c78505..26d73811518 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -132,6 +132,9 @@ public: #ifdef DEBUG_ENABLED static SafeNumeric 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; diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index 242b347edb1..b72500d58ba 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -124,18 +124,38 @@ Ref SceneState::get_remap_resource(const Ref &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 stray_instances; -#define NODE_FROM_ID(p_name, p_id) \ - Node *p_name; \ - 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); \ - } else { \ - ERR_FAIL_INDEX_V(p_id & FLAG_MASK, nc, nullptr); \ - p_name = ret_nodes[p_id & FLAG_MASK]; \ +#define NODE_FROM_ID(p_name, p_id) \ + Node *p_name; \ + 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]; \ } int nc = nodes.size(); @@ -165,6 +185,8 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { LocalVector 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 &name_map, HashMap &variant_map, HashMap &node_map, HashMap &nodepath_map) { +Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, HashMap &name_map, HashMap &variant_map, HashMap &node_map, HashMap &nodepath_map, HashSet &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_map; HashMap node_map; HashMap nodepath_map; + HashSet 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 &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 &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 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 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 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 sdata = ss->variants[ss->base_scene_idx]; + if (sdata.is_null()) { + return nullptr; + } + Ref 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 sdata = ss->variants[nd.instance & FLAG_MASK]; + ERR_FAIL_COND_V(sdata.is_null(), nullptr); + Ref 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; } diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h index 51e8a8df298..59af4721cdb 100644 --- a/scene/resources/packed_scene.h +++ b/scene/resources/packed_scene.h @@ -39,6 +39,8 @@ class SceneState : public RefCounted { Vector names; Vector variants; Vector node_paths; + Vector id_paths; + mutable PackedInt32Array ids; Vector editable_instances; mutable HashMap node_path_cache; mutable HashMap base_scene_node_remap; @@ -88,7 +90,7 @@ class SceneState : public RefCounted { Vector connections; - Error _parse_node(Node *p_owner, Node *p_node, int p_parent_idx, HashMap &name_map, HashMap &variant_map, HashMap &node_map, HashMap &nodepath_map); + Error _parse_node(Node *p_owner, Node *p_node, int p_parent_idx, HashMap &name_map, HashMap &variant_map, HashMap &node_map, HashMap &nodepath_map, HashSet &ids_saved); Error _parse_connections(Node *p_owner, Node *p_node, HashMap &name_map, HashMap &variant_map, HashMap &node_map, HashMap &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 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); diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index 3665fdd4e95..4206d13ea97 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -194,6 +194,8 @@ Ref 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 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 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 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 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 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 Refget_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 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= 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 Refget_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);