summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/io/missing_resource.cpp90
-rw-r--r--core/io/missing_resource.h63
-rw-r--r--core/io/resource_format_binary.cpp72
-rw-r--r--core/io/resource_loader.cpp5
-rw-r--r--core/io/resource_loader.h4
-rw-r--r--core/object/object.cpp35
-rw-r--r--core/register_core_types.cpp2
-rw-r--r--doc/classes/MissingNode.xml17
-rw-r--r--doc/classes/MissingResource.xml17
-rw-r--r--editor/editor_node.cpp1
-rw-r--r--editor/icons/MissingNode.svg1
-rw-r--r--editor/icons/MissingResource.svg1
-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
17 files changed, 630 insertions, 58 deletions
diff --git a/core/io/missing_resource.cpp b/core/io/missing_resource.cpp
new file mode 100644
index 0000000000..7aae6c6f0a
--- /dev/null
+++ b/core/io/missing_resource.cpp
@@ -0,0 +1,90 @@
+/*************************************************************************/
+/* missing_resource.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_resource.h"
+
+bool MissingResource::_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 MissingResource::_get(const StringName &p_name, Variant &r_ret) const {
+ if (!properties.has(p_name)) {
+ return false;
+ }
+ r_ret = properties[p_name];
+ return true;
+}
+
+void MissingResource::_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 MissingResource::set_original_class(const String &p_class) {
+ original_class = p_class;
+}
+
+String MissingResource::get_original_class() const {
+ return original_class;
+}
+
+void MissingResource::set_recording_properties(bool p_enable) {
+ recording_properties = p_enable;
+}
+
+bool MissingResource::is_recording_properties() const {
+ return recording_properties;
+}
+
+void MissingResource::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_original_class", "name"), &MissingResource::set_original_class);
+ ClassDB::bind_method(D_METHOD("get_original_class"), &MissingResource::get_original_class);
+
+ ClassDB::bind_method(D_METHOD("set_recording_properties", "enable"), &MissingResource::set_recording_properties);
+ ClassDB::bind_method(D_METHOD("is_recording_properties"), &MissingResource::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");
+}
+
+MissingResource::MissingResource() {
+}
diff --git a/core/io/missing_resource.h b/core/io/missing_resource.h
new file mode 100644
index 0000000000..e87efe1a98
--- /dev/null
+++ b/core/io/missing_resource.h
@@ -0,0 +1,63 @@
+/*************************************************************************/
+/* missing_resource.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_RESOURCE_H
+#define MISSING_RESOURCE_H
+
+#include "core/io/resource.h"
+
+#define META_PROPERTY_MISSING_RESOURCES "metadata/_missing_resources"
+#define META_MISSING_RESOURCES "_missing_resources"
+
+class MissingResource : public Resource {
+ GDCLASS(MissingResource, Resource)
+ 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;
+
+ MissingResource();
+};
+
+#endif // MISSING_RESOURCE_H
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index c9eb0e5234..fbb4293961 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -35,6 +35,7 @@
#include "core/io/file_access_compressed.h"
#include "core/io/image.h"
#include "core/io/marshalls.h"
+#include "core/io/missing_resource.h"
#include "core/version.h"
//#define print_bl(m_what) print_line(m_what)
@@ -728,13 +729,23 @@ Error ResourceLoaderBinary::load() {
}
}
+ MissingResource *missing_resource = nullptr;
+
if (res.is_null()) {
//did not replace
Object *obj = ClassDB::instantiate(t);
if (!obj) {
- error = ERR_FILE_CORRUPT;
- ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource of unrecognized type in file: " + t + ".");
+ if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) {
+ //create a missing resource
+ missing_resource = memnew(MissingResource);
+ missing_resource->set_original_class(t);
+ missing_resource->set_recording_properties(true);
+ obj = missing_resource;
+ } else {
+ error = ERR_FILE_CORRUPT;
+ ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource of unrecognized type in file: " + t + ".");
+ }
}
Resource *r = Object::cast_to<Resource>(obj);
@@ -760,6 +771,8 @@ Error ResourceLoaderBinary::load() {
//set properties
+ Dictionary missing_resource_properties;
+
for (int j = 0; j < pc; j++) {
StringName name = _get_string();
@@ -775,8 +788,32 @@ Error ResourceLoaderBinary::load() {
return error;
}
- res->set(name, 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[name] = mr;
+ set_valid = false;
+ }
+ }
+
+ if (set_valid) {
+ res->set(name, value);
+ }
+ }
+
+ if (missing_resource) {
+ missing_resource->set_recording_properties(false);
+ }
+
+ if (!missing_resource_properties.is_empty()) {
+ res->set_meta(META_MISSING_RESOURCES, missing_resource_properties);
}
+
#ifdef TOOLS_ENABLED
res->set_edited(false);
#endif
@@ -1833,6 +1870,15 @@ int ResourceFormatSaverBinaryInstance::get_string_index(const String &p_string)
return strings.size() - 1;
}
+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 ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags) {
Error err;
Ref<FileAccess> f;
@@ -1885,7 +1931,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
return ERR_CANT_CREATE;
}
- save_unicode_string(f, p_resource->get_class());
+ save_unicode_string(f, _resource_get_class(p_resource));
f->store_64(0); //offset to import metadata
{
uint32_t format_flags = FORMAT_FLAG_NAMED_SCENE_IDS | FORMAT_FLAG_UIDS;
@@ -1902,10 +1948,12 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
List<ResourceData> resources;
+ Dictionary missing_resource_properties = p_resource->get_meta(META_MISSING_RESOURCES, Dictionary());
+
{
for (const Ref<Resource> &E : saved_resources) {
ResourceData &rd = resources.push_back(ResourceData())->get();
- rd.type = E->get_class();
+ rd.type = _resource_get_class(E);
List<PropertyInfo> property_list;
E->get_property_list(&property_list);
@@ -1914,6 +1962,10 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
if (skip_editor && F.name.begins_with("__editor")) {
continue;
}
+ if (F.name == META_PROPERTY_MISSING_RESOURCES) {
+ continue;
+ }
+
if ((F.usage & PROPERTY_USAGE_STORAGE)) {
Property p;
p.name_idx = get_string_index(F.name);
@@ -1929,6 +1981,14 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
p.value = E->get(F.name);
}
+ if (p.pi.type == Variant::OBJECT && missing_resource_properties.has(F.name)) {
+ // Was this missing resource overriden? If so do not save the old value.
+ Ref<Resource> res = p.value;
+ if (res.is_null()) {
+ p.value = missing_resource_properties[F.name];
+ }
+ }
+
Variant default_value = ClassDB::class_get_default_property_value(E->get_class(), F.name);
if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, p.value, default_value))) {
@@ -1990,7 +2050,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
String new_id;
while (true) {
- new_id = r->get_class() + "_" + Resource::generate_scene_unique_id();
+ new_id = _resource_get_class(r) + "_" + Resource::generate_scene_unique_id();
if (!used_unique_ids.has(new_id)) {
break;
}
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index c8cebd672a..d125dd4e91 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -979,6 +979,10 @@ void ResourceLoader::remove_custom_resource_format_loader(String script_path) {
}
}
+void ResourceLoader::set_create_missing_resources_if_class_unavailable(bool p_enable) {
+ create_missing_resources_if_class_unavailable = p_enable;
+}
+
void ResourceLoader::add_custom_loaders() {
// Custom loaders registration exploits global class names
@@ -1030,6 +1034,7 @@ void *ResourceLoader::err_notify_ud = nullptr;
DependencyErrorNotify ResourceLoader::dep_err_notify = nullptr;
void *ResourceLoader::dep_err_notify_ud = nullptr;
+bool ResourceLoader::create_missing_resources_if_class_unavailable = false;
bool ResourceLoader::abort_on_missing_resource = true;
bool ResourceLoader::timestamp_on_load = false;
diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h
index 15ecfacf4a..ea18ac23fd 100644
--- a/core/io/resource_loader.h
+++ b/core/io/resource_loader.h
@@ -110,6 +110,7 @@ private:
static void *dep_err_notify_ud;
static DependencyErrorNotify dep_err_notify;
static bool abort_on_missing_resource;
+ static bool create_missing_resources_if_class_unavailable;
static HashMap<String, Vector<String>> translation_remaps;
static HashMap<String, String> path_remaps;
@@ -222,6 +223,9 @@ public:
static void add_custom_loaders();
static void remove_custom_loaders();
+ static void set_create_missing_resources_if_class_unavailable(bool p_enable);
+ _FORCE_INLINE_ static bool is_creating_missing_resources_if_class_unavailable_enabled() { return create_missing_resources_if_class_unavailable; }
+
static void initialize();
static void finalize();
};
diff --git a/core/object/object.cpp b/core/object/object.cpp
index 1defd85a14..2b62041533 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -434,15 +434,6 @@ void Object::set(const StringName &p_name, const Variant &p_value, bool *r_valid
}
}
- // Something inside the object... :|
- bool success = _setv(p_name, p_value);
- if (success) {
- if (r_valid) {
- *r_valid = true;
- }
- return;
- }
-
#ifdef TOOLS_ENABLED
if (script_instance) {
bool valid;
@@ -456,6 +447,15 @@ void Object::set(const StringName &p_name, const Variant &p_value, bool *r_valid
}
#endif
+ // Something inside the object... :|
+ bool success = _setv(p_name, p_value);
+ if (success) {
+ if (r_valid) {
+ *r_valid = true;
+ }
+ return;
+ }
+
if (r_valid) {
*r_valid = false;
}
@@ -518,15 +518,6 @@ Variant Object::get(const StringName &p_name, bool *r_valid) const {
return ret;
} else {
- // Something inside the object... :|
- bool success = _getv(p_name, ret);
- if (success) {
- if (r_valid) {
- *r_valid = true;
- }
- return ret;
- }
-
#ifdef TOOLS_ENABLED
if (script_instance) {
bool valid;
@@ -539,6 +530,14 @@ Variant Object::get(const StringName &p_name, bool *r_valid) const {
}
}
#endif
+ // Something inside the object... :|
+ bool success = _getv(p_name, ret);
+ if (success) {
+ if (r_valid) {
+ *r_valid = true;
+ }
+ return ret;
+ }
if (r_valid) {
*r_valid = false;
diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp
index 415b56cd83..e60d325f74 100644
--- a/core/register_core_types.cpp
+++ b/core/register_core_types.cpp
@@ -49,6 +49,7 @@
#include "core/io/image_loader.h"
#include "core/io/json.h"
#include "core/io/marshalls.h"
+#include "core/io/missing_resource.h"
#include "core/io/packed_data_container.h"
#include "core/io/packet_peer.h"
#include "core/io/packet_peer_dtls.h"
@@ -151,6 +152,7 @@ void register_core_types() {
GDREGISTER_CLASS(RefCounted);
GDREGISTER_CLASS(WeakRef);
GDREGISTER_CLASS(Resource);
+ GDREGISTER_VIRTUAL_CLASS(MissingResource);
GDREGISTER_CLASS(Image);
GDREGISTER_CLASS(Shortcut);
diff --git a/doc/classes/MissingNode.xml b/doc/classes/MissingNode.xml
new file mode 100644
index 0000000000..b5aa02cfd6
--- /dev/null
+++ b/doc/classes/MissingNode.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="MissingNode" inherits="Node" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ This is an internal editor class intended for keeping data of nodes of unknown type.
+ </brief_description>
+ <description>
+ This is an internal editor class intended for keeping data of nodes of unknown type (most likely this type was supplied by an extension that is no longer loaded). It can´t be manually instantiated or placed in the scene. Ignore it if you don't know what it is.
+ </description>
+ <tutorials>
+ </tutorials>
+ <members>
+ <member name="original_class" type="String" setter="set_original_class" getter="get_original_class">
+ </member>
+ <member name="recording_properties" type="bool" setter="set_recording_properties" getter="is_recording_properties">
+ </member>
+ </members>
+</class>
diff --git a/doc/classes/MissingResource.xml b/doc/classes/MissingResource.xml
new file mode 100644
index 0000000000..eede6350d8
--- /dev/null
+++ b/doc/classes/MissingResource.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="MissingResource" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ This is an internal editor class intended for keeping data of resources of unknown type.
+ </brief_description>
+ <description>
+ This is an internal editor class intended for keeping data of resources of unknown type (most likely this type was supplied by an extension that is no longer loaded). It can´t be manually instantiated or placed in the scene. Ignore it if you don't know what it is.
+ </description>
+ <tutorials>
+ </tutorials>
+ <members>
+ <member name="original_class" type="String" setter="set_original_class" getter="get_original_class">
+ </member>
+ <member name="recording_properties" type="bool" setter="set_recording_properties" getter="is_recording_properties">
+ </member>
+ </members>
+</class>
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index f4caa29a47..2830282d8c 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -5890,6 +5890,7 @@ EditorNode::EditorNode() {
SceneState::set_disable_placeholders(true);
ResourceLoader::clear_translation_remaps(); // Using no remaps if in editor.
ResourceLoader::clear_path_remaps();
+ ResourceLoader::set_create_missing_resources_if_class_unavailable(true);
Input *id = Input::get_singleton();
diff --git a/editor/icons/MissingNode.svg b/editor/icons/MissingNode.svg
new file mode 100644
index 0000000000..582b244614
--- /dev/null
+++ b/editor/icons/MissingNode.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m2.9902 1.9902a1.0001 1.0001 0 0 0 -.69727 1.7168l4.293 4.293-4.293 4.293a1.0001 1.0001 0 1 0 1.4141 1.4141l4.293-4.293 4.293 4.293a1.0001 1.0001 0 1 0 1.4141-1.4141l-4.293-4.293 4.293-4.293a1.0001 1.0001 0 0 0 -.72656-1.7148 1.0001 1.0001 0 0 0 -.6875.30078l-4.293 4.293-4.293-4.293a1.0001 1.0001 0 0 0 -.7168-.30273z" fill="#ff5f5f" fill-rule="evenodd"/></svg>
diff --git a/editor/icons/MissingResource.svg b/editor/icons/MissingResource.svg
new file mode 100644
index 0000000000..582b244614
--- /dev/null
+++ b/editor/icons/MissingResource.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m2.9902 1.9902a1.0001 1.0001 0 0 0 -.69727 1.7168l4.293 4.293-4.293 4.293a1.0001 1.0001 0 1 0 1.4141 1.4141l4.293-4.293 4.293 4.293a1.0001 1.0001 0 1 0 1.4141-1.4141l-4.293-4.293 4.293-4.293a1.0001 1.0001 0 0 0 -.72656-1.7148 1.0001 1.0001 0 0 0 -.6875.30078l-4.293 4.293-4.293-4.293a1.0001 1.0001 0 0 0 -.7168-.30273z" fill="#ff5f5f" fill-rule="evenodd"/></svg>
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 6c0192cf44..db78a68cc9 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))) {