| 1 | /**************************************************************************/ |
| 2 | /* property_utils.cpp */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #include "property_utils.h" |
| 32 | |
| 33 | #include "core/config/engine.h" |
| 34 | #include "core/object/script_language.h" |
| 35 | #include "core/templates/local_vector.h" |
| 36 | #include "scene/resources/packed_scene.h" |
| 37 | |
| 38 | #ifdef TOOLS_ENABLED |
| 39 | #include "editor/editor_node.h" |
| 40 | #endif // TOOLS_ENABLED |
| 41 | |
| 42 | bool PropertyUtils::is_property_value_different(const Variant &p_a, const Variant &p_b) { |
| 43 | if (p_a.get_type() == Variant::FLOAT && p_b.get_type() == Variant::FLOAT) { |
| 44 | //this must be done because, as some scenes save as text, there might be a tiny difference in floats due to numerical error |
| 45 | return !Math::is_equal_approx((float)p_a, (float)p_b); |
| 46 | } else { |
| 47 | // For our purposes, treating null object as NIL is the right thing to do |
| 48 | const Variant &a = p_a.get_type() == Variant::OBJECT && (Object *)p_a == nullptr ? Variant() : p_a; |
| 49 | const Variant &b = p_b.get_type() == Variant::OBJECT && (Object *)p_b == nullptr ? Variant() : p_b; |
| 50 | return a != b; |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | Variant PropertyUtils::get_property_default_value(const Object *p_object, const StringName &p_property, bool *r_is_valid, const Vector<SceneState::PackState> *p_states_stack_cache, bool p_update_exports, const Node *p_owner, bool *r_is_class_default) { |
| 55 | // This function obeys the way property values are set when an object is instantiated, |
| 56 | // which is the following (the latter wins): |
| 57 | // 1. Default value from builtin class |
| 58 | // 2. Default value from script exported variable (from the topmost script) |
| 59 | // 3. Value overrides from the instantiation/inheritance stack |
| 60 | |
| 61 | if (r_is_class_default) { |
| 62 | *r_is_class_default = false; |
| 63 | } |
| 64 | if (r_is_valid) { |
| 65 | *r_is_valid = false; |
| 66 | } |
| 67 | |
| 68 | Ref<Script> topmost_script; |
| 69 | |
| 70 | if (const Node *node = Object::cast_to<Node>(p_object)) { |
| 71 | // Check inheritance/instantiation ancestors |
| 72 | const Vector<SceneState::PackState> &states_stack = p_states_stack_cache ? *p_states_stack_cache : PropertyUtils::get_node_states_stack(node, p_owner); |
| 73 | for (int i = 0; i < states_stack.size(); ++i) { |
| 74 | const SceneState::PackState &ia = states_stack[i]; |
| 75 | bool found = false; |
| 76 | Variant value_in_ancestor = ia.state->get_property_value(ia.node, p_property, found); |
| 77 | if (found) { |
| 78 | if (r_is_valid) { |
| 79 | *r_is_valid = true; |
| 80 | } |
| 81 | return value_in_ancestor; |
| 82 | } |
| 83 | // Save script for later |
| 84 | bool has_script = false; |
| 85 | Variant script = ia.state->get_property_value(ia.node, SNAME("script" ), has_script); |
| 86 | if (has_script) { |
| 87 | Ref<Script> scr = script; |
| 88 | if (scr.is_valid()) { |
| 89 | topmost_script = scr; |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | // Let's see what default is set by the topmost script having a default, if any |
| 96 | if (topmost_script.is_null()) { |
| 97 | topmost_script = p_object->get_script(); |
| 98 | } |
| 99 | if (topmost_script.is_valid()) { |
| 100 | // Should be called in the editor only and not at runtime, |
| 101 | // otherwise it can cause problems because of missing instance state support |
| 102 | if (p_update_exports && Engine::get_singleton()->is_editor_hint()) { |
| 103 | topmost_script->update_exports(); |
| 104 | } |
| 105 | Variant default_value; |
| 106 | if (topmost_script->get_property_default_value(p_property, default_value)) { |
| 107 | if (r_is_valid) { |
| 108 | *r_is_valid = true; |
| 109 | } |
| 110 | return default_value; |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | // Fall back to the default from the native class |
| 115 | { |
| 116 | if (r_is_class_default) { |
| 117 | *r_is_class_default = true; |
| 118 | } |
| 119 | bool valid = false; |
| 120 | Variant value = ClassDB::class_get_default_property_value(p_object->get_class_name(), p_property, &valid); |
| 121 | if (valid) { |
| 122 | if (r_is_valid) { |
| 123 | *r_is_valid = true; |
| 124 | } |
| 125 | return value; |
| 126 | } else { |
| 127 | // Heuristically check if this is a synthetic property (whatever/0, whatever/1, etc.) |
| 128 | // because they are not in the class DB yet must have a default (null). |
| 129 | String prop_str = String(p_property); |
| 130 | int p = prop_str.rfind("/" ); |
| 131 | if (p != -1 && p < prop_str.length() - 1) { |
| 132 | bool all_digits = true; |
| 133 | for (int i = p + 1; i < prop_str.length(); i++) { |
| 134 | if (!is_digit(prop_str[i])) { |
| 135 | all_digits = false; |
| 136 | break; |
| 137 | } |
| 138 | } |
| 139 | if (r_is_valid) { |
| 140 | *r_is_valid = all_digits; |
| 141 | } |
| 142 | } |
| 143 | return Variant(); |
| 144 | } |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | // Like SceneState::PackState, but using a raw pointer to avoid the cost of |
| 149 | // updating the reference count during the internal work of the functions below |
| 150 | namespace { |
| 151 | struct _FastPackState { |
| 152 | SceneState *state = nullptr; |
| 153 | int node = -1; |
| 154 | }; |
| 155 | } // namespace |
| 156 | |
| 157 | static bool _collect_inheritance_chain(const Ref<SceneState> &p_state, const NodePath &p_path, LocalVector<_FastPackState> &r_states_stack) { |
| 158 | bool found = false; |
| 159 | |
| 160 | LocalVector<_FastPackState> inheritance_states; |
| 161 | |
| 162 | Ref<SceneState> state = p_state; |
| 163 | while (state.is_valid()) { |
| 164 | int node = state->find_node_by_path(p_path); |
| 165 | if (node >= 0) { |
| 166 | // This one has state for this node |
| 167 | inheritance_states.push_back({ state.ptr(), node }); |
| 168 | found = true; |
| 169 | } |
| 170 | state = state->get_base_scene_state(); |
| 171 | } |
| 172 | |
| 173 | if (inheritance_states.size() > 0) { |
| 174 | for (int i = inheritance_states.size() - 1; i >= 0; --i) { |
| 175 | r_states_stack.push_back(inheritance_states[i]); |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | return found; |
| 180 | } |
| 181 | |
| 182 | Vector<SceneState::PackState> PropertyUtils::get_node_states_stack(const Node *p_node, const Node *p_owner, bool *r_instantiated_by_owner) { |
| 183 | if (r_instantiated_by_owner) { |
| 184 | *r_instantiated_by_owner = true; |
| 185 | } |
| 186 | |
| 187 | LocalVector<_FastPackState> states_stack; |
| 188 | { |
| 189 | const Node *owner = p_owner; |
| 190 | #ifdef TOOLS_ENABLED |
| 191 | if (!p_owner && Engine::get_singleton()->is_editor_hint()) { |
| 192 | owner = EditorNode::get_singleton()->get_edited_scene(); |
| 193 | } |
| 194 | #endif |
| 195 | |
| 196 | const Node *n = p_node; |
| 197 | while (n) { |
| 198 | if (n == owner) { |
| 199 | const Ref<SceneState> &state = n->get_scene_inherited_state(); |
| 200 | if (_collect_inheritance_chain(state, n->get_path_to(p_node), states_stack)) { |
| 201 | if (r_instantiated_by_owner) { |
| 202 | *r_instantiated_by_owner = false; |
| 203 | } |
| 204 | } |
| 205 | break; |
| 206 | } else if (!n->get_scene_file_path().is_empty()) { |
| 207 | const Ref<SceneState> &state = n->get_scene_instance_state(); |
| 208 | _collect_inheritance_chain(state, n->get_path_to(p_node), states_stack); |
| 209 | } |
| 210 | n = n->get_owner(); |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | // Convert to the proper type for returning, inverting the vector on the go |
| 215 | // (it was more convenient to fill the vector in reverse order) |
| 216 | Vector<SceneState::PackState> states_stack_ret; |
| 217 | { |
| 218 | states_stack_ret.resize(states_stack.size()); |
| 219 | _FastPackState *ps = states_stack.ptr(); |
| 220 | if (states_stack.size() > 0) { |
| 221 | for (int i = states_stack.size() - 1; i >= 0; --i) { |
| 222 | states_stack_ret.write[i].state.reference_ptr(ps->state); |
| 223 | states_stack_ret.write[i].node = ps->node; |
| 224 | ++ps; |
| 225 | } |
| 226 | } |
| 227 | } |
| 228 | return states_stack_ret; |
| 229 | } |
| 230 | |