From be4cbee8734eb1910f5294171da464ce2ed2862f Mon Sep 17 00:00:00 2001 From: kobewi Date: Thu, 4 Jan 2024 14:12:27 +0100 Subject: [PATCH] Allow opening scenes with missing scene dependency --- doc/classes/MissingNode.xml | 5 ++- scene/main/missing_node.cpp | 23 ++++++++++-- scene/main/missing_node.h | 4 +++ scene/resources/packed_scene.cpp | 45 +++++++++++++++++------- scene/resources/resource_format_text.cpp | 7 ++++ 5 files changed, 69 insertions(+), 15 deletions(-) diff --git a/doc/classes/MissingNode.xml b/doc/classes/MissingNode.xml index 4e528206cc6..9171c8321f2 100644 --- a/doc/classes/MissingNode.xml +++ b/doc/classes/MissingNode.xml @@ -13,8 +13,11 @@ The name of the class this node was supposed to be (see [method Object.get_class]). + + Returns the path of the scene this node was instance of originally. + - If set to [code]true[/code], allows new properties to be added on top of the existing ones with [method Object.set]. + If [code]true[/code], allows new properties to be set along with existing ones. If [code]false[/code], only existing properties' values can be set, and new properties cannot be added. diff --git a/scene/main/missing_node.cpp b/scene/main/missing_node.cpp index 49e565a39c4..02e079892c3 100644 --- a/scene/main/missing_node.cpp +++ b/scene/main/missing_node.cpp @@ -66,6 +66,14 @@ String MissingNode::get_original_class() const { return original_class; } +void MissingNode::set_original_scene(const String &p_scene) { + original_scene = p_scene; +} + +String MissingNode::get_original_scene() const { + return original_scene; +} + void MissingNode::set_recording_properties(bool p_enable) { recording_properties = p_enable; } @@ -77,8 +85,15 @@ bool MissingNode::is_recording_properties() const { Array MissingNode::get_configuration_warnings() const { // The mere existence of this node is warning. Array ret; - ret.push_back(vformat(RTR("This node was saved as class type '%s', which was no longer available when this scene was loaded."), original_class)); - ret.push_back(RTR("Data from the original node is kept as a placeholder until this type of node is available again. It can hence be safely re-saved without risk of data loss.")); + if (!original_scene.is_empty()) { + ret.push_back(vformat(RTR("This node was an instance of scene '%s', which was no longer available when this scene was loaded."), original_scene)); + ret.push_back(vformat(RTR("Saving current scene will discard instance and all its properties, including editable children edits (if existing)."))); + } else if (!original_class.is_empty()) { + ret.push_back(vformat(RTR("This node was saved as class type '%s', which was no longer available when this scene was loaded."), original_class)); + ret.push_back(RTR("Data from the original node is kept as a placeholder until this type of node is available again. It can hence be safely re-saved without risk of data loss.")); + } else { + ret.push_back(RTR("Unrecognized missing node. Check scene dependency errors for details.")); + } return ret; } @@ -86,11 +101,15 @@ void MissingNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_original_class", "name"), &MissingNode::set_original_class); ClassDB::bind_method(D_METHOD("get_original_class"), &MissingNode::get_original_class); + ClassDB::bind_method(D_METHOD("set_original_scene", "name"), &MissingNode::set_original_class); + ClassDB::bind_method(D_METHOD("get_original_scene"), &MissingNode::get_original_class); + ClassDB::bind_method(D_METHOD("set_recording_properties", "enable"), &MissingNode::set_recording_properties); ClassDB::bind_method(D_METHOD("is_recording_properties"), &MissingNode::is_recording_properties); // Expose, but not save. ADD_PROPERTY(PropertyInfo(Variant::STRING, "original_class", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_original_class", "get_original_class"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "original_scene", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_original_scene", "get_original_scene"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "recording_properties", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_recording_properties", "is_recording_properties"); } diff --git a/scene/main/missing_node.h b/scene/main/missing_node.h index a56469b8ac0..ccaf1e471e2 100644 --- a/scene/main/missing_node.h +++ b/scene/main/missing_node.h @@ -39,6 +39,7 @@ class MissingNode : public Node { HashMap properties; String original_class; + String original_scene; bool recording_properties = false; protected: @@ -52,6 +53,9 @@ public: void set_original_class(const String &p_class); String get_original_class() const; + void set_original_scene(const String &p_scene); + String get_original_scene() const; + void set_recording_properties(bool p_enable); bool is_recording_properties() const; diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index e90f163546c..a59ac9b56dc 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -191,7 +191,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { MissingNode *missing_node = nullptr; if (i == 0 && base_scene_idx >= 0) { - //scene inheritance on root node + // Scene inheritance on root node. Ref sdata = props[base_scene_idx]; ERR_FAIL_COND_V(!sdata.is_valid(), nullptr); node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); //only main gets main edit state @@ -201,14 +201,22 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } } else if (n.instance >= 0) { - //instance a scene into this node + // Instance a scene into this node. if (n.instance & FLAG_INSTANCE_IS_PLACEHOLDER) { - String scene_path = props[n.instance & FLAG_MASK]; + const String scene_path = props[n.instance & FLAG_MASK]; if (disable_placeholders) { Ref sdata = ResourceLoader::load(scene_path, "PackedScene"); - ERR_FAIL_COND_V(!sdata.is_valid(), nullptr); - node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); - ERR_FAIL_NULL_V(node, nullptr); + if (sdata.is_valid()) { + node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); + ERR_FAIL_NULL_V(node, nullptr); + } else if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) { + missing_node = memnew(MissingNode); + missing_node->set_original_scene(scene_path); + missing_node->set_recording_properties(true); + node = missing_node; + } else { + ERR_FAIL_V_MSG(nullptr, "Placeholder scene is missing."); + } } else { InstancePlaceholder *ip = memnew(InstancePlaceholder); ip->set_instance_path(scene_path); @@ -216,14 +224,27 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } node->set_scene_instance_load_placeholder(true); } else { - Ref sdata = props[n.instance & FLAG_MASK]; - ERR_FAIL_COND_V(!sdata.is_valid(), nullptr); - node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); - ERR_FAIL_NULL_V_MSG(node, nullptr, vformat("Failed to load scene dependency: \"%s\". Make sure the required scene is valid.", sdata->get_path())); + Ref res = props[n.instance & FLAG_MASK]; + Ref sdata = res; + if (sdata.is_valid()) { + node = sdata->instantiate(p_edit_state == GEN_EDIT_STATE_DISABLED ? PackedScene::GEN_EDIT_STATE_DISABLED : PackedScene::GEN_EDIT_STATE_INSTANCE); + ERR_FAIL_NULL_V_MSG(node, nullptr, vformat("Failed to load scene dependency: \"%s\". Make sure the required scene is valid.", sdata->get_path())); + } else if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) { + missing_node = memnew(MissingNode); +#ifdef TOOLS_ENABLED + if (res.is_valid()) { + missing_node->set_original_scene(res->get_meta("__load_path__", "")); + } +#endif + missing_node->set_recording_properties(true); + node = missing_node; + } else { + ERR_FAIL_V_MSG(nullptr, "Scene instance is missing."); + } } } else if (n.type == TYPE_INSTANTIATED) { - //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) { node = parent->_get_child_by_name(snames[n.name]); #ifdef DEBUG_ENABLED @@ -233,7 +254,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { #endif } } else { - //node belongs to this scene and must be created + // Node belongs to this scene and must be created. Object *obj = ClassDB::instantiate(snames[n.type]); node = Object::cast_to(obj); diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index a97ff5054db..d08919a8c65 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -176,6 +176,13 @@ Error ResourceLoaderText::_parse_ext_resource(VariantParser::Stream *p_stream, R } else { r_res = Ref(); } +#ifdef TOOLS_ENABLED + if (r_res.is_null()) { + // Hack to allow checking original path. + r_res.instantiate(); + r_res->set_meta("__load_path__", ext_resources[id].path); + } +#endif } VariantParser::get_token(p_stream, token, line, r_err_str);