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.
This commit is contained in:
Juan 2025-05-26 20:01:34 +02:00 committed by Rémi Verschelde
parent 60b7b8b16e
commit faddd60c40
No known key found for this signature in database
GPG key ID: C3336907360768E1
6 changed files with 370 additions and 28 deletions

View file

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

View file

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

View file

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

View file

@ -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) \ #define NODE_FROM_ID(p_name, p_id) \
Node *p_name; \ Node *p_name; \
if (p_id & FLAG_ID_IS_PATH) { \ if (p_id & FLAG_ID_IS_PATH) { \
NodePath np = node_paths[p_id & FLAG_MASK]; \ NodePath np = node_paths[p_id & FLAG_MASK]; \
p_name = ret_nodes[0]->get_node_or_null(np); \ p_name = ret_nodes[0]->get_node_or_null(np); \
} else { \ if (!p_name) { \
ERR_FAIL_INDEX_V(p_id & FLAG_MASK, nc, nullptr); \ p_name = _recover_node_path_index(ret_nodes[0], p_id & FLAG_MASK); \
p_name = ret_nodes[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 bool save_node = p_node == p_owner; // owner is always saved
save_node = 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;
} }

View file

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

View file

@ -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 PackedInt32Array np_id;
parent = packed_scene->get_state()->add_node_path(np); 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(from.simplified(), from_id),
packed_scene->get_state()->add_node_path(to.simplified()), 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()) { if (parent_path != NodePath()) {
header += " parent=\"" + String(path.simplified()).c_escape() + "\""; 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);