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 | |