1 | /**************************************************************************/ |
2 | /* editor_debugger_inspector.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 "editor_debugger_inspector.h" |
32 | |
33 | #include "core/debugger/debugger_marshalls.h" |
34 | #include "core/io/marshalls.h" |
35 | #include "editor/editor_node.h" |
36 | #include "scene/debugger/scene_debugger.h" |
37 | |
38 | bool EditorDebuggerRemoteObject::_set(const StringName &p_name, const Variant &p_value) { |
39 | if (!prop_values.has(p_name) || String(p_name).begins_with("Constants/" )) { |
40 | return false; |
41 | } |
42 | |
43 | prop_values[p_name] = p_value; |
44 | emit_signal(SNAME("value_edited" ), remote_object_id, p_name, p_value); |
45 | return true; |
46 | } |
47 | |
48 | bool EditorDebuggerRemoteObject::_get(const StringName &p_name, Variant &r_ret) const { |
49 | if (!prop_values.has(p_name)) { |
50 | return false; |
51 | } |
52 | |
53 | r_ret = prop_values[p_name]; |
54 | return true; |
55 | } |
56 | |
57 | void EditorDebuggerRemoteObject::_get_property_list(List<PropertyInfo> *p_list) const { |
58 | p_list->clear(); // Sorry, no want category. |
59 | for (const PropertyInfo &prop : prop_list) { |
60 | if (prop.name == "script" ) { |
61 | // Skip the script property, it's always added by the non-virtual method. |
62 | continue; |
63 | } |
64 | |
65 | p_list->push_back(prop); |
66 | } |
67 | } |
68 | |
69 | String EditorDebuggerRemoteObject::get_title() { |
70 | if (remote_object_id.is_valid()) { |
71 | return vformat(TTR("Remote %s:" ), String(type_name)) + " " + itos(remote_object_id); |
72 | } else { |
73 | return "<null>" ; |
74 | } |
75 | } |
76 | |
77 | Variant EditorDebuggerRemoteObject::get_variant(const StringName &p_name) { |
78 | Variant var; |
79 | _get(p_name, var); |
80 | return var; |
81 | } |
82 | |
83 | void EditorDebuggerRemoteObject::_bind_methods() { |
84 | ClassDB::bind_method(D_METHOD("get_title" ), &EditorDebuggerRemoteObject::get_title); |
85 | ClassDB::bind_method(D_METHOD("get_variant" ), &EditorDebuggerRemoteObject::get_variant); |
86 | ClassDB::bind_method(D_METHOD("clear" ), &EditorDebuggerRemoteObject::clear); |
87 | ClassDB::bind_method(D_METHOD("get_remote_object_id" ), &EditorDebuggerRemoteObject::get_remote_object_id); |
88 | |
89 | ADD_SIGNAL(MethodInfo("value_edited" , PropertyInfo(Variant::INT, "object_id" ), PropertyInfo(Variant::STRING, "property" ), PropertyInfo("value" ))); |
90 | } |
91 | |
92 | EditorDebuggerInspector::EditorDebuggerInspector() { |
93 | variables = memnew(EditorDebuggerRemoteObject); |
94 | } |
95 | |
96 | EditorDebuggerInspector::~EditorDebuggerInspector() { |
97 | clear_cache(); |
98 | memdelete(variables); |
99 | } |
100 | |
101 | void EditorDebuggerInspector::_bind_methods() { |
102 | ADD_SIGNAL(MethodInfo("object_selected" , PropertyInfo(Variant::INT, "id" ))); |
103 | ADD_SIGNAL(MethodInfo("object_edited" , PropertyInfo(Variant::INT, "id" ), PropertyInfo(Variant::STRING, "property" ), PropertyInfo("value" ))); |
104 | ADD_SIGNAL(MethodInfo("object_property_updated" , PropertyInfo(Variant::INT, "id" ), PropertyInfo(Variant::STRING, "property" ))); |
105 | } |
106 | |
107 | void EditorDebuggerInspector::_notification(int p_what) { |
108 | switch (p_what) { |
109 | case NOTIFICATION_POSTINITIALIZE: { |
110 | connect("object_id_selected" , callable_mp(this, &EditorDebuggerInspector::_object_selected)); |
111 | } break; |
112 | |
113 | case NOTIFICATION_ENTER_TREE: { |
114 | edit(variables); |
115 | } break; |
116 | } |
117 | } |
118 | |
119 | void EditorDebuggerInspector::_object_edited(ObjectID p_id, const String &p_prop, const Variant &p_value) { |
120 | emit_signal(SNAME("object_edited" ), p_id, p_prop, p_value); |
121 | } |
122 | |
123 | void EditorDebuggerInspector::_object_selected(ObjectID p_object) { |
124 | emit_signal(SNAME("object_selected" ), p_object); |
125 | } |
126 | |
127 | ObjectID EditorDebuggerInspector::add_object(const Array &p_arr) { |
128 | EditorDebuggerRemoteObject *debug_obj = nullptr; |
129 | |
130 | SceneDebuggerObject obj; |
131 | obj.deserialize(p_arr); |
132 | ERR_FAIL_COND_V(obj.id.is_null(), ObjectID()); |
133 | |
134 | if (remote_objects.has(obj.id)) { |
135 | debug_obj = remote_objects[obj.id]; |
136 | } else { |
137 | debug_obj = memnew(EditorDebuggerRemoteObject); |
138 | debug_obj->remote_object_id = obj.id; |
139 | debug_obj->type_name = obj.class_name; |
140 | remote_objects[obj.id] = debug_obj; |
141 | debug_obj->connect("value_edited" , callable_mp(this, &EditorDebuggerInspector::_object_edited)); |
142 | } |
143 | |
144 | int old_prop_size = debug_obj->prop_list.size(); |
145 | |
146 | debug_obj->prop_list.clear(); |
147 | int new_props_added = 0; |
148 | HashSet<String> changed; |
149 | for (int i = 0; i < obj.properties.size(); i++) { |
150 | PropertyInfo &pinfo = obj.properties[i].first; |
151 | Variant &var = obj.properties[i].second; |
152 | |
153 | if (pinfo.type == Variant::OBJECT) { |
154 | if (var.get_type() == Variant::STRING) { |
155 | String path = var; |
156 | if (path.contains("::" )) { |
157 | // built-in resource |
158 | String base_path = path.get_slice("::" , 0); |
159 | Ref<Resource> dependency = ResourceLoader::load(base_path); |
160 | if (dependency.is_valid()) { |
161 | remote_dependencies.insert(dependency); |
162 | } |
163 | } |
164 | var = ResourceLoader::load(path); |
165 | |
166 | if (pinfo.hint_string == "Script" ) { |
167 | if (debug_obj->get_script() != var) { |
168 | debug_obj->set_script(Ref<RefCounted>()); |
169 | Ref<Script> scr(var); |
170 | if (!scr.is_null()) { |
171 | ScriptInstance *scr_instance = scr->placeholder_instance_create(debug_obj); |
172 | if (scr_instance) { |
173 | debug_obj->set_script_and_instance(var, scr_instance); |
174 | } |
175 | } |
176 | } |
177 | } |
178 | } |
179 | } |
180 | |
181 | //always add the property, since props may have been added or removed |
182 | debug_obj->prop_list.push_back(pinfo); |
183 | |
184 | if (!debug_obj->prop_values.has(pinfo.name)) { |
185 | new_props_added++; |
186 | debug_obj->prop_values[pinfo.name] = var; |
187 | } else { |
188 | if (bool(Variant::evaluate(Variant::OP_NOT_EQUAL, debug_obj->prop_values[pinfo.name], var))) { |
189 | debug_obj->prop_values[pinfo.name] = var; |
190 | changed.insert(pinfo.name); |
191 | } |
192 | } |
193 | } |
194 | |
195 | if (old_prop_size == debug_obj->prop_list.size() && new_props_added == 0) { |
196 | //only some may have changed, if so, then update those, if exist |
197 | for (const String &E : changed) { |
198 | emit_signal(SNAME("object_property_updated" ), debug_obj->remote_object_id, E); |
199 | } |
200 | } else { |
201 | //full update, because props were added or removed |
202 | debug_obj->update(); |
203 | } |
204 | return obj.id; |
205 | } |
206 | |
207 | void EditorDebuggerInspector::clear_cache() { |
208 | for (const KeyValue<ObjectID, EditorDebuggerRemoteObject *> &E : remote_objects) { |
209 | EditorNode *editor = EditorNode::get_singleton(); |
210 | if (editor->get_editor_selection_history()->get_current() == E.value->get_instance_id()) { |
211 | editor->push_item(nullptr); |
212 | } |
213 | memdelete(E.value); |
214 | } |
215 | remote_objects.clear(); |
216 | remote_dependencies.clear(); |
217 | } |
218 | |
219 | Object *EditorDebuggerInspector::get_object(ObjectID p_id) { |
220 | if (remote_objects.has(p_id)) { |
221 | return remote_objects[p_id]; |
222 | } |
223 | return nullptr; |
224 | } |
225 | |
226 | void EditorDebuggerInspector::add_stack_variable(const Array &p_array) { |
227 | DebuggerMarshalls::ScriptStackVariable var; |
228 | var.deserialize(p_array); |
229 | String n = var.name; |
230 | Variant v = var.value; |
231 | |
232 | PropertyHint h = PROPERTY_HINT_NONE; |
233 | String hs; |
234 | |
235 | if (var.var_type == Variant::OBJECT && v) { |
236 | v = Object::cast_to<EncodedObjectAsID>(v)->get_object_id(); |
237 | h = PROPERTY_HINT_OBJECT_ID; |
238 | hs = "Object" ; |
239 | } |
240 | String type; |
241 | switch (var.type) { |
242 | case 0: |
243 | type = "Locals/" ; |
244 | break; |
245 | case 1: |
246 | type = "Members/" ; |
247 | break; |
248 | case 2: |
249 | type = "Globals/" ; |
250 | break; |
251 | default: |
252 | type = "Unknown/" ; |
253 | } |
254 | |
255 | PropertyInfo pinfo; |
256 | pinfo.name = type + n; |
257 | pinfo.type = v.get_type(); |
258 | pinfo.hint = h; |
259 | pinfo.hint_string = hs; |
260 | |
261 | variables->prop_list.push_back(pinfo); |
262 | variables->prop_values[type + n] = v; |
263 | variables->update(); |
264 | edit(variables); |
265 | |
266 | // To prevent constantly resizing when using filtering. |
267 | int size_x = get_size().x; |
268 | if (size_x > get_custom_minimum_size().x) { |
269 | set_custom_minimum_size(Size2(size_x, 0)); |
270 | } |
271 | } |
272 | |
273 | void EditorDebuggerInspector::clear_stack_variables() { |
274 | variables->clear(); |
275 | variables->update(); |
276 | set_custom_minimum_size(Size2(0, 0)); |
277 | } |
278 | |
279 | String EditorDebuggerInspector::get_stack_variable(const String &p_var) { |
280 | for (KeyValue<StringName, Variant> &E : variables->prop_values) { |
281 | String v = E.key.operator String(); |
282 | if (v.get_slice("/" , 1) == p_var) { |
283 | return variables->get_variant(v); |
284 | } |
285 | } |
286 | return String(); |
287 | } |
288 | |