Browse Source

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

Add unique Node IDs to support base and instantiated scene refactorings
Rémi Verschelde 1 week ago
parent
commit
ef5cd99604

+ 1 - 0
core/io/resource_uid.cpp

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

+ 8 - 0
scene/main/node.cpp

@@ -2082,6 +2082,14 @@ Node *Node::find_parent(const String &p_pattern) const {
 	return nullptr;
 	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 {
 Window *Node::get_window() const {
 	ERR_THREAD_GUARD_V(nullptr);
 	ERR_THREAD_GUARD_V(nullptr);
 	Viewport *vp = get_viewport();
 	Viewport *vp = get_viewport();

+ 8 - 0
scene/main/node.h

@@ -132,6 +132,9 @@ public:
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
 	static SafeNumeric<uint64_t> total_node_count;
 	static SafeNumeric<uint64_t> total_node_count;
 #endif
 #endif
+	enum {
+		UNIQUE_SCENE_ID_UNASSIGNED = 0
+	};
 
 
 	void _update_process(bool p_enable, bool p_for_children);
 	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_inherited : 1;
 		mutable bool is_translation_domain_dirty : 1;
 		mutable bool is_translation_domain_dirty : 1;
 
 
+		int32_t unique_scene_id = UNIQUE_SCENE_ID_UNASSIGNED;
+
 		mutable NodePath *path_cache = nullptr;
 		mutable NodePath *path_cache = nullptr;
 
 
 	} data;
 	} data;
@@ -527,6 +532,9 @@ public:
 	Node *get_parent() const;
 	Node *get_parent() const;
 	Node *find_parent(const String &p_pattern) 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_window() const;
 	Window *get_non_popup_window() const;
 	Window *get_non_popup_window() const;
 	Window *get_last_exclusive_window() const;
 	Window *get_last_exclusive_window() const;

+ 279 - 16
scene/resources/packed_scene.cpp

@@ -124,18 +124,38 @@ Ref<Resource> SceneState::get_remap_resource(const Ref<Resource> &p_resource, Ha
 	return remap_resource;
 	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 {
 Node *SceneState::instantiate(GenEditState p_edit_state) const {
 	// Nodes where instantiation failed (because something is missing.)
 	// Nodes where instantiation failed (because something is missing.)
 	List<Node *> stray_instances;
 	List<Node *> 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();
 	int nc = nodes.size();
@@ -165,6 +185,8 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
 
 
 	LocalVector<DeferredNodePathProperties> deferred_node_paths;
 	LocalVector<DeferredNodePathProperties> deferred_node_paths;
 
 
+	bool deep_search_warned = false;
+
 	for (int i = 0; i < nc; i++) {
 	for (int i = 0; i < nc; i++) {
 		const NodeData &n = nd[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.
 			// Get the node from somewhere, it likely already exists from another instance.
 			if (parent) {
 			if (parent) {
 				node = parent->_get_child_by_name(snames[n.name]);
 				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
 #ifdef DEBUG_ENABLED
 				if (!node) {
 				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());
 					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 (node) {
+			if (i < ids.size()) {
+				node->set_unique_scene_id(ids[i]);
+			}
 			// may not have found the node (part of instantiated scene and removed)
 			// may not have found the node (part of instantiated scene and removed)
 			// if found all is good, otherwise ignore
 			// 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;
 	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
 	// this function handles all the work related to properly packing scenes, be it
 	// instantiated or inherited.
 	// instantiated or inherited.
 	// given the complexity of this process, an attempt will be made to properly
 	// 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
 	// below condition is true for all nodes of the scene being saved, and ones in subscenes
 	// that hold changes
 	// 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
 	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 idx = nodes.size();
 	int parent_node = NO_PARENT_SAVED;
 	int parent_node = NO_PARENT_SAVED;
 
 
-	if (save_node) {
+	if (save_node || save_data) {
 		//don't save the node if nothing and subscene
 		//don't save the node if nothing and subscene
 
 
 		node_map[p_node] = idx;
 		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;
 			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;
 		parent_node = idx;
 		nodes.push_back(nd);
 		nodes.push_back(nd);
 	}
 	}
 
 
 	for (int i = 0; i < p_node->get_child_count(); i++) {
 	for (int i = 0; i < p_node->get_child_count(); i++) {
 		Node *c = p_node->get_child(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) {
 		if (err) {
 			return err;
 			return err;
 		}
 		}
@@ -1273,6 +1348,7 @@ Error SceneState::pack(Node *p_scene) {
 	HashMap<Variant, int, VariantHasher, VariantComparator> variant_map;
 	HashMap<Variant, int, VariantHasher, VariantComparator> variant_map;
 	HashMap<Node *, int> node_map;
 	HashMap<Node *, int> node_map;
 	HashMap<Node *, int> nodepath_map;
 	HashMap<Node *, int> nodepath_map;
+	HashSet<int32_t> ids_saved;
 
 
 	// If using scene inheritance, pack the scene it inherits from.
 	// If using scene inheritance, pack the scene it inherits from.
 	if (scene->get_scene_inherited_state().is_valid()) {
 	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.
 	// 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) {
 	if (err) {
 		clear();
 		clear();
 		ERR_FAIL_V(err);
 		ERR_FAIL_V(err);
@@ -1310,8 +1386,31 @@ Error SceneState::pack(Node *p_scene) {
 	}
 	}
 
 
 	node_paths.resize(nodepath_map.size());
 	node_paths.resize(nodepath_map.size());
+	id_paths.resize(nodepath_map.size());
 	for (const KeyValue<Node *, int> &E : nodepath_map) {
 	for (const KeyValue<Node *, int> &E : nodepath_map) {
 		node_paths.write[E.value] = scene->get_path_to(E.key);
 		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()) {
 	if (Engine::get_singleton()->is_editor_hint()) {
@@ -1340,6 +1439,8 @@ void SceneState::clear() {
 	node_path_cache.clear();
 	node_path_cache.clear();
 	node_paths.clear();
 	node_paths.clear();
 	editable_instances.clear();
 	editable_instances.clear();
+	ids.clear();
+	id_paths.clear();
 	base_scene_idx = -1;
 	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) {
 	for (const NodePath &E : p_scene_state->node_paths) {
 		node_paths.append(E);
 		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) {
 	for (const NodePath &E : p_scene_state->editable_instances) {
 		editable_instances.append(E);
 		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.");
 	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 (!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()) {
 		if (get_base_scene_state().is_valid()) {
 			int idx = get_base_scene_state()->find_node_by_path(p_node);
 			int idx = get_base_scene_state()->find_node_by_path(p_node);
 			if (idx != -1) {
 			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;
 	Array np;
 	if (p_dictionary.has("node_paths")) {
 	if (p_dictionary.has("node_paths")) {
 		np = p_dictionary["node_paths"];
 		np = p_dictionary["node_paths"];
 	}
 	}
+
 	node_paths.resize(np.size());
 	node_paths.resize(np.size());
 	for (int i = 0; i < np.size(); i++) {
 	for (int i = 0; i < np.size(); i++) {
 		node_paths.write[i] = np[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;
 	Array ei;
 	if (p_dictionary.has("editable_instances")) {
 	if (p_dictionary.has("editable_instances")) {
 		ei = p_dictionary["editable_instances"];
 		ei = p_dictionary["editable_instances"];
@@ -1678,6 +1798,7 @@ Dictionary SceneState::get_bundled_scene() const {
 	}
 	}
 
 
 	d["nodes"] = rnodes;
 	d["nodes"] = rnodes;
+	d["node_ids"] = ids;
 
 
 	Vector<int> rconns;
 	Vector<int> rconns;
 	d["conn_count"] = connections.size();
 	d["conn_count"] = connections.size();
@@ -1705,6 +1826,13 @@ Dictionary SceneState::get_bundled_scene() const {
 	}
 	}
 	d["node_paths"] = rnode_paths;
 	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;
 	Array reditable_instances;
 	reditable_instances.resize(editable_instances.size());
 	reditable_instances.resize(editable_instances.size());
 	for (int i = 0; i < editable_instances.size(); i++) {
 	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;
 	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 {
 NodePath SceneState::get_node_path(int p_idx, bool p_for_parent) const {
 	ERR_FAIL_INDEX_V(p_idx, nodes.size(), NodePath());
 	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);
 	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 {
 int SceneState::get_node_property_count(int p_idx) const {
 	ERR_FAIL_INDEX_V(p_idx, nodes.size(), -1);
 	ERR_FAIL_INDEX_V(p_idx, nodes.size(), -1);
 	return nodes[p_idx].properties.size();
 	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 {
 StringName SceneState::get_connection_method(int p_idx) const {
 	ERR_FAIL_INDEX_V(p_idx, connections.size(), StringName());
 	ERR_FAIL_INDEX_V(p_idx, connections.size(), StringName());
 	return names[connections[p_idx].method];
 	return names[connections[p_idx].method];
@@ -2013,12 +2273,13 @@ int SceneState::add_value(const Variant &p_value) {
 	return variants.size() - 1;
 	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);
 	node_paths.push_back(p_path);
+	id_paths.push_back(p_uid_path);
 	return (node_paths.size() - 1) | FLAG_ID_IS_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;
 	NodeData nd;
 	nd.parent = p_parent;
 	nd.parent = p_parent;
 	nd.owner = p_owner;
 	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);
 	nodes.push_back(nd);
 
 
+	ids.push_back(p_unique_id);
+
 	return nodes.size() - 1;
 	return nodes.size() - 1;
 }
 }
 
 

+ 15 - 3
scene/resources/packed_scene.h

@@ -39,6 +39,8 @@ class SceneState : public RefCounted {
 	Vector<StringName> names;
 	Vector<StringName> names;
 	Vector<Variant> variants;
 	Vector<Variant> variants;
 	Vector<NodePath> node_paths;
 	Vector<NodePath> node_paths;
+	Vector<PackedInt32Array> id_paths;
+	mutable PackedInt32Array ids;
 	Vector<NodePath> editable_instances;
 	Vector<NodePath> editable_instances;
 	mutable HashMap<NodePath, int> node_path_cache;
 	mutable HashMap<NodePath, int> node_path_cache;
 	mutable HashMap<int, int> base_scene_node_remap;
 	mutable HashMap<int, int> base_scene_node_remap;
@@ -88,7 +90,7 @@ class SceneState : public RefCounted {
 
 
 	Vector<ConnectionData> connections;
 	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);
 	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;
 	String path;
@@ -101,6 +103,8 @@ class SceneState : public RefCounted {
 
 
 	int _find_base_scene_node_remap_key(int p_idx) const;
 	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
 #ifdef TOOLS_ENABLED
 public:
 public:
 	typedef void (*InstantiationWarningNotify)(const String &p_warning);
 	typedef void (*InstantiationWarningNotify)(const String &p_warning);
@@ -171,7 +175,11 @@ public:
 	StringName get_node_type(int p_idx) const;
 	StringName get_node_type(int p_idx) const;
 	StringName get_node_name(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;
 	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;
 	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;
 	Ref<PackedScene> get_node_instance(int p_idx) const;
 	String get_node_instance_placeholder(int p_idx) const;
 	String get_node_instance_placeholder(int p_idx) const;
 	bool is_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;
 	StringName get_connection_signal(int p_idx) const;
 	NodePath get_connection_target(int p_idx) const;
 	NodePath get_connection_target(int p_idx) const;
 	StringName get_connection_method(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_flags(int p_idx) const;
 	int get_connection_unbinds(int p_idx) const;
 	int get_connection_unbinds(int p_idx) const;
 	Array get_connection_binds(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_name(const StringName &p_name);
 	int add_value(const Variant &p_value);
 	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_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 add_node_group(int p_node, int p_group);
 	void set_base_scene(int p_idx);
 	void set_base_scene(int p_idx);

+ 59 - 9
scene/resources/resource_format_text.cpp

@@ -194,6 +194,8 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars
 			int name = -1;
 			int name = -1;
 			int instance = -1;
 			int instance = -1;
 			int index = -1;
 			int index = -1;
+			int unique_id = Node::UNIQUE_SCENE_ID_UNASSIGNED;
+
 			//int base_scene=-1;
 			//int base_scene=-1;
 
 
 			if (next_tag.fields.has("name")) {
 			if (next_tag.fields.has("name")) {
@@ -202,8 +204,14 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars
 
 
 			if (next_tag.fields.has("parent")) {
 			if (next_tag.fields.has("parent")) {
 				NodePath np = next_tag.fields["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")) {
 			if (next_tag.fields.has("type")) {
@@ -246,7 +254,11 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars
 			}
 			}
 
 
 			if (next_tag.fields.has("owner")) {
 			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 {
 			} else {
 				if (parent != -1 && !(type == SceneState::TYPE_INSTANTIATED && instance == -1)) {
 				if (parent != -1 && !(type == SceneState::TYPE_INSTANTIATED && instance == -1)) {
 					owner = 0; //if no owner, owner is root
 					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"];
 				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")) {
 			if (next_tag.fields.has("groups")) {
 				Array groups = next_tag.fields["groups"];
 				Array groups = next_tag.fields["groups"];
@@ -327,6 +339,16 @@ Ref<PackedScene> ResourceLoaderText::_parse_node_tag(VariantParser::ResourcePars
 			int unbinds = 0;
 			int unbinds = 0;
 			Array binds;
 			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")) {
 			if (next_tag.fields.has("flags")) {
 				flags = next_tag.fields["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_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(signal),
 					packed_scene->get_state()->add_name(method),
 					packed_scene->get_state()->add_name(method),
 					flags,
 					flags,
@@ -1963,7 +1985,10 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
 			StringName type = state->get_node_type(i);
 			StringName type = state->get_node_type(i);
 			StringName name = state->get_node_name(i);
 			StringName name = state->get_node_name(i);
 			int index = state->get_node_index(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);
 			NodePath owner = state->get_node_owner_path(i);
 			Ref<PackedScene> instance = state->get_node_instance(i);
 			Ref<PackedScene> instance = state->get_node_instance(i);
 			String instance_placeholder = state->get_node_instance_placeholder(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()) {
 			if (type != StringName()) {
 				header += " type=\"" + String(type) + "\"";
 				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(".")) {
 			if (owner != NodePath() && owner != NodePath(".")) {
 				header += " owner=\"" + String(owner.simplified()).c_escape() + "\"";
 				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) {
 			if (index >= 0) {
 				header += " index=\"" + itos(index) + "\"";
 				header += " index=\"" + itos(index) + "\"";
 			}
 			}
 
 
+			if (unique_id != Node::UNIQUE_SCENE_ID_UNASSIGNED) {
+				header += " unique_id=" + itos(unique_id) + "";
+			}
+
 			if (deferred_node_paths.size()) {
 			if (deferred_node_paths.size()) {
 				header += " node_paths=" + Variant(deferred_node_paths).get_construct_string();
 				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);
 				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);
 			int unbinds = state->get_connection_unbinds(i);
 			if (unbinds > 0) {
 			if (unbinds > 0) {
 				connstr += " unbinds=" + itos(unbinds);
 				connstr += " unbinds=" + itos(unbinds);