Path: blob/master/modules/objectdb_profiler/snapshot_collector.cpp
11322 views
/**************************************************************************/1/* snapshot_collector.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_collector.h"3132#include "core/core_bind.h"33#include "core/debugger/engine_debugger.h"34#include "core/os/time.h"35#include "core/version.h"36#include "scene/main/node.h"37#include "scene/main/window.h"3839void SnapshotCollector::initialize() {40pending_snapshots.clear();41EngineDebugger::register_message_capture("snapshot", EngineDebugger::Capture(nullptr, SnapshotCollector::parse_message));42}4344void SnapshotCollector::deinitialize() {45EngineDebugger::unregister_message_capture("snapshot");46pending_snapshots.clear();47}4849void SnapshotCollector::snapshot_objects(Array *p_arr, Dictionary &p_snapshot_context) {50print_verbose("Starting to snapshot");51p_arr->clear();5253// Gather all ObjectIDs first. The ObjectDB will be locked in debug_objects, so we can't serialize until it exits.5455// In rare cases, the object may be deleted as the snapshot is taken. So, we store the object's class name to give users a clue about what went wrong.56LocalVector<Pair<ObjectID, StringName>> debugger_object_ids;57debugger_object_ids.reserve(ObjectDB::get_object_count());5859ObjectDB::debug_objects(60[](Object *p_obj, void *p_user_data) {61LocalVector<Pair<ObjectID, StringName>> *debugger_object_ids_ptr = (LocalVector<Pair<ObjectID, StringName>> *)p_user_data;62debugger_object_ids_ptr->push_back(Pair<ObjectID, StringName>(p_obj->get_instance_id(), p_obj->get_class_name()));63},64(void *)&debugger_object_ids);6566// Get SnapshotDataTransportObject from ObjectID list now that DB is unlocked.67LocalVector<SnapshotDataTransportObject> debugger_objects;68debugger_objects.reserve(debugger_object_ids.size());69for (Pair<ObjectID, StringName> ids : debugger_object_ids) {70ObjectID oid = ids.first;71Object *obj = ObjectDB::get_instance(oid);72if (unlikely(obj == nullptr)) {73print_verbose(vformat("Object of class '%s' with ID %ud was found to be deleted after ObjectDB was snapshotted.", ids.second, (uint64_t)oid));74continue;75}7677if (ids.second == SNAME("EditorInterface")) {78// The EditorInterface + EditorNode is _kind of_ constructed in a debug game, but many properties are null79// We can prevent it from being constructed, but that would break other projects so better to just skip it.80continue;81}8283// This is the same way objects in the remote scene tree are serialized,84// but here we add a few extra properties via the extra_debug_data dictionary.85SnapshotDataTransportObject debug_data(obj);8687// If we're RefCounted, send over our RefCount too. Could add code here to add a few other interesting properties.88RefCounted *ref = Object::cast_to<RefCounted>(obj);89if (ref) {90debug_data.extra_debug_data["ref_count"] = ref->get_reference_count();91}9293Node *node = Object::cast_to<Node>(obj);94if (node) {95debug_data.extra_debug_data["node_name"] = node->get_name();96if (node->get_parent() != nullptr) {97debug_data.extra_debug_data["node_parent"] = node->get_parent()->get_instance_id();98}99100debug_data.extra_debug_data["node_is_scene_root"] = SceneTree::get_singleton()->get_root() == node;101102Array children;103for (int i = 0; i < node->get_child_count(); i++) {104children.push_back(node->get_child(i)->get_instance_id());105}106debug_data.extra_debug_data["node_children"] = children;107}108109debugger_objects.push_back(debug_data);110}111112// Add a header to the snapshot with general data about the state of the game, not tied to any particular object.113p_snapshot_context["mem_usage"] = Memory::get_mem_usage();114p_snapshot_context["mem_max_usage"] = Memory::get_mem_max_usage();115p_snapshot_context["timestamp"] = Time::get_singleton()->get_unix_time_from_system();116p_snapshot_context["game_version"] = get_godot_version_string();117p_arr->push_back(p_snapshot_context);118for (SnapshotDataTransportObject &debug_data : debugger_objects) {119debug_data.serialize(*p_arr);120p_arr->push_back(debug_data.extra_debug_data);121}122123print_verbose("Snapshot size: " + String::num_uint64(p_arr->size()));124}125126Error SnapshotCollector::parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured) {127r_captured = true;128if (p_msg == "request_prepare_snapshot") {129int request_id = p_args[0];130Dictionary snapshot_context;131snapshot_context["editor_version"] = (String)p_args[1];132Array objects;133snapshot_objects(&objects, snapshot_context);134// Debugger networking has a limit on both how many objects can be queued to send and how135// many bytes can be queued to send. Serializing to a string means we never hit the object136// limit, and only have to deal with the byte limit.137// Compress the snapshot in the game client to make sending the snapshot from game to editor a little faster.138CoreBind::Marshalls *m = CoreBind::Marshalls::get_singleton();139Vector<uint8_t> objs_buffer = m->base64_to_raw(m->variant_to_base64(objects));140Vector<uint8_t> objs_buffer_compressed;141objs_buffer_compressed.resize(objs_buffer.size());142int new_size = Compression::compress(objs_buffer_compressed.ptrw(), objs_buffer.ptrw(), objs_buffer.size(), Compression::MODE_DEFLATE);143objs_buffer_compressed.resize(new_size);144pending_snapshots[request_id] = objs_buffer_compressed;145146// Tell the editor how long the snapshot is.147Array resp = { request_id, pending_snapshots[request_id].size() };148EngineDebugger::get_singleton()->send_message("snapshot:snapshot_prepared", resp);149150} else if (p_msg == "request_snapshot_chunk") {151int request_id = p_args[0];152int begin = p_args[1];153int end = p_args[2];154155Array resp = { request_id, pending_snapshots[request_id].slice(begin, end) };156EngineDebugger::get_singleton()->send_message("snapshot:snapshot_chunk", resp);157158// If we sent the last part of the string, delete it locally.159if (end >= pending_snapshots[request_id].size()) {160pending_snapshots.erase(request_id);161}162} else {163r_captured = false;164}165return OK;166}167168String SnapshotCollector::get_godot_version_string() {169String hash = String(VERSION_HASH);170if (hash.length() != 0) {171hash = " " + vformat("[%s]", hash.left(9));172}173return "v" VERSION_FULL_BUILD + hash;174}175176177