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
42bool 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
54Variant 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
150namespace {
151struct _FastPackState {
152 SceneState *state = nullptr;
153 int node = -1;
154};
155} // namespace
156
157static 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
182Vector<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