diff options
author | RĂ©mi Verschelde <remi@verschelde.fr> | 2022-05-05 15:57:50 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-05-05 15:57:50 +0200 |
commit | 71e41eb395319cc521c2e814126fcc2a5a8034d6 (patch) | |
tree | 742b044d9f2ba74f06536aba1203d99cd5e5de86 /scene | |
parent | f10f2a723290ff2c0647b12a8e3a73086e2ac4c4 (diff) | |
parent | 0a57f964a357976e023b638e872397ba94123776 (diff) |
Merge pull request #60597 from reduz/missing-node-resource-placeholders
Diffstat (limited to 'scene')
-rw-r--r-- | scene/main/missing_node.cpp | 98 | ||||
-rw-r--r-- | scene/main/missing_node.h | 63 | ||||
-rw-r--r-- | scene/register_scene_types.cpp | 2 | ||||
-rw-r--r-- | scene/resources/packed_scene.cpp | 83 | ||||
-rw-r--r-- | scene/resources/resource_format_text.cpp | 134 |
5 files changed, 346 insertions, 34 deletions
diff --git a/scene/main/missing_node.cpp b/scene/main/missing_node.cpp new file mode 100644 index 0000000000..6daa9dec6b --- /dev/null +++ b/scene/main/missing_node.cpp @@ -0,0 +1,98 @@ +/*************************************************************************/ +/* missing_node.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "missing_node.h" + +bool MissingNode::_set(const StringName &p_name, const Variant &p_value) { + if (is_recording_properties()) { + properties.insert(p_name, p_value); + return true; //always valid to set (add) + } else { + if (!properties.has(p_name)) { + return false; + } + + properties[p_name] = p_value; + return true; + } +} + +bool MissingNode::_get(const StringName &p_name, Variant &r_ret) const { + if (!properties.has(p_name)) { + return false; + } + r_ret = properties[p_name]; + return true; +} + +void MissingNode::_get_property_list(List<PropertyInfo> *p_list) const { + for (OrderedHashMap<StringName, Variant>::ConstElement E = properties.front(); E; E = E.next()) { + p_list->push_back(PropertyInfo(E.value().get_type(), E.key())); + } +} + +void MissingNode::set_original_class(const String &p_class) { + original_class = p_class; +} + +String MissingNode::get_original_class() const { + return original_class; +} + +void MissingNode::set_recording_properties(bool p_enable) { + recording_properties = p_enable; +} + +bool MissingNode::is_recording_properties() const { + return recording_properties; +} + +TypedArray<String> MissingNode::get_configuration_warnings() const { + // The mere existence of this node is warning. + TypedArray<String> 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.")); + return ret; +} + +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_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::BOOL, "recording_properties", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_recording_properties", "is_recording_properties"); +} + +MissingNode::MissingNode() { +} diff --git a/scene/main/missing_node.h b/scene/main/missing_node.h new file mode 100644 index 0000000000..b0f9492456 --- /dev/null +++ b/scene/main/missing_node.h @@ -0,0 +1,63 @@ +/*************************************************************************/ +/* missing_node.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef MISSING_NODE_H +#define MISSING_NODE_H + +#include "core/io/missing_resource.h" +#include "scene/main/node.h" + +class MissingNode : public Node { + GDCLASS(MissingNode, Node) + OrderedHashMap<StringName, Variant> properties; + + String original_class; + bool recording_properties = false; + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + + static void _bind_methods(); + +public: + void set_original_class(const String &p_class); + String get_original_class() const; + + void set_recording_properties(bool p_enable); + bool is_recording_properties() const; + + virtual TypedArray<String> get_configuration_warnings() const override; + + MissingNode(); +}; + +#endif // MISSING_NODE_H diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 3f5e221116..632952c2cb 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -129,6 +129,7 @@ #include "scene/main/canvas_layer.h" #include "scene/main/http_request.h" #include "scene/main/instance_placeholder.h" +#include "scene/main/missing_node.h" #include "scene/main/resource_preloader.h" #include "scene/main/scene_tree.h" #include "scene/main/timer.h" @@ -302,6 +303,7 @@ void register_scene_types() { GDREGISTER_CLASS(Object); GDREGISTER_CLASS(Node); + GDREGISTER_VIRTUAL_CLASS(MissingNode); GDREGISTER_ABSTRACT_CLASS(InstancePlaceholder); GDREGISTER_ABSTRACT_CLASS(Viewport); diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index b991cb1abe..f7504974d2 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -33,11 +33,13 @@ #include "core/config/engine.h" #include "core/config/project_settings.h" #include "core/core_string_names.h" +#include "core/io/missing_resource.h" #include "core/io/resource_loader.h" #include "scene/2d/node_2d.h" #include "scene/3d/node_3d.h" #include "scene/gui/control.h" #include "scene/main/instance_placeholder.h" +#include "scene/main/missing_node.h" #include "scene/property_utils.h" #define PACKED_SCENE_VERSION 2 @@ -130,6 +132,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } Node *node = nullptr; + MissingNode *missing_node = nullptr; if (i == 0 && base_scene_idx >= 0) { //scene inheritance on root node @@ -184,24 +187,33 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { memdelete(obj); obj = nullptr; } - WARN_PRINT(vformat("Node %s of type %s cannot be created. A placeholder will be created instead.", snames[n.name], snames[n.type]).ascii().get_data()); - if (n.parent >= 0 && n.parent < nc && ret_nodes[n.parent]) { - if (Object::cast_to<Control>(ret_nodes[n.parent])) { - obj = memnew(Control); - } else if (Object::cast_to<Node2D>(ret_nodes[n.parent])) { - obj = memnew(Node2D); + + if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) { + missing_node = memnew(MissingNode); + missing_node->set_original_class(snames[n.type]); + missing_node->set_recording_properties(true); + node = missing_node; + obj = missing_node; + } else { + WARN_PRINT(vformat("Node %s of type %s cannot be created. A placeholder will be created instead.", snames[n.name], snames[n.type]).ascii().get_data()); + if (n.parent >= 0 && n.parent < nc && ret_nodes[n.parent]) { + if (Object::cast_to<Control>(ret_nodes[n.parent])) { + obj = memnew(Control); + } else if (Object::cast_to<Node2D>(ret_nodes[n.parent])) { + obj = memnew(Node2D); #ifndef _3D_DISABLED - } else if (Object::cast_to<Node3D>(ret_nodes[n.parent])) { - obj = memnew(Node3D); + } else if (Object::cast_to<Node3D>(ret_nodes[n.parent])) { + obj = memnew(Node3D); #endif // _3D_DISABLED + } } - } - if (!obj) { - obj = memnew(Node); - } + if (!obj) { + obj = memnew(Node); + } - node = Object::cast_to<Node>(obj); + node = Object::cast_to<Node>(obj); + } } } @@ -214,6 +226,8 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { if (nprop_count) { const NodeData::Property *nprops = &n.properties[0]; + Dictionary missing_resource_properties; + for (int j = 0; j < nprop_count; j++) { bool valid; ERR_FAIL_INDEX_V(nprops[j].name, sname_count, nullptr); @@ -270,9 +284,24 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } else if (p_edit_state == GEN_EDIT_STATE_INSTANCE) { value = value.duplicate(true); // Duplicate arrays and dictionaries for the editor } - node->set(snames[nprops[j].name], value, &valid); + + bool set_valid = true; + if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled() && value.get_type() == Variant::OBJECT) { + Ref<MissingResource> mr = value; + if (mr.is_valid()) { + missing_resource_properties[snames[nprops[j].name]] = mr; + set_valid = false; + } + } + + if (set_valid) { + node->set(snames[nprops[j].name], value, &valid); + } } } + if (!missing_resource_properties.is_empty()) { + node->set_meta(META_MISSING_RESOURCES, missing_resource_properties); + } } //name @@ -324,6 +353,10 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } } + if (missing_node) { + missing_node->set_recording_properties(false); + } + ret_nodes[i] = node; if (node && gen_node_path_cache && ret_nodes[0]) { @@ -485,6 +518,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map p_node->get_property_list(&plist); Array pinned_props = _sanitize_node_pinned_properties(p_node); + Dictionary missing_resource_properties = p_node->get_meta(META_MISSING_RESOURCES, Dictionary()); for (const PropertyInfo &E : plist) { if (!(E.usage & PROPERTY_USAGE_STORAGE)) { @@ -493,6 +527,10 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map Variant forced_value; + if (E.name == META_PROPERTY_MISSING_RESOURCES) { + continue; //ignore this property when packing + } + // If instance or inheriting, not saving if property requested so, or it's meta if (states_stack.size()) { if ((E.usage & PROPERTY_USAGE_NO_INSTANCE_STATE)) { @@ -511,6 +549,14 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map StringName name = E.name; Variant value = forced_value.get_type() == Variant::NIL ? p_node->get(name) : forced_value; + if (E.type == Variant::OBJECT && missing_resource_properties.has(E.name)) { + // Was this missing resource overriden? If so do not save the old value. + Ref<Resource> ures = value; + if (ures.is_null()) { + value = missing_resource_properties[E.name]; + } + } + if (!pinned_props.has(name) && forced_value.get_type() == Variant::NIL) { bool is_valid_default = false; Variant default_value = PropertyUtils::get_property_default_value(p_node, name, &is_valid_default, &states_stack, true); @@ -566,11 +612,18 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map nd.owner = -1; } + MissingNode *missing_node = Object::cast_to<MissingNode>(p_node); + // Save the right type. If this node was created by an instance // then flag that the node should not be created but reused if (states_stack.is_empty() && !is_editable_instance) { //this node is not part of an instancing process, so save the type - nd.type = _nm_get_string(p_node->get_class(), name_map); + if (missing_node != nullptr) { + // Its a missing node (type non existant on load). + nd.type = _nm_get_string(missing_node->get_original_class(), name_map); + } else { + nd.type = _nm_get_string(p_node->get_class(), name_map); + } } else { // this node is part of an instantiated process, so do not save the type. // instead, save that it was instantiated diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index 04a6ad7675..ae321fd9a7 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -32,6 +32,7 @@ #include "core/config/project_settings.h" #include "core/io/dir_access.h" +#include "core/io/missing_resource.h" #include "core/io/resource_format_binary.h" #include "core/version.h" @@ -535,6 +536,8 @@ Error ResourceLoaderText::load() { } } + MissingResource *missing_resource = nullptr; + if (res.is_null()) { //not reuse if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE && ResourceCache::has(path)) { //only if it doesn't exist //cached, do not assign @@ -545,10 +548,17 @@ Error ResourceLoaderText::load() { Object *obj = ClassDB::instantiate(type); if (!obj) { - error_text += "Can't create sub resource of type: " + type; - _printerr(); - error = ERR_FILE_CORRUPT; - return error; + if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) { + missing_resource = memnew(MissingResource); + missing_resource->set_original_class(type); + missing_resource->set_recording_properties(true); + obj = missing_resource; + } else { + error_text += "Can't create sub resource of type: " + type; + _printerr(); + error = ERR_FILE_CORRUPT; + return error; + } } Resource *r = Object::cast_to<Resource>(obj); @@ -572,6 +582,8 @@ Error ResourceLoaderText::load() { res->set_scene_unique_id(id); } + Dictionary missing_resource_properties; + while (true) { String assign; Variant value; @@ -585,7 +597,23 @@ Error ResourceLoaderText::load() { if (!assign.is_empty()) { if (do_assign) { - res->set(assign, value); + bool set_valid = true; + + if (value.get_type() == Variant::OBJECT && missing_resource != nullptr) { + // If the property being set is a missing resource (and the parent is not), + // then setting it will most likely not work. + // Instead, save it as metadata. + + Ref<MissingResource> mr = value; + if (mr.is_valid()) { + missing_resource_properties[assign] = mr; + set_valid = false; + } + } + + if (set_valid) { + res->set(assign, value); + } } //it's assignment } else if (!next_tag.name.is_empty()) { @@ -599,6 +627,14 @@ Error ResourceLoaderText::load() { } } + if (missing_resource) { + missing_resource->set_recording_properties(false); + } + + if (!missing_resource_properties.is_empty()) { + res->set_meta(META_MISSING_RESOURCES, missing_resource_properties); + } + if (progress && resources_total > 0) { *progress = resource_current / float(resources_total); } @@ -624,13 +660,22 @@ Error ResourceLoaderText::load() { } } + MissingResource *missing_resource = nullptr; + if (!resource.is_valid()) { Object *obj = ClassDB::instantiate(res_type); if (!obj) { - error_text += "Can't create sub resource of type: " + res_type; - _printerr(); - error = ERR_FILE_CORRUPT; - return error; + if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) { + missing_resource = memnew(MissingResource); + missing_resource->set_original_class(res_type); + missing_resource->set_recording_properties(true); + obj = missing_resource; + } else { + error_text += "Can't create sub resource of type: " + res_type; + _printerr(); + error = ERR_FILE_CORRUPT; + return error; + } } Resource *r = Object::cast_to<Resource>(obj); @@ -646,6 +691,8 @@ Error ResourceLoaderText::load() { resource_current++; + Dictionary missing_resource_properties; + while (true) { String assign; Variant value; @@ -668,7 +715,23 @@ Error ResourceLoaderText::load() { } if (!assign.is_empty()) { - resource->set(assign, value); + bool set_valid = true; + + if (value.get_type() == Variant::OBJECT && missing_resource != nullptr) { + // If the property being set is a missing resource (and the parent is not), + // then setting it will most likely not work. + // Instead, save it as metadata. + + Ref<MissingResource> mr = value; + if (mr.is_valid()) { + missing_resource_properties[assign] = mr; + set_valid = false; + } + } + + if (set_valid) { + resource->set(assign, value); + } //it's assignment } else if (!next_tag.name.is_empty()) { error = ERR_FILE_CORRUPT; @@ -676,14 +739,24 @@ Error ResourceLoaderText::load() { _printerr(); return error; } else { - error = OK; - if (progress && resources_total > 0) { - *progress = resource_current / float(resources_total); - } - - return error; + break; } } + + if (missing_resource) { + missing_resource->set_recording_properties(false); + } + + if (!missing_resource_properties.is_empty()) { + resource->set_meta(META_MISSING_RESOURCES, missing_resource_properties); + } + + error = OK; + if (progress && resources_total > 0) { + *progress = resource_current / float(resources_total); + } + + return error; } //for scene files @@ -1593,6 +1666,15 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, } } +static String _resource_get_class(Ref<Resource> p_resource) { + Ref<MissingResource> missing_resource = p_resource; + if (missing_resource.is_valid()) { + return missing_resource->get_original_class(); + } else { + return p_resource->get_class(); + } +} + Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags) { if (p_path.ends_with(".tscn")) { packed_scene = p_resource; @@ -1634,7 +1716,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso { String title = packed_scene.is_valid() ? "[gd_scene " : "[gd_resource "; if (packed_scene.is_null()) { - title += "type=\"" + p_resource->get_class() + "\" "; + title += "type=\"" + _resource_get_class(p_resource) + "\" "; } int load_steps = saved_resources.size() + external_resources.size(); @@ -1758,7 +1840,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso if (res->get_scene_unique_id().is_empty()) { String new_id; while (true) { - new_id = res->get_class() + "_" + Resource::generate_scene_unique_id(); + new_id = _resource_get_class(res) + "_" + Resource::generate_scene_unique_id(); if (!used_unique_ids.has(new_id)) { break; @@ -1770,7 +1852,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso } String id = res->get_scene_unique_id(); - line += "type=\"" + res->get_class() + "\" id=\"" + id; + line += "type=\"" + _resource_get_class(res) + "\" id=\"" + id; f->store_line(line + "\"]"); if (takeover_paths) { res->set_path(p_path + "::" + id, true); @@ -1782,12 +1864,17 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso #endif } + Dictionary missing_resource_properties = p_resource->get_meta(META_MISSING_RESOURCES, Dictionary()); + List<PropertyInfo> property_list; res->get_property_list(&property_list); for (List<PropertyInfo>::Element *PE = property_list.front(); PE; PE = PE->next()) { if (skip_editor && PE->get().name.begins_with("__editor")) { continue; } + if (PE->get().name == META_PROPERTY_MISSING_RESOURCES) { + continue; + } if (PE->get().usage & PROPERTY_USAGE_STORAGE) { String name = PE->get().name; @@ -1802,6 +1889,15 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso } else { value = res->get(name); } + + if (PE->get().type == Variant::OBJECT && missing_resource_properties.has(PE->get().name)) { + // Was this missing resource overriden? If so do not save the old value. + Ref<Resource> ures = value; + if (ures.is_null()) { + value = missing_resource_properties[PE->get().name]; + } + } + Variant default_value = ClassDB::class_get_default_property_value(res->get_class(), name); if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) { |