Path: blob/master/modules/objectdb_profiler/editor/snapshot_data.cpp
11391 views
/**************************************************************************/1/* snapshot_data.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "snapshot_data.h"3132#include "core/core_bind.h"33#include "core/io/compression.h"34#include "core/object/script_language.h"35#include "scene/debugger/scene_debugger.h"3637#if defined(MODULE_GDSCRIPT_ENABLED) && defined(DEBUG_ENABLED)38#include "modules/gdscript/gdscript.h"39#endif4041SnapshotDataObject::SnapshotDataObject(SceneDebuggerObject &p_obj, GameStateSnapshot *p_snapshot, ResourceCache &resource_cache) :42snapshot(p_snapshot) {43remote_object_id = p_obj.id;44type_name = p_obj.class_name;4546for (const SceneDebuggerObject::SceneDebuggerProperty &prop : p_obj.properties) {47PropertyInfo pinfo = prop.first;48Variant pvalue = prop.second;4950if (pinfo.type == Variant::OBJECT && pvalue.is_string()) {51String path = pvalue;52// If a resource is followed by a ::, it is a nested resource (like a sub_resource in a .tscn file).53// To get a reference to it, first we load the parent resource (the .tscn, for example), then,54// we load the child resource. The parent resource (dependency) should not be destroyed before the child55// resource (pvalue) is loaded.56if (path.is_resource_file()) {57// Built-in resource.58String base_path = path.get_slice("::", 0);59if (!resource_cache.cache.has(base_path)) {60resource_cache.cache[base_path] = ResourceLoader::load(base_path);61resource_cache.misses++;62} else {63resource_cache.hits++;64}65}66if (!resource_cache.cache.has(path)) {67resource_cache.cache[path] = ResourceLoader::load(path);68resource_cache.misses++;69} else {70resource_cache.hits++;71}72pvalue = resource_cache.cache[path];7374if (pinfo.hint_string == "Script") {75if (get_script() != pvalue) {76set_script(Ref<RefCounted>());77Ref<Script> scr(pvalue);78if (scr.is_valid()) {79ScriptInstance *scr_instance = scr->placeholder_instance_create(this);80if (scr_instance) {81set_script_and_instance(pvalue, scr_instance);82}83}84}85}86}87prop_list.push_back(pinfo);88prop_values[pinfo.name] = pvalue;89}90}9192bool SnapshotDataObject::_get(const StringName &p_name, Variant &r_ret) const {93String name = p_name;9495if (name.begins_with("Metadata/")) {96name = name.replace_first("Metadata/", "metadata/");97}98if (!prop_values.has(name)) {99return false;100}101102r_ret = prop_values[p_name];103return true;104}105106void SnapshotDataObject::_get_property_list(List<PropertyInfo> *p_list) const {107p_list->clear(); // Sorry, don't want any categories.108for (const PropertyInfo &prop : prop_list) {109if (prop.name == "script") {110// Skip the script property, it's always added by the non-virtual method.111continue;112}113114p_list->push_back(prop);115}116}117118void SnapshotDataObject::_bind_methods() {119ClassDB::bind_method(D_METHOD("_is_read_only"), &SnapshotDataObject::_is_read_only);120}121122String SnapshotDataObject::get_node_path() {123if (!is_node()) {124return "";125}126SnapshotDataObject *current = this;127String path;128129while (true) {130String current_node_name = current->extra_debug_data["node_name"];131if (current_node_name != "") {132if (path != "") {133path = current_node_name + "/" + path;134} else {135path = current_node_name;136}137}138if (!current->extra_debug_data.has("node_parent")) {139break;140}141current = snapshot->objects[current->extra_debug_data["node_parent"]];142}143return path;144}145146String SnapshotDataObject::_get_script_name(Ref<Script> p_script) {147#if defined(MODULE_GDSCRIPT_ENABLED) && defined(DEBUG_ENABLED)148// GDScripts have more specific names than base scripts, so use those names if possible.149return GDScript::debug_get_script_name(p_script);150#else151// Otherwise fallback to the base script's name.152return p_script->get_global_name();153#endif154}155156String SnapshotDataObject::get_name() {157String found_type_name = type_name;158159// Ideally, we will name it after the script attached to it.160Ref<Script> maybe_script = get_script();161if (maybe_script.is_valid()) {162String full_name;163while (maybe_script.is_valid()) {164String global_name = _get_script_name(maybe_script);165if (global_name != "") {166if (full_name != "") {167full_name = global_name + "/" + full_name;168} else {169full_name = global_name;170}171}172maybe_script = maybe_script->get_base_script().ptr();173}174175found_type_name = type_name + "/" + full_name;176}177178return found_type_name + "_" + uitos(remote_object_id);179}180181bool SnapshotDataObject::is_refcounted() {182return is_class(RefCounted::get_class_static());183}184185bool SnapshotDataObject::is_node() {186return is_class(Node::get_class_static());187}188189bool SnapshotDataObject::is_class(const String &p_base_class) {190return ClassDB::is_parent_class(type_name, p_base_class);191}192193HashSet<ObjectID> SnapshotDataObject::_unique_references(const HashMap<String, ObjectID> &p_refs) {194HashSet<ObjectID> obj_set;195196for (const KeyValue<String, ObjectID> &pair : p_refs) {197obj_set.insert(pair.value);198}199200return obj_set;201}202203HashSet<ObjectID> SnapshotDataObject::get_unique_outbound_refernces() {204return _unique_references(outbound_references);205}206207HashSet<ObjectID> SnapshotDataObject::get_unique_inbound_references() {208return _unique_references(inbound_references);209}210211void GameStateSnapshot::_get_outbound_references(Variant &p_var, HashMap<String, ObjectID> &r_ret_val, const String &p_current_path) {212String path_divider = p_current_path.size() > 0 ? "/" : ""; // Make sure we don't start with a /.213switch (p_var.get_type()) {214case Variant::Type::INT:215case Variant::Type::OBJECT: { // Means ObjectID.216ObjectID as_id = ObjectID((uint64_t)p_var);217if (!objects.has(as_id)) {218return;219}220r_ret_val[p_current_path] = as_id;221break;222}223case Variant::Type::DICTIONARY: {224Dictionary dict = (Dictionary)p_var;225LocalVector<Variant> keys = dict.get_key_list();226for (Variant &k : keys) {227// 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.228_get_outbound_references(k, r_ret_val, p_current_path + path_divider + (String)k + "_key");229Variant v = dict.get(k, Variant());230_get_outbound_references(v, r_ret_val, p_current_path + path_divider + (String)k);231}232break;233}234case Variant::Type::ARRAY: {235Array arr = (Array)p_var;236int i = 0;237for (Variant &v : arr) {238_get_outbound_references(v, r_ret_val, p_current_path + path_divider + itos(i));239i++;240}241break;242}243default: {244break;245}246}247}248249void GameStateSnapshot::_get_rc_cycles(250SnapshotDataObject *p_obj,251SnapshotDataObject *p_source_obj,252HashSet<SnapshotDataObject *> p_traversed_objs,253LocalVector<String> &r_ret_val,254const String &p_current_path) {255// We're at the end of this branch and it was a cycle.256if (p_obj == p_source_obj && p_current_path != "") {257r_ret_val.push_back(p_current_path);258return;259}260261// Go through each of our children and try traversing them.262for (const KeyValue<String, ObjectID> &next_child : p_obj->outbound_references) {263SnapshotDataObject *next_obj = p_obj->snapshot->objects[next_child.value];264String next_name = next_obj == p_source_obj ? "self" : next_obj->get_name();265String current_name = p_obj == p_source_obj ? "self" : p_obj->get_name();266String child_path = current_name + "[\"" + next_child.key + "\"] -> " + next_name;267if (p_current_path != "") {268child_path = p_current_path + "\n" + child_path;269}270271SnapshotDataObject *next = objects[next_child.value];272if (next != nullptr && next->is_class(RefCounted::get_class_static()) && !next->is_class(WeakRef::get_class_static()) && !p_traversed_objs.has(next)) {273HashSet<SnapshotDataObject *> traversed_copy = p_traversed_objs;274if (p_obj != p_source_obj) {275traversed_copy.insert(p_obj);276}277_get_rc_cycles(next, p_source_obj, traversed_copy, r_ret_val, child_path);278}279}280}281282void GameStateSnapshot::recompute_references() {283for (const KeyValue<ObjectID, SnapshotDataObject *> &obj : objects) {284Dictionary values;285for (const KeyValue<StringName, Variant> &kv : obj.value->prop_values) {286// Should only ever be one entry in this context.287values[kv.key] = kv.value;288}289290Variant values_variant(values);291HashMap<String, ObjectID> refs;292_get_outbound_references(values_variant, refs);293294obj.value->outbound_references = refs;295296for (const KeyValue<String, ObjectID> &kv : refs) {297// Get the guy we are pointing to, and indicate the name of _our_ property that is pointing to them.298if (objects.has(kv.value)) {299objects[kv.value]->inbound_references[kv.key] = obj.key;300}301}302}303304for (const KeyValue<ObjectID, SnapshotDataObject *> &obj : objects) {305if (!obj.value->is_class(RefCounted::get_class_static()) || obj.value->is_class(WeakRef::get_class_static())) {306continue;307}308HashSet<SnapshotDataObject *> traversed_objs;309LocalVector<String> cycles;310311_get_rc_cycles(obj.value, obj.value, traversed_objs, cycles, "");312Array cycles_array;313for (const String &cycle : cycles) {314cycles_array.push_back(cycle);315}316obj.value->extra_debug_data["ref_cycles"] = cycles_array;317}318}319320Ref<GameStateSnapshotRef> GameStateSnapshot::create_ref(const String &p_snapshot_name, const Vector<uint8_t> &p_snapshot_buffer) {321// A ref to a refcounted object which is a wrapper of a non-refcounted object.322Ref<GameStateSnapshotRef> sn;323sn.instantiate(memnew(GameStateSnapshot));324GameStateSnapshot *snapshot = sn->get_snapshot();325snapshot->name = p_snapshot_name;326327// Snapshots may have been created by an older version of the editor. Handle parsing old snapshot versions here based on the version number.328329Vector<uint8_t> snapshot_buffer_decompressed;330int success = Compression::decompress_dynamic(&snapshot_buffer_decompressed, -1, p_snapshot_buffer.ptr(), p_snapshot_buffer.size(), Compression::MODE_DEFLATE);331ERR_FAIL_COND_V_MSG(success != Z_OK, nullptr, "ObjectDB Snapshot could not be parsed. Failed to decompress snapshot.");332CoreBind::Marshalls *m = CoreBind::Marshalls::get_singleton();333Array snapshot_data = m->base64_to_variant(m->raw_to_base64(snapshot_buffer_decompressed));334ERR_FAIL_COND_V_MSG(snapshot_data.is_empty(), nullptr, "ObjectDB Snapshot could not be parsed. Variant array is empty.");335const Variant &first_item = snapshot_data[0];336ERR_FAIL_COND_V_MSG(first_item.get_type() != Variant::DICTIONARY, nullptr, "ObjectDB Snapshot could not be parsed. First item is not a Dictionary.");337snapshot->snapshot_context = first_item;338339SnapshotDataObject::ResourceCache resource_cache;340for (int i = 1; i < snapshot_data.size(); i += 4) {341SceneDebuggerObject obj;342obj.deserialize(uint64_t(snapshot_data[i + 0]), snapshot_data[i + 1], snapshot_data[i + 2]);343ERR_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.");344345if (obj.id.is_null()) {346continue;347}348349snapshot->objects[obj.id] = memnew(SnapshotDataObject(obj, snapshot, resource_cache));350snapshot->objects[obj.id]->extra_debug_data = (Dictionary)snapshot_data[i + 3];351}352353snapshot->recompute_references();354print_verbose("Resource cache hits: " + String::num(resource_cache.hits) + ". Resource cache misses: " + String::num(resource_cache.misses));355return sn;356}357358GameStateSnapshot::~GameStateSnapshot() {359for (const KeyValue<ObjectID, SnapshotDataObject *> &item : objects) {360memdelete(item.value);361}362}363364bool GameStateSnapshotRef::unreference() {365bool die = RefCounted::unreference();366if (die) {367memdelete(gamestate_snapshot);368}369return die;370}371372GameStateSnapshot *GameStateSnapshotRef::get_snapshot() {373return gamestate_snapshot;374}375376377