Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/objectdb_profiler/editor/snapshot_data.cpp
11391 views
1
/**************************************************************************/
2
/* snapshot_data.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 "snapshot_data.h"
32
33
#include "core/core_bind.h"
34
#include "core/io/compression.h"
35
#include "core/object/script_language.h"
36
#include "scene/debugger/scene_debugger.h"
37
38
#if defined(MODULE_GDSCRIPT_ENABLED) && defined(DEBUG_ENABLED)
39
#include "modules/gdscript/gdscript.h"
40
#endif
41
42
SnapshotDataObject::SnapshotDataObject(SceneDebuggerObject &p_obj, GameStateSnapshot *p_snapshot, ResourceCache &resource_cache) :
43
snapshot(p_snapshot) {
44
remote_object_id = p_obj.id;
45
type_name = p_obj.class_name;
46
47
for (const SceneDebuggerObject::SceneDebuggerProperty &prop : p_obj.properties) {
48
PropertyInfo pinfo = prop.first;
49
Variant pvalue = prop.second;
50
51
if (pinfo.type == Variant::OBJECT && pvalue.is_string()) {
52
String path = pvalue;
53
// If a resource is followed by a ::, it is a nested resource (like a sub_resource in a .tscn file).
54
// To get a reference to it, first we load the parent resource (the .tscn, for example), then,
55
// we load the child resource. The parent resource (dependency) should not be destroyed before the child
56
// resource (pvalue) is loaded.
57
if (path.is_resource_file()) {
58
// Built-in resource.
59
String base_path = path.get_slice("::", 0);
60
if (!resource_cache.cache.has(base_path)) {
61
resource_cache.cache[base_path] = ResourceLoader::load(base_path);
62
resource_cache.misses++;
63
} else {
64
resource_cache.hits++;
65
}
66
}
67
if (!resource_cache.cache.has(path)) {
68
resource_cache.cache[path] = ResourceLoader::load(path);
69
resource_cache.misses++;
70
} else {
71
resource_cache.hits++;
72
}
73
pvalue = resource_cache.cache[path];
74
75
if (pinfo.hint_string == "Script") {
76
if (get_script() != pvalue) {
77
set_script(Ref<RefCounted>());
78
Ref<Script> scr(pvalue);
79
if (scr.is_valid()) {
80
ScriptInstance *scr_instance = scr->placeholder_instance_create(this);
81
if (scr_instance) {
82
set_script_and_instance(pvalue, scr_instance);
83
}
84
}
85
}
86
}
87
}
88
prop_list.push_back(pinfo);
89
prop_values[pinfo.name] = pvalue;
90
}
91
}
92
93
bool SnapshotDataObject::_get(const StringName &p_name, Variant &r_ret) const {
94
String name = p_name;
95
96
if (name.begins_with("Metadata/")) {
97
name = name.replace_first("Metadata/", "metadata/");
98
}
99
if (!prop_values.has(name)) {
100
return false;
101
}
102
103
r_ret = prop_values[p_name];
104
return true;
105
}
106
107
void SnapshotDataObject::_get_property_list(List<PropertyInfo> *p_list) const {
108
p_list->clear(); // Sorry, don't want any categories.
109
for (const PropertyInfo &prop : prop_list) {
110
if (prop.name == "script") {
111
// Skip the script property, it's always added by the non-virtual method.
112
continue;
113
}
114
115
p_list->push_back(prop);
116
}
117
}
118
119
void SnapshotDataObject::_bind_methods() {
120
ClassDB::bind_method(D_METHOD("_is_read_only"), &SnapshotDataObject::_is_read_only);
121
}
122
123
String SnapshotDataObject::get_node_path() {
124
if (!is_node()) {
125
return "";
126
}
127
SnapshotDataObject *current = this;
128
String path;
129
130
while (true) {
131
String current_node_name = current->extra_debug_data["node_name"];
132
if (current_node_name != "") {
133
if (path != "") {
134
path = current_node_name + "/" + path;
135
} else {
136
path = current_node_name;
137
}
138
}
139
if (!current->extra_debug_data.has("node_parent")) {
140
break;
141
}
142
current = snapshot->objects[current->extra_debug_data["node_parent"]];
143
}
144
return path;
145
}
146
147
String SnapshotDataObject::_get_script_name(Ref<Script> p_script) {
148
#if defined(MODULE_GDSCRIPT_ENABLED) && defined(DEBUG_ENABLED)
149
// GDScripts have more specific names than base scripts, so use those names if possible.
150
return GDScript::debug_get_script_name(p_script);
151
#else
152
// Otherwise fallback to the base script's name.
153
return p_script->get_global_name();
154
#endif
155
}
156
157
String SnapshotDataObject::get_name() {
158
String found_type_name = type_name;
159
160
// Ideally, we will name it after the script attached to it.
161
Ref<Script> maybe_script = get_script();
162
if (maybe_script.is_valid()) {
163
String full_name;
164
while (maybe_script.is_valid()) {
165
String global_name = _get_script_name(maybe_script);
166
if (global_name != "") {
167
if (full_name != "") {
168
full_name = global_name + "/" + full_name;
169
} else {
170
full_name = global_name;
171
}
172
}
173
maybe_script = maybe_script->get_base_script().ptr();
174
}
175
176
found_type_name = type_name + "/" + full_name;
177
}
178
179
return found_type_name + "_" + uitos(remote_object_id);
180
}
181
182
bool SnapshotDataObject::is_refcounted() {
183
return is_class(RefCounted::get_class_static());
184
}
185
186
bool SnapshotDataObject::is_node() {
187
return is_class(Node::get_class_static());
188
}
189
190
bool SnapshotDataObject::is_class(const String &p_base_class) {
191
return ClassDB::is_parent_class(type_name, p_base_class);
192
}
193
194
HashSet<ObjectID> SnapshotDataObject::_unique_references(const HashMap<String, ObjectID> &p_refs) {
195
HashSet<ObjectID> obj_set;
196
197
for (const KeyValue<String, ObjectID> &pair : p_refs) {
198
obj_set.insert(pair.value);
199
}
200
201
return obj_set;
202
}
203
204
HashSet<ObjectID> SnapshotDataObject::get_unique_outbound_refernces() {
205
return _unique_references(outbound_references);
206
}
207
208
HashSet<ObjectID> SnapshotDataObject::get_unique_inbound_references() {
209
return _unique_references(inbound_references);
210
}
211
212
void GameStateSnapshot::_get_outbound_references(Variant &p_var, HashMap<String, ObjectID> &r_ret_val, const String &p_current_path) {
213
String path_divider = p_current_path.size() > 0 ? "/" : ""; // Make sure we don't start with a /.
214
switch (p_var.get_type()) {
215
case Variant::Type::INT:
216
case Variant::Type::OBJECT: { // Means ObjectID.
217
ObjectID as_id = ObjectID((uint64_t)p_var);
218
if (!objects.has(as_id)) {
219
return;
220
}
221
r_ret_val[p_current_path] = as_id;
222
break;
223
}
224
case Variant::Type::DICTIONARY: {
225
Dictionary dict = (Dictionary)p_var;
226
LocalVector<Variant> keys = dict.get_key_list();
227
for (Variant &k : keys) {
228
// The dictionary key _could be_ an object. If it is, we name the key property with the same name as the value, but with _key appended to it.
229
_get_outbound_references(k, r_ret_val, p_current_path + path_divider + (String)k + "_key");
230
Variant v = dict.get(k, Variant());
231
_get_outbound_references(v, r_ret_val, p_current_path + path_divider + (String)k);
232
}
233
break;
234
}
235
case Variant::Type::ARRAY: {
236
Array arr = (Array)p_var;
237
int i = 0;
238
for (Variant &v : arr) {
239
_get_outbound_references(v, r_ret_val, p_current_path + path_divider + itos(i));
240
i++;
241
}
242
break;
243
}
244
default: {
245
break;
246
}
247
}
248
}
249
250
void GameStateSnapshot::_get_rc_cycles(
251
SnapshotDataObject *p_obj,
252
SnapshotDataObject *p_source_obj,
253
HashSet<SnapshotDataObject *> p_traversed_objs,
254
LocalVector<String> &r_ret_val,
255
const String &p_current_path) {
256
// We're at the end of this branch and it was a cycle.
257
if (p_obj == p_source_obj && p_current_path != "") {
258
r_ret_val.push_back(p_current_path);
259
return;
260
}
261
262
// Go through each of our children and try traversing them.
263
for (const KeyValue<String, ObjectID> &next_child : p_obj->outbound_references) {
264
SnapshotDataObject *next_obj = p_obj->snapshot->objects[next_child.value];
265
String next_name = next_obj == p_source_obj ? "self" : next_obj->get_name();
266
String current_name = p_obj == p_source_obj ? "self" : p_obj->get_name();
267
String child_path = current_name + "[\"" + next_child.key + "\"] -> " + next_name;
268
if (p_current_path != "") {
269
child_path = p_current_path + "\n" + child_path;
270
}
271
272
SnapshotDataObject *next = objects[next_child.value];
273
if (next != nullptr && next->is_class(RefCounted::get_class_static()) && !next->is_class(WeakRef::get_class_static()) && !p_traversed_objs.has(next)) {
274
HashSet<SnapshotDataObject *> traversed_copy = p_traversed_objs;
275
if (p_obj != p_source_obj) {
276
traversed_copy.insert(p_obj);
277
}
278
_get_rc_cycles(next, p_source_obj, traversed_copy, r_ret_val, child_path);
279
}
280
}
281
}
282
283
void GameStateSnapshot::recompute_references() {
284
for (const KeyValue<ObjectID, SnapshotDataObject *> &obj : objects) {
285
Dictionary values;
286
for (const KeyValue<StringName, Variant> &kv : obj.value->prop_values) {
287
// Should only ever be one entry in this context.
288
values[kv.key] = kv.value;
289
}
290
291
Variant values_variant(values);
292
HashMap<String, ObjectID> refs;
293
_get_outbound_references(values_variant, refs);
294
295
obj.value->outbound_references = refs;
296
297
for (const KeyValue<String, ObjectID> &kv : refs) {
298
// Get the guy we are pointing to, and indicate the name of _our_ property that is pointing to them.
299
if (objects.has(kv.value)) {
300
objects[kv.value]->inbound_references[kv.key] = obj.key;
301
}
302
}
303
}
304
305
for (const KeyValue<ObjectID, SnapshotDataObject *> &obj : objects) {
306
if (!obj.value->is_class(RefCounted::get_class_static()) || obj.value->is_class(WeakRef::get_class_static())) {
307
continue;
308
}
309
HashSet<SnapshotDataObject *> traversed_objs;
310
LocalVector<String> cycles;
311
312
_get_rc_cycles(obj.value, obj.value, traversed_objs, cycles, "");
313
Array cycles_array;
314
for (const String &cycle : cycles) {
315
cycles_array.push_back(cycle);
316
}
317
obj.value->extra_debug_data["ref_cycles"] = cycles_array;
318
}
319
}
320
321
Ref<GameStateSnapshotRef> GameStateSnapshot::create_ref(const String &p_snapshot_name, const Vector<uint8_t> &p_snapshot_buffer) {
322
// A ref to a refcounted object which is a wrapper of a non-refcounted object.
323
Ref<GameStateSnapshotRef> sn;
324
sn.instantiate(memnew(GameStateSnapshot));
325
GameStateSnapshot *snapshot = sn->get_snapshot();
326
snapshot->name = p_snapshot_name;
327
328
// Snapshots may have been created by an older version of the editor. Handle parsing old snapshot versions here based on the version number.
329
330
Vector<uint8_t> snapshot_buffer_decompressed;
331
int success = Compression::decompress_dynamic(&snapshot_buffer_decompressed, -1, p_snapshot_buffer.ptr(), p_snapshot_buffer.size(), Compression::MODE_DEFLATE);
332
ERR_FAIL_COND_V_MSG(success != Z_OK, nullptr, "ObjectDB Snapshot could not be parsed. Failed to decompress snapshot.");
333
CoreBind::Marshalls *m = CoreBind::Marshalls::get_singleton();
334
Array snapshot_data = m->base64_to_variant(m->raw_to_base64(snapshot_buffer_decompressed));
335
ERR_FAIL_COND_V_MSG(snapshot_data.is_empty(), nullptr, "ObjectDB Snapshot could not be parsed. Variant array is empty.");
336
const Variant &first_item = snapshot_data[0];
337
ERR_FAIL_COND_V_MSG(first_item.get_type() != Variant::DICTIONARY, nullptr, "ObjectDB Snapshot could not be parsed. First item is not a Dictionary.");
338
snapshot->snapshot_context = first_item;
339
340
SnapshotDataObject::ResourceCache resource_cache;
341
for (int i = 1; i < snapshot_data.size(); i += 4) {
342
SceneDebuggerObject obj;
343
obj.deserialize(uint64_t(snapshot_data[i + 0]), snapshot_data[i + 1], snapshot_data[i + 2]);
344
ERR_FAIL_COND_V_MSG(snapshot_data[i + 3].get_type() != Variant::DICTIONARY, nullptr, "ObjectDB Snapshot could not be parsed. Extra debug data is not a Dictionary.");
345
346
if (obj.id.is_null()) {
347
continue;
348
}
349
350
snapshot->objects[obj.id] = memnew(SnapshotDataObject(obj, snapshot, resource_cache));
351
snapshot->objects[obj.id]->extra_debug_data = (Dictionary)snapshot_data[i + 3];
352
}
353
354
snapshot->recompute_references();
355
print_verbose("Resource cache hits: " + String::num(resource_cache.hits) + ". Resource cache misses: " + String::num(resource_cache.misses));
356
return sn;
357
}
358
359
GameStateSnapshot::~GameStateSnapshot() {
360
for (const KeyValue<ObjectID, SnapshotDataObject *> &item : objects) {
361
memdelete(item.value);
362
}
363
}
364
365
bool GameStateSnapshotRef::unreference() {
366
bool die = RefCounted::unreference();
367
if (die) {
368
memdelete(gamestate_snapshot);
369
}
370
return die;
371
}
372
373
GameStateSnapshot *GameStateSnapshotRef::get_snapshot() {
374
return gamestate_snapshot;
375
}
376
377