Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/debugger/editor_debugger_inspector.cpp
9896 views
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/docks/inspector_dock.h"
36
#include "editor/editor_node.h"
37
#include "editor/editor_undo_redo_manager.h"
38
#include "scene/debugger/scene_debugger.h"
39
40
bool EditorDebuggerRemoteObjects::_set(const StringName &p_name, const Variant &p_value) {
41
return _set_impl(p_name, p_value, "");
42
}
43
44
bool EditorDebuggerRemoteObjects::_set_impl(const StringName &p_name, const Variant &p_value, const String &p_field) {
45
String name = p_name;
46
if (!prop_values.has(name) || String(name).begins_with("Constants/")) {
47
return false;
48
}
49
50
// Change it back to the real name when fetching.
51
if (name == "Script") {
52
name = "script";
53
} else if (name.begins_with("Metadata/")) {
54
name = name.replace_first("Metadata/", "metadata/");
55
}
56
57
Dictionary &values = prop_values[p_name];
58
Dictionary old_values = values.duplicate();
59
for (const uint64_t key : values.keys()) {
60
values.set(key, p_value);
61
}
62
63
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
64
const int size = remote_object_ids.size();
65
ur->create_action(size == 1 ? vformat(TTR("Set %s"), name) : vformat(TTR("Set %s on %d objects"), name, size), UndoRedo::MERGE_ENDS);
66
67
ur->add_do_method(this, SNAME("emit_signal"), SNAME("values_edited"), name, values, p_field);
68
ur->add_undo_method(this, SNAME("emit_signal"), SNAME("values_edited"), name, old_values, p_field);
69
ur->commit_action();
70
71
return true;
72
}
73
74
bool EditorDebuggerRemoteObjects::_get(const StringName &p_name, Variant &r_ret) const {
75
String name = p_name;
76
if (!prop_values.has(name)) {
77
return false;
78
}
79
80
// Change it back to the real name when fetching.
81
if (name == "Script") {
82
name = "script";
83
} else if (name.begins_with("Metadata/")) {
84
name = name.replace_first("Metadata/", "metadata/");
85
}
86
87
r_ret = prop_values[p_name][remote_object_ids[0]];
88
return true;
89
}
90
91
void EditorDebuggerRemoteObjects::_get_property_list(List<PropertyInfo> *p_list) const {
92
p_list->clear(); // Sorry, don't want any categories.
93
for (const PropertyInfo &prop : prop_list) {
94
p_list->push_back(prop);
95
}
96
}
97
98
void EditorDebuggerRemoteObjects::set_property_field(const StringName &p_property, const Variant &p_value, const String &p_field) {
99
_set_impl(p_property, p_value, p_field);
100
}
101
102
String EditorDebuggerRemoteObjects::get_title() {
103
if (!remote_object_ids.is_empty() && ObjectID(remote_object_ids[0].operator uint64_t()).is_valid()) {
104
const int size = remote_object_ids.size();
105
return size == 1 ? vformat(TTR("Remote %s: %d"), type_name, remote_object_ids[0]) : vformat(TTR("Remote %s (%d Selected)"), type_name, size);
106
}
107
108
return "<null>";
109
}
110
111
Variant EditorDebuggerRemoteObjects::get_variant(const StringName &p_name) {
112
Variant var;
113
_get(p_name, var);
114
return var;
115
}
116
117
void EditorDebuggerRemoteObjects::_bind_methods() {
118
ClassDB::bind_method(D_METHOD("get_title"), &EditorDebuggerRemoteObjects::get_title);
119
ClassDB::bind_method("_hide_script_from_inspector", &EditorDebuggerRemoteObjects::_hide_script_from_inspector);
120
ClassDB::bind_method("_hide_metadata_from_inspector", &EditorDebuggerRemoteObjects::_hide_metadata_from_inspector);
121
122
ADD_SIGNAL(MethodInfo("values_edited", PropertyInfo(Variant::STRING, "property"), PropertyInfo(Variant::DICTIONARY, "values", PROPERTY_HINT_DICTIONARY_TYPE, "uint64_t:Variant"), PropertyInfo(Variant::STRING, "field")));
123
}
124
125
/// EditorDebuggerInspector
126
127
EditorDebuggerInspector::EditorDebuggerInspector() {
128
variables = memnew(EditorDebuggerRemoteObjects);
129
}
130
131
EditorDebuggerInspector::~EditorDebuggerInspector() {
132
clear_cache();
133
memdelete(variables);
134
}
135
136
void EditorDebuggerInspector::_bind_methods() {
137
ADD_SIGNAL(MethodInfo("object_selected", PropertyInfo(Variant::INT, "id")));
138
ADD_SIGNAL(MethodInfo("objects_edited", PropertyInfo(Variant::ARRAY, "ids"), PropertyInfo(Variant::STRING, "property"), PropertyInfo("value"), PropertyInfo(Variant::STRING, "field")));
139
ADD_SIGNAL(MethodInfo("object_property_updated", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property")));
140
}
141
142
void EditorDebuggerInspector::_notification(int p_what) {
143
switch (p_what) {
144
case NOTIFICATION_POSTINITIALIZE: {
145
connect("object_id_selected", callable_mp(this, &EditorDebuggerInspector::_object_selected));
146
} break;
147
148
case NOTIFICATION_ENTER_TREE: {
149
variables->remote_object_ids.append(0);
150
edit(variables);
151
} break;
152
}
153
}
154
155
void EditorDebuggerInspector::_objects_edited(const String &p_prop, const TypedDictionary<uint64_t, Variant> &p_values, const String &p_field) {
156
emit_signal(SNAME("objects_edited"), p_prop, p_values, p_field);
157
}
158
159
void EditorDebuggerInspector::_object_selected(ObjectID p_object) {
160
emit_signal(SNAME("object_selected"), p_object);
161
}
162
163
EditorDebuggerRemoteObjects *EditorDebuggerInspector::set_objects(const Array &p_arr) {
164
ERR_FAIL_COND_V(p_arr.is_empty(), nullptr);
165
166
TypedArray<uint64_t> ids;
167
LocalVector<SceneDebuggerObject> objects;
168
for (const Array arr : p_arr) {
169
SceneDebuggerObject obj;
170
obj.deserialize(arr);
171
if (obj.id.is_valid()) {
172
ids.push_back((uint64_t)obj.id);
173
objects.push_back(obj);
174
}
175
}
176
ERR_FAIL_COND_V(ids.is_empty(), nullptr);
177
178
// Sorting is necessary, as selected nodes in the remote tree are ordered by index.
179
ids.sort();
180
181
EditorDebuggerRemoteObjects *remote_objects = nullptr;
182
for (EditorDebuggerRemoteObjects *robjs : remote_objects_list) {
183
if (robjs->remote_object_ids == ids) {
184
remote_objects = robjs;
185
break;
186
}
187
}
188
189
if (!remote_objects) {
190
remote_objects = memnew(EditorDebuggerRemoteObjects);
191
remote_objects->remote_object_ids = ids;
192
remote_objects->remote_object_ids.make_read_only();
193
remote_objects->connect("values_edited", callable_mp(this, &EditorDebuggerInspector::_objects_edited));
194
remote_objects_list.push_back(remote_objects);
195
}
196
197
StringName class_name = objects[0].class_name;
198
if (class_name != SNAME("Object")) {
199
// Search for the common class between all selected objects.
200
bool check_type_again = true;
201
while (check_type_again) {
202
check_type_again = false;
203
204
if (class_name == SNAME("Object") || class_name == StringName()) {
205
// All objects inherit from Object, so no need to continue checking.
206
class_name = SNAME("Object");
207
break;
208
}
209
210
// Check that all objects inherit from type_name.
211
for (const SceneDebuggerObject &obj : objects) {
212
if (obj.class_name == class_name || ClassDB::is_parent_class(obj.class_name, class_name)) {
213
continue; // class_name is the same or a parent of the object's class.
214
}
215
216
// class_name is not a parent of the node's class, so check again with the parent class.
217
class_name = ClassDB::get_parent_class(class_name);
218
check_type_again = true;
219
break;
220
}
221
}
222
}
223
remote_objects->type_name = class_name;
224
225
// Search for properties that are present in all selected objects.
226
struct UsageData {
227
int qty = 0;
228
SceneDebuggerObject::SceneDebuggerProperty prop;
229
TypedDictionary<uint64_t, Variant> values;
230
};
231
HashMap<String, UsageData> usage;
232
int nc = 0;
233
for (const SceneDebuggerObject &obj : objects) {
234
for (const SceneDebuggerObject::SceneDebuggerProperty &prop : obj.properties) {
235
PropertyInfo pinfo = prop.first;
236
// Rename those variables, so they don't conflict with the ones from the resource itself.
237
if (pinfo.name == "script") {
238
pinfo.name = "Script";
239
} else if (pinfo.name.begins_with("metadata/")) {
240
pinfo.name = pinfo.name.replace_first("metadata/", "Metadata/");
241
}
242
243
if (!usage.has(pinfo.name)) {
244
UsageData usage_dt;
245
usage_dt.prop = prop;
246
usage_dt.prop.first.name = pinfo.name;
247
usage_dt.values[obj.id] = prop.second;
248
usage[pinfo.name] = usage_dt;
249
}
250
251
// Make sure only properties with the same exact PropertyInfo data will appear.
252
if (usage[pinfo.name].prop.first == pinfo) {
253
usage[pinfo.name].qty++;
254
usage[pinfo.name].values[obj.id] = prop.second;
255
}
256
}
257
258
nc++;
259
}
260
for (HashMap<String, UsageData>::Iterator E = usage.begin(); E;) {
261
HashMap<String, UsageData>::Iterator next = E;
262
++next;
263
264
UsageData usage_dt = E->value;
265
if (nc != usage_dt.qty) {
266
// Doesn't appear on all of them, remove it.
267
usage.erase(E->key);
268
}
269
270
E = next;
271
}
272
273
int old_prop_size = remote_objects->prop_list.size();
274
275
remote_objects->prop_list.clear();
276
int new_props_added = 0;
277
HashSet<String> changed;
278
for (KeyValue<String, UsageData> &KV : usage) {
279
const PropertyInfo &pinfo = KV.value.prop.first;
280
Variant var = KV.value.values[remote_objects->remote_object_ids[0]];
281
282
if (pinfo.type == Variant::OBJECT && var.is_string()) {
283
String path = var;
284
if (path.contains("::")) {
285
// Built-in resource.
286
String base_path = path.get_slice("::", 0);
287
Ref<Resource> dependency = ResourceLoader::load(base_path);
288
if (dependency.is_valid()) {
289
remote_dependencies.insert(dependency);
290
}
291
}
292
var = ResourceLoader::load(path);
293
KV.value.values[remote_objects->remote_object_ids[0]] = var;
294
}
295
296
// Always add the property, since props may have been added or removed.
297
remote_objects->prop_list.push_back(pinfo);
298
299
if (!remote_objects->prop_values.has(pinfo.name)) {
300
new_props_added++;
301
} else if (bool(Variant::evaluate(Variant::OP_NOT_EQUAL, remote_objects->prop_values[pinfo.name], var))) {
302
changed.insert(pinfo.name);
303
}
304
305
remote_objects->prop_values[pinfo.name] = KV.value.values;
306
}
307
308
if (old_prop_size == remote_objects->prop_list.size() && new_props_added == 0) {
309
// Only some may have changed, if so, then update those, if they exist.
310
for (const String &E : changed) {
311
emit_signal(SNAME("object_property_updated"), remote_objects->get_instance_id(), E);
312
}
313
} else {
314
// Full update, because props were added or removed.
315
remote_objects->update();
316
}
317
318
return remote_objects;
319
}
320
321
void EditorDebuggerInspector::clear_remote_inspector() {
322
if (remote_objects_list.is_empty()) {
323
return;
324
}
325
326
const Object *obj = InspectorDock::get_inspector_singleton()->get_edited_object();
327
// Check if the inspector holds remote items, and take it out if so.
328
if (Object::cast_to<EditorDebuggerRemoteObjects>(obj)) {
329
EditorNode::get_singleton()->push_item(nullptr);
330
}
331
}
332
333
void EditorDebuggerInspector::clear_cache() {
334
clear_remote_inspector();
335
336
for (EditorDebuggerRemoteObjects *robjs : remote_objects_list) {
337
memdelete(robjs);
338
}
339
remote_objects_list.clear();
340
341
remote_dependencies.clear();
342
}
343
344
void EditorDebuggerInspector::invalidate_selection_from_cache(const TypedArray<uint64_t> &p_ids) {
345
for (EditorDebuggerRemoteObjects *robjs : remote_objects_list) {
346
if (robjs->remote_object_ids == p_ids) {
347
const Object *obj = InspectorDock::get_inspector_singleton()->get_edited_object();
348
if (obj == robjs) {
349
EditorNode::get_singleton()->push_item(nullptr);
350
}
351
352
remote_objects_list.erase(robjs);
353
memdelete(robjs);
354
break;
355
}
356
}
357
}
358
359
void EditorDebuggerInspector::add_stack_variable(const Array &p_array, int p_offset) {
360
DebuggerMarshalls::ScriptStackVariable var;
361
var.deserialize(p_array);
362
String n = var.name;
363
Variant v = var.value;
364
365
PropertyHint h = PROPERTY_HINT_NONE;
366
String hs;
367
368
if (var.var_type == Variant::OBJECT && v) {
369
v = Object::cast_to<EncodedObjectAsID>(v)->get_object_id();
370
h = PROPERTY_HINT_OBJECT_ID;
371
hs = "Object";
372
}
373
String type;
374
switch (var.type) {
375
case 0:
376
type = "Locals/";
377
break;
378
case 1:
379
type = "Members/";
380
break;
381
case 2:
382
type = "Globals/";
383
break;
384
case 3:
385
type = "Evaluated/";
386
break;
387
default:
388
type = "Unknown/";
389
}
390
391
PropertyInfo pinfo;
392
// Encode special characters to avoid issues with expressions in Evaluator.
393
// Dots are skipped by uri_encode(), but uri_decode() process them correctly when replaced with "%2E".
394
pinfo.name = type + n.uri_encode().replace(".", "%2E");
395
pinfo.type = v.get_type();
396
pinfo.hint = h;
397
pinfo.hint_string = hs;
398
399
if ((p_offset == -1) || variables->prop_list.is_empty()) {
400
variables->prop_list.push_back(pinfo);
401
} else {
402
List<PropertyInfo>::Element *current = variables->prop_list.front();
403
for (int i = 0; i < p_offset; i++) {
404
current = current->next();
405
}
406
variables->prop_list.insert_before(current, pinfo);
407
}
408
variables->prop_values[pinfo.name][0] = v;
409
variables->update();
410
edit(variables);
411
}
412
413
void EditorDebuggerInspector::clear_stack_variables() {
414
variables->clear();
415
variables->update();
416
}
417
418
String EditorDebuggerInspector::get_stack_variable(const String &p_var) {
419
for (KeyValue<StringName, TypedDictionary<uint64_t, Variant>> &E : variables->prop_values) {
420
String v = E.key.operator String();
421
if (v.get_slicec('/', 1) == p_var) {
422
return variables->get_variant(v);
423
}
424
}
425
return String();
426
}
427
428