diff options
author | Pedro J. Estébanez <pedrojrulez@gmail.com> | 2021-09-14 13:05:54 +0200 |
---|---|---|
committer | Pedro J. Estébanez <pedrojrulez@gmail.com> | 2021-11-07 15:27:47 +0100 |
commit | 1806ec7c14a8f038c0ff33a88d19087225c8d7ab (patch) | |
tree | 3250e119eea32a2d33b76276e63712831b1c6620 /scene | |
parent | c7fefe50daebe2f3ae568baaa888ddb3cddfe5e1 (diff) | |
download | redot-engine-1806ec7c14a8f038c0ff33a88d19087225c8d7ab.tar.gz |
Unify determination of default property values
Diffstat (limited to 'scene')
-rw-r--r-- | scene/property_utils.cpp | 185 | ||||
-rw-r--r-- | scene/property_utils.h | 51 | ||||
-rw-r--r-- | scene/resources/packed_scene.cpp | 179 | ||||
-rw-r--r-- | scene/resources/packed_scene.h | 14 |
4 files changed, 280 insertions, 149 deletions
diff --git a/scene/property_utils.cpp b/scene/property_utils.cpp new file mode 100644 index 0000000000..79821b8399 --- /dev/null +++ b/scene/property_utils.cpp @@ -0,0 +1,185 @@ +/*************************************************************************/ +/* property_utils.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 "property_utils.h" + +#include "core/config/engine.h" +#include "core/templates/local_vector.h" +#include "editor/editor_node.h" +#include "scene/resources/packed_scene.h" + +bool PropertyUtils::is_property_value_different(const Variant &p_a, const Variant &p_b) { + if (p_a.get_type() == Variant::FLOAT && p_b.get_type() == Variant::FLOAT) { + //this must be done because, as some scenes save as text, there might be a tiny difference in floats due to numerical error + return !Math::is_equal_approx((float)p_a, (float)p_b); + } else { + // For our purposes, treating null object as NIL is the right thing to do + const Variant &a = p_a.get_type() == Variant::OBJECT && (Object *)p_a == nullptr ? Variant() : p_a; + const Variant &b = p_b.get_type() == Variant::OBJECT && (Object *)p_b == nullptr ? Variant() : p_b; + return a != b; + } +} + +Variant PropertyUtils::get_property_default_value(const Object *p_object, const StringName &p_property, const Vector<SceneState::PackState> *p_states_stack_cache, bool p_update_exports, const Node *p_owner, bool *r_is_class_default) { + // This function obeys the way property values are set when an object is instantiated, + // which is the following (the latter wins): + // 1. Default value from builtin class + // 2. Default value from script exported variable (from the topmost script) + // 3. Value overrides from the instantiation/inheritance stack + + if (r_is_class_default) { + *r_is_class_default = false; + } + + Ref<Script> topmost_script; + + if (const Node *node = Object::cast_to<Node>(p_object)) { + // Check inheritance/instantiation ancestors + const Vector<SceneState::PackState> &states_stack = p_states_stack_cache ? *p_states_stack_cache : PropertyUtils::get_node_states_stack(node, p_owner); + for (int i = 0; i < states_stack.size(); ++i) { + const SceneState::PackState &ia = states_stack[i]; + bool found = false; + Variant value_in_ancestor = ia.state->get_property_value(ia.node, p_property, found); + if (found) { + return value_in_ancestor; + } + // Save script for later + bool has_script = false; + Variant script = ia.state->get_property_value(ia.node, SNAME("script"), has_script); + if (has_script) { + Ref<Script> scr = script; + if (scr.is_valid()) { + topmost_script = scr; + } + } + } + } + + // Let's see what default is set by the topmost script having a default, if any + if (topmost_script.is_null()) { + topmost_script = p_object->get_script(); + } + if (topmost_script.is_valid()) { + // Should be called in the editor only and not at runtime, + // otherwise it can cause problems because of missing instance state support + if (p_update_exports && Engine::get_singleton()->is_editor_hint()) { + topmost_script->update_exports(); + } + Variant default_value; + if (topmost_script->get_property_default_value(p_property, default_value)) { + return default_value; + } + } + + // Fall back to the default from the native class + if (r_is_class_default) { + *r_is_class_default = true; + } + return ClassDB::class_get_default_property_value(p_object->get_class_name(), p_property); +} + +// Like SceneState::PackState, but using a raw pointer to avoid the cost of +// updating the reference count during the internal work of the functions below +namespace { +struct _FastPackState { + SceneState *state = nullptr; + int node = -1; +}; +} // namespace + +static bool _collect_inheritance_chain(const Ref<SceneState> &p_state, const NodePath &p_path, LocalVector<_FastPackState> &r_states_stack) { + bool found = false; + + LocalVector<_FastPackState> inheritance_states; + + Ref<SceneState> state = p_state; + while (state.is_valid()) { + int node = state->find_node_by_path(p_path); + if (node >= 0) { + // This one has state for this node + inheritance_states.push_back({ state.ptr(), node }); + found = true; + } + state = state->get_base_scene_state(); + } + + for (int i = inheritance_states.size() - 1; i >= 0; --i) { + r_states_stack.push_back(inheritance_states[i]); + } + + return found; +} + +Vector<SceneState::PackState> PropertyUtils::get_node_states_stack(const Node *p_node, const Node *p_owner, bool *r_instantiated_by_owner) { + if (r_instantiated_by_owner) { + *r_instantiated_by_owner = true; + } + + LocalVector<_FastPackState> states_stack; + { + const Node *owner = p_owner; +#ifdef TOOLS_ENABLED + if (!p_owner && Engine::get_singleton()->is_editor_hint()) { + owner = EditorNode::get_singleton()->get_edited_scene(); + } +#endif + + const Node *n = p_node; + while (n) { + if (n == owner) { + const Ref<SceneState> &state = n->get_scene_inherited_state(); + if (_collect_inheritance_chain(state, n->get_path_to(p_node), states_stack)) { + if (r_instantiated_by_owner) { + *r_instantiated_by_owner = false; + } + } + break; + } else if (n->get_scene_file_path() != String()) { + const Ref<SceneState> &state = n->get_scene_instance_state(); + _collect_inheritance_chain(state, n->get_path_to(p_node), states_stack); + } + n = n->get_owner(); + } + } + + // Convert to the proper type for returning, inverting the vector on the go + // (it was more convenient to fill the vector in reverse order) + Vector<SceneState::PackState> states_stack_ret; + { + states_stack_ret.resize(states_stack.size()); + _FastPackState *ps = states_stack.ptr(); + for (int i = states_stack.size() - 1; i >= 0; --i) { + states_stack_ret.write[i].state.reference_ptr(ps->state); + states_stack_ret.write[i].node = ps->node; + ++ps; + } + } + return states_stack_ret; +} diff --git a/scene/property_utils.h b/scene/property_utils.h new file mode 100644 index 0000000000..fde9163548 --- /dev/null +++ b/scene/property_utils.h @@ -0,0 +1,51 @@ +/*************************************************************************/ +/* property_utils.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 PROPERTY_UTILS_H +#define PROPERTY_UTILS_H + +#include "scene/main/node.h" +#include "scene/resources/packed_scene.h" + +class PropertyUtils { +public: + static bool is_property_value_different(const Variant &p_a, const Variant &p_b); + // Gets the most pure default value, the one that would be set when the node has just been instantiated + static Variant get_property_default_value(const Object *p_object, const StringName &p_property, const Vector<SceneState::PackState> *p_states_stack_cache = nullptr, bool p_update_exports = false, const Node *p_owner = nullptr, bool *r_is_class_default = nullptr); + + // Gets the instance/inheritance states of this node, in order of precedence, + // that is, from the topmost (the most able to override values) to the lowermost + // (Note that in nested instancing the one with the greatest precedence is the furthest + // in the tree, since every owner found while traversing towards the root gets a chance + // to override property values.) + static Vector<SceneState::PackState> get_node_states_stack(const Node *p_node, const Node *p_owner = nullptr, bool *r_instantiated_by_owner = nullptr); +}; + +#endif // PROPERTY_UTILS_H diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index 978be2d46e..6eeeccc081 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -34,10 +34,12 @@ #include "core/config/project_settings.h" #include "core/core_string_names.h" #include "core/io/resource_loader.h" +#include "editor/editor_inspector.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/property_utils.h" #define PACKED_SCENE_VERSION 2 @@ -415,61 +417,22 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map // with the instance states, we can query for identical properties/groups // and only save what has changed - List<PackState> pack_state_stack; - - bool instantiated_by_owner = true; - - { - Node *n = p_node; - - while (n) { - if (n == p_owner) { - Ref<SceneState> state = n->get_scene_inherited_state(); - if (state.is_valid()) { - int node = state->find_node_by_path(n->get_path_to(p_node)); - if (node >= 0) { - //this one has state for this node, save - PackState ps; - ps.node = node; - ps.state = state; - pack_state_stack.push_back(ps); - instantiated_by_owner = false; - } - } - - if (p_node->get_scene_file_path() != String() && p_node->get_owner() == p_owner && instantiated_by_owner) { - if (p_node->get_scene_instance_load_placeholder()) { - //it's a placeholder, use the placeholder path - nd.instance = _vm_get_variant(p_node->get_scene_file_path(), variant_map); - nd.instance |= FLAG_INSTANCE_IS_PLACEHOLDER; - } else { - //must instance ourselves - Ref<PackedScene> instance = ResourceLoader::load(p_node->get_scene_file_path()); - if (!instance.is_valid()) { - return ERR_CANT_OPEN; - } + bool instantiated_by_owner = false; + Vector<SceneState::PackState> states_stack = PropertyUtils::get_node_states_stack(p_node, p_owner, &instantiated_by_owner); - nd.instance = _vm_get_variant(instance, variant_map); - } - } - n = nullptr; - } else { - if (n->get_scene_file_path() != String()) { - //is an instance - Ref<SceneState> state = n->get_scene_instance_state(); - if (state.is_valid()) { - int node = state->find_node_by_path(n->get_path_to(p_node)); - if (node >= 0) { - //this one has state for this node, save - PackState ps; - ps.node = node; - ps.state = state; - pack_state_stack.push_back(ps); - } - } - } - n = n->get_owner(); + if (p_node->get_scene_file_path() != String() && p_node->get_owner() == p_owner && instantiated_by_owner) { + if (p_node->get_scene_instance_load_placeholder()) { + //it's a placeholder, use the placeholder path + nd.instance = _vm_get_variant(p_node->get_scene_file_path(), variant_map); + nd.instance |= FLAG_INSTANCE_IS_PLACEHOLDER; + } else { + //must instance ourselves + Ref<PackedScene> instance = ResourceLoader::load(p_node->get_scene_file_path()); + if (!instance.is_valid()) { + return ERR_CANT_OPEN; } + + nd.instance = _vm_get_variant(instance, variant_map); } } @@ -478,90 +441,23 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map List<PropertyInfo> plist; p_node->get_property_list(&plist); - StringName type = p_node->get_class(); - - Ref<Script> script = p_node->get_script(); - if (Engine::get_singleton()->is_editor_hint() && script.is_valid()) { - // Should be called in the editor only and not at runtime, - // otherwise it can cause problems because of missing instance state support. - script->update_exports(); - } for (const PropertyInfo &E : plist) { if (!(E.usage & PROPERTY_USAGE_STORAGE)) { continue; } - String name = E.name; - Variant value = p_node->get(E.name); - - bool isdefault = false; - Variant default_value = ClassDB::class_get_default_property_value(type, name); - - if (default_value.get_type() != Variant::NIL) { - isdefault = bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value)); + // If instance or inheriting, not saving if property requested so, or it's meta + if (states_stack.size() && ((E.usage & PROPERTY_USAGE_NO_INSTANCE_STATE) || E.name == "__meta__")) { + continue; } - if (!isdefault && script.is_valid() && script->get_property_default_value(name, default_value)) { - isdefault = bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value)); - } - // the version above makes more sense, because it does not rely on placeholder or usage flag - // in the script, just the default value function. - // if (E.usage & PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE) { - // isdefault = true; //is script default value - // } - - if (pack_state_stack.size()) { - // we are on part of an instantiated subscene - // or part of instantiated scene. - // only save what has been changed - // only save changed properties in instance - - if ((E.usage & PROPERTY_USAGE_NO_INSTANCE_STATE) || E.name == "__meta__") { - //property has requested that no instance state is saved, sorry - //also, meta won't be overridden or saved - continue; - } - - bool exists = false; - Variant original; - - for (List<PackState>::Element *F = pack_state_stack.back(); F; F = F->prev()) { - //check all levels of pack to see if the property exists somewhere - const PackState &ps = F->get(); - - original = ps.state->get_property_value(ps.node, E.name, exists); - if (exists) { - break; - } - } - - if (exists) { - //check if already exists and did not change - if (value.get_type() == Variant::FLOAT && original.get_type() == Variant::FLOAT) { - //this must be done because, as some scenes save as text, there might be a tiny difference in floats due to numerical error - float a = value; - float b = original; - - if (Math::is_equal_approx(a, b)) { - continue; - } - } else if (bool(Variant::evaluate(Variant::OP_EQUAL, value, original))) { - continue; - } - } - - if (!exists && isdefault) { - //does not exist in original node, but it's the default value - //so safe to skip too. - continue; - } + String name = E.name; + Variant value = p_node->get(name); - } else { - if (isdefault) { - //it's the default value, no point in saving it - continue; - } + Variant default_value = PropertyUtils::get_property_default_value(p_node, name, &states_stack, true); + if (!PropertyUtils::is_property_value_different(value, default_value)) { + continue; } NodeData::Property prop; @@ -585,10 +481,9 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map */ bool skip = false; - for (const PackState &F : pack_state_stack) { + for (const SceneState::PackState &ia : states_stack) { //check all levels of pack to see if the group was added somewhere - const PackState &ps = F; - if (ps.state->is_node_in_group(ps.node, gi.name)) { + if (ia.state->is_node_in_group(ia.node, gi.name)) { skip = true; break; } @@ -618,7 +513,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map // Save the right type. If this node was created by an instance // then flag that the node should not be created but reused - if (pack_state_stack.is_empty() && !is_editable_instance) { + 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); } else { @@ -635,7 +530,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map bool save_node = nd.properties.size() || nd.groups.size(); // some local properties or groups exist save_node = save_node || p_node == p_owner; // owner is always saved - save_node = save_node || (p_node->get_owner() == p_owner && instantiated_by_owner); //part of scene and not instantiated + save_node = save_node || (p_node->get_owner() == p_owner && instantiated_by_owner); //part of scene and not instanced int idx = nodes.size(); int parent_node = NO_PARENT_SAVED; @@ -932,7 +827,7 @@ void SceneState::clear() { base_scene_idx = -1; } -Ref<SceneState> SceneState::_get_base_scene_state() const { +Ref<SceneState> SceneState::get_base_scene_state() const { if (base_scene_idx >= 0) { Ref<PackedScene> ps = variants[base_scene_idx]; if (ps.is_valid()) { @@ -947,8 +842,8 @@ int SceneState::find_node_by_path(const NodePath &p_node) const { ERR_FAIL_COND_V_MSG(node_path_cache.size() == 0, -1, "This operation requires the node cache to have been built."); if (!node_path_cache.has(p_node)) { - if (_get_base_scene_state().is_valid()) { - int idx = _get_base_scene_state()->find_node_by_path(p_node); + if (get_base_scene_state().is_valid()) { + int idx = get_base_scene_state()->find_node_by_path(p_node); if (idx != -1) { int rkey = _find_base_scene_node_remap_key(idx); if (rkey == -1) { @@ -963,11 +858,11 @@ int SceneState::find_node_by_path(const NodePath &p_node) const { int nid = node_path_cache[p_node]; - if (_get_base_scene_state().is_valid() && !base_scene_node_remap.has(nid)) { + if (get_base_scene_state().is_valid() && !base_scene_node_remap.has(nid)) { //for nodes that _do_ exist in current scene, still try to look for //the node in the instantiated scene, as a property may be missing //from the local one - int idx = _get_base_scene_state()->find_node_by_path(p_node); + int idx = get_base_scene_state()->find_node_by_path(p_node); if (idx != -1) { base_scene_node_remap[nid] = idx; } @@ -1007,7 +902,7 @@ Variant SceneState::get_property_value(int p_node, const StringName &p_property, //property not found, try on instance if (base_scene_node_remap.has(p_node)) { - return _get_base_scene_state()->get_property_value(base_scene_node_remap[p_node], p_property, found); + return get_base_scene_state()->get_property_value(base_scene_node_remap[p_node], p_property, found); } return Variant(); @@ -1026,7 +921,7 @@ bool SceneState::is_node_in_group(int p_node, const StringName &p_group) const { } if (base_scene_node_remap.has(p_node)) { - return _get_base_scene_state()->is_node_in_group(base_scene_node_remap[p_node], p_group); + return get_base_scene_state()->is_node_in_group(base_scene_node_remap[p_node], p_group); } return false; @@ -1065,7 +960,7 @@ bool SceneState::is_connection(int p_node, const StringName &p_signal, int p_to_ } if (base_scene_node_remap.has(p_node) && base_scene_node_remap.has(p_to_node)) { - return _get_base_scene_state()->is_connection(base_scene_node_remap[p_node], p_signal, base_scene_node_remap[p_to_node], p_to_method); + return get_base_scene_state()->is_connection(base_scene_node_remap[p_node], p_signal, base_scene_node_remap[p_to_node], p_to_method); } return false; @@ -1488,7 +1383,7 @@ bool SceneState::has_connection(const NodePath &p_node_from, const StringName &p } } - ss = ss->_get_base_scene_state(); + ss = ss->get_base_scene_state(); } while (ss.is_valid()); return false; diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h index 55708f7914..462e6bb5d2 100644 --- a/scene/resources/packed_scene.h +++ b/scene/resources/packed_scene.h @@ -69,11 +69,6 @@ class SceneState : public RefCounted { Vector<int> groups; }; - struct PackState { - Ref<SceneState> state; - int node = -1; - }; - Vector<NodeData> nodes; struct ConnectionData { @@ -94,8 +89,6 @@ class SceneState : public RefCounted { uint64_t last_modified_time = 0; - _FORCE_INLINE_ Ref<SceneState> _get_base_scene_state() const; - static bool disable_placeholders; Vector<String> _get_node_groups(int p_idx) const; @@ -119,6 +112,11 @@ public: GEN_EDIT_STATE_MAIN, }; + struct PackState { + Ref<SceneState> state; + int node = -1; + }; + static void set_disable_placeholders(bool p_disable); int find_node_by_path(const NodePath &p_node) const; @@ -139,6 +137,8 @@ public: bool can_instantiate() const; Node *instantiate(GenEditState p_edit_state) const; + Ref<SceneState> get_base_scene_state() const; + //unbuild API int get_node_count() const; |