mirror of
https://github.com/godotengine/godot.git
synced 2025-12-08 06:09:55 +00:00
Merge pull request #60597 from reduz/missing-node-resource-placeholders
This commit is contained in:
commit
71e41eb395
17 changed files with 630 additions and 58 deletions
90
core/io/missing_resource.cpp
Normal file
90
core/io/missing_resource.cpp
Normal file
|
|
@ -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() {
|
||||
}
|
||||
63
core/io/missing_resource.h
Normal file
63
core/io/missing_resource.h
Normal file
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue