Fix arrays and dictionaries containing nodes losing their values when saved with an out-of-date .NET project

This commit is contained in:
Juan Pablo Arce 2024-10-05 22:03:41 -03:00
parent db66bd35af
commit aa90e91d3b
5 changed files with 136 additions and 26 deletions

View File

@ -0,0 +1,37 @@
/**************************************************************************/
/* metadata_exclusion.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "metadata_exclusion.h"
#include "core/io/missing_resource.h"
bool is_meta_property_excluded_from_serialization(const String &p_property) {
return p_property == META_PROPERTY_UNRESOLVED_NODE_PATH_PROPERTIES || p_property == META_PROPERTY_MISSING_RESOURCES;
}

View File

@ -0,0 +1,41 @@
/**************************************************************************/
/* metadata_exclusion.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 METADATA_EXCLUSION_H
#define METADATA_EXCLUSION_H
class String;
#define META_PROPERTY_UNRESOLVED_NODE_PATH_PROPERTIES "metadata/_unresolved_node_path_properties"
#define META_UNRESOLVED_NODE_PATH_PROPERTIES "_unresolved_node_path_properties"
extern bool is_meta_property_excluded_from_serialization(const String &p_property);
#endif // METADATA_EXCLUSION_H

View File

@ -35,6 +35,7 @@
#include "core/io/file_access_compressed.h"
#include "core/io/image.h"
#include "core/io/marshalls.h"
#include "core/io/metadata_exclusion.h"
#include "core/io/missing_resource.h"
#include "core/object/script_language.h"
#include "core/version.h"
@ -2234,7 +2235,7 @@ 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) {
if (is_meta_property_excluded_from_serialization(F.name)) {
continue;
}

View File

@ -32,6 +32,7 @@
#include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "core/io/metadata_exclusion.h"
#include "core/io/missing_resource.h"
#include "core/io/resource_loader.h"
#include "core/templates/local_vector.h"
@ -124,6 +125,18 @@ Ref<Resource> SceneState::get_remap_resource(const Ref<Resource> &p_resource, Ha
return remap_resource;
}
// A node-containing property can be impossible to correct if we're currently dealing with an out-of-date .NET project.
// Store the node paths as-is and add metadata to ensure that the correct type will be assigned when the project is rebuilt.
static void _dnp_add_unresolved_node_property(Node *p_dnp_node, const StringName &p_dnp_property) {
if (!p_dnp_node->has_meta(META_UNRESOLVED_NODE_PATH_PROPERTIES)) {
Array new_array_of_unresolved_properties;
new_array_of_unresolved_properties.set_typed(Variant::STRING_NAME, StringName(), Variant());
p_dnp_node->set_meta(META_UNRESOLVED_NODE_PATH_PROPERTIES, new_array_of_unresolved_properties);
}
Array unresolved_node_path_properties = p_dnp_node->get_meta(META_UNRESOLVED_NODE_PATH_PROPERTIES);
unresolved_node_path_properties.append(p_dnp_property);
}
Node *SceneState::instantiate(GenEditState p_edit_state) const {
// Nodes where instantiation failed (because something is missing.)
List<Node *> stray_instances;
@ -527,37 +540,48 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
bool valid;
Array array = dnp.base->get(dnp.property, &valid);
ERR_CONTINUE_EDMSG(!valid, vformat("Failed to get property '%s' from node '%s'.", dnp.property, dnp.base->get_name()));
array = array.duplicate();
array.resize(paths.size());
for (int i = 0; i < array.size(); i++) {
array.set(i, dnp.base->get_node_or_null(paths[i]));
if (valid) {
array = array.duplicate();
array.resize(paths.size());
for (int i = 0; i < array.size(); i++) {
array.set(i, dnp.base->get_node_or_null(paths[i]));
}
} else {
array = paths.duplicate();
_dnp_add_unresolved_node_property(dnp.base, dnp.property);
}
dnp.base->set(dnp.property, array);
} else if (dnp.value.get_type() == Variant::DICTIONARY) {
Dictionary paths = dnp.value;
bool convert_key = false;
bool convert_value = false;
bool valid;
Dictionary dict = dnp.base->get(dnp.property, &valid);
ERR_CONTINUE_EDMSG(!valid, vformat("Failed to get property '%s' from node '%s'.", dnp.property, dnp.base->get_name()));
dict = dict.duplicate();
bool convert_key = dict.get_typed_key_builtin() == Variant::OBJECT &&
ClassDB::is_parent_class(dict.get_typed_key_class_name(), "Node");
bool convert_value = dict.get_typed_value_builtin() == Variant::OBJECT &&
ClassDB::is_parent_class(dict.get_typed_value_class_name(), "Node");
for (int i = 0; i < paths.size(); i++) {
Variant key = paths.get_key_at_index(i);
if (convert_key) {
key = dnp.base->get_node_or_null(key);
if (valid) {
dict = dict.duplicate();
convert_key = dict.get_typed_key_builtin() == Variant::OBJECT &&
ClassDB::is_parent_class(dict.get_typed_key_class_name(), "Node");
convert_value = dict.get_typed_value_builtin() == Variant::OBJECT &&
ClassDB::is_parent_class(dict.get_typed_value_class_name(), "Node");
for (int i = 0; i < paths.size(); i++) {
Variant key = paths.get_key_at_index(i);
if (convert_key) {
key = dnp.base->get_node_or_null(key);
}
Variant value = paths.get_value_at_index(i);
if (convert_value) {
value = dnp.base->get_node_or_null(value);
}
dict[key] = value;
}
Variant value = paths.get_value_at_index(i);
if (convert_value) {
value = dnp.base->get_node_or_null(value);
}
dict[key] = value;
} else {
dict = paths.duplicate();
_dnp_add_unresolved_node_property(dnp.base, dnp.property);
}
dnp.base->set(dnp.property, dict);
} else {
dnp.base->set(dnp.property, dnp.base->get_node_or_null(dnp.value));
@ -784,13 +808,17 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
Array pinned_props = _sanitize_node_pinned_properties(p_node);
Dictionary missing_resource_properties = p_node->get_meta(META_MISSING_RESOURCES, Dictionary());
Array unresolved_node_path_properties =
p_node->has_meta(META_UNRESOLVED_NODE_PATH_PROPERTIES)
? (Array)p_node->get_meta(META_UNRESOLVED_NODE_PATH_PROPERTIES)
: Array();
for (const PropertyInfo &E : plist) {
if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
continue;
}
if (E.name == META_PROPERTY_MISSING_RESOURCES) {
if (is_meta_property_excluded_from_serialization(E.name)) {
continue; // Ignore this property when packing.
}
@ -805,7 +833,9 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
Variant value = p_node->get(name);
bool use_deferred_node_path_bit = false;
if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE) {
if (unresolved_node_path_properties.find(name) != -1) {
use_deferred_node_path_bit = true;
} else if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE) {
if (value.get_type() == Variant::OBJECT) {
if (Node *n = Object::cast_to<Node>(value)) {
value = p_node->get_path_to(n);

View File

@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
#include "core/io/metadata_exclusion.h"
#include "core/io/missing_resource.h"
#include "core/object/script_language.h"
@ -1908,7 +1909,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
if (skip_editor && PE->get().name.begins_with("__editor")) {
continue;
}
if (PE->get().name == META_PROPERTY_MISSING_RESOURCES) {
if (is_meta_property_excluded_from_serialization(PE->get().name)) {
continue;
}