summaryrefslogtreecommitdiff
path: root/scene
diff options
context:
space:
mode:
authorRĂ©mi Verschelde <remi@verschelde.fr>2022-05-05 15:57:50 +0200
committerGitHub <noreply@github.com>2022-05-05 15:57:50 +0200
commit71e41eb395319cc521c2e814126fcc2a5a8034d6 (patch)
tree742b044d9f2ba74f06536aba1203d99cd5e5de86 /scene
parentf10f2a723290ff2c0647b12a8e3a73086e2ac4c4 (diff)
parent0a57f964a357976e023b638e872397ba94123776 (diff)
Merge pull request #60597 from reduz/missing-node-resource-placeholders
Diffstat (limited to 'scene')
-rw-r--r--scene/main/missing_node.cpp98
-rw-r--r--scene/main/missing_node.h63
-rw-r--r--scene/register_scene_types.cpp2
-rw-r--r--scene/resources/packed_scene.cpp83
-rw-r--r--scene/resources/resource_format_text.cpp134
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))) {