Refactor ScriptDebugger.

EngineDebugger is the new interface to access the debugger.
It tries to be as agnostic as possible on the data that various
subsystems can expose.

It allows 2 types of interactions:

- Profilers:
  A subsystem can register a profiler, assigning it a unique name.
  That name can be used to activate the profiler or add data to it.
  The registered profiler can be composed of up to 3 functions:
    - Toggle: called when the profiler is activated/deactivated.
    - Add: called whenever data is added to the debugger
      (via `EngineDebugger::profiler_add_frame_data`)
    - Tick: called every frame (during idle), receives frame times.

- Captures: (Only relevant in remote debugger for now)
  A subsystem can register a capture, assigning it a unique name.
  When receiving a message, the remote debugger will check if it starts
  with `[prefix]:` and call the associated capture with name `prefix`.

Port MultiplayerAPI, Servers, Scripts, Visual, Performance to the new
profiler system.

Port SceneDebugger and RemoteDebugger to the new capture system.
The LocalDebugger also uses the new profiler system for scripts
profiling.
This commit is contained in:
Fabio Alessandrelli 2020-02-27 03:30:20 +01:00
parent d0009636df
commit b8ddaf9c33
48 changed files with 2748 additions and 2316 deletions

View file

@ -165,6 +165,7 @@ SConscript('os/SCsub')
SConscript('math/SCsub')
SConscript('crypto/SCsub')
SConscript('io/SCsub')
SConscript('debugger/SCsub')
SConscript('bind/SCsub')

5
core/debugger/SCsub Normal file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env python
Import('env')
env.add_source_files(env.core_sources, "*.cpp")

View file

@ -0,0 +1,329 @@
/*************************************************************************/
/* debugger_marshalls.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "debugger_marshalls.h"
#include "core/io/marshalls.h"
#define CHECK_SIZE(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() < (uint32_t)(expected), false, String("Malformed ") + what + " message from script debugger, message too short. Exptected size: " + itos(expected) + ", actual size: " + itos(arr.size()))
#define CHECK_END(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() > (uint32_t)expected, false, String("Malformed ") + what + " message from script debugger, message too long. Exptected size: " + itos(expected) + ", actual size: " + itos(arr.size()))
Array DebuggerMarshalls::ResourceUsage::serialize() {
infos.sort();
Array arr;
arr.push_back(infos.size() * 4);
for (List<ResourceInfo>::Element *E = infos.front(); E; E = E->next()) {
arr.push_back(E->get().path);
arr.push_back(E->get().format);
arr.push_back(E->get().type);
arr.push_back(E->get().vram);
}
return arr;
}
bool DebuggerMarshalls::ResourceUsage::deserialize(const Array &p_arr) {
CHECK_SIZE(p_arr, 1, "ResourceUsage");
uint32_t size = p_arr[0];
CHECK_SIZE(p_arr, size, "ResourceUsage");
int idx = 1;
for (uint32_t i = 0; i < size / 4; i++) {
ResourceInfo info;
info.path = p_arr[idx];
info.format = p_arr[idx + 1];
info.type = p_arr[idx + 2];
info.vram = p_arr[idx + 3];
infos.push_back(info);
}
CHECK_END(p_arr, idx, "ResourceUsage");
return true;
}
Array DebuggerMarshalls::ScriptFunctionSignature::serialize() {
Array arr;
arr.push_back(name);
arr.push_back(id);
return arr;
}
bool DebuggerMarshalls::ScriptFunctionSignature::deserialize(const Array &p_arr) {
CHECK_SIZE(p_arr, 2, "ScriptFunctionSignature");
name = p_arr[0];
id = p_arr[1];
CHECK_END(p_arr, 2, "ScriptFunctionSignature");
return true;
}
Array DebuggerMarshalls::NetworkProfilerFrame::serialize() {
Array arr;
arr.push_back(infos.size() * 6);
for (int i = 0; i < infos.size(); ++i) {
arr.push_back(uint64_t(infos[i].node));
arr.push_back(infos[i].node_path);
arr.push_back(infos[i].incoming_rpc);
arr.push_back(infos[i].incoming_rset);
arr.push_back(infos[i].outgoing_rpc);
arr.push_back(infos[i].outgoing_rset);
}
return arr;
}
bool DebuggerMarshalls::NetworkProfilerFrame::deserialize(const Array &p_arr) {
CHECK_SIZE(p_arr, 1, "NetworkProfilerFrame");
uint32_t size = p_arr[0];
CHECK_SIZE(p_arr, size, "NetworkProfilerFrame");
infos.resize(size);
int idx = 1;
for (uint32_t i = 0; i < size / 6; ++i) {
infos.write[i].node = uint64_t(p_arr[idx]);
infos.write[i].node_path = p_arr[idx + 1];
infos.write[i].incoming_rpc = p_arr[idx + 2];
infos.write[i].incoming_rset = p_arr[idx + 3];
infos.write[i].outgoing_rpc = p_arr[idx + 4];
infos.write[i].outgoing_rset = p_arr[idx + 5];
}
CHECK_END(p_arr, idx, "NetworkProfilerFrame");
return true;
}
Array DebuggerMarshalls::ServersProfilerFrame::serialize() {
Array arr;
arr.push_back(frame_number);
arr.push_back(frame_time);
arr.push_back(idle_time);
arr.push_back(physics_time);
arr.push_back(physics_frame_time);
arr.push_back(script_time);
arr.push_back(servers.size());
for (int i = 0; i < servers.size(); i++) {
ServerInfo &s = servers[i];
arr.push_back(s.name);
arr.push_back(s.functions.size() * 2);
for (int j = 0; j < s.functions.size(); j++) {
ServerFunctionInfo &f = s.functions[j];
arr.push_back(f.name);
arr.push_back(f.time);
}
}
arr.push_back(script_functions.size() * 4);
for (int i = 0; i < script_functions.size(); i++) {
arr.push_back(script_functions[i].sig_id);
arr.push_back(script_functions[i].call_count);
arr.push_back(script_functions[i].self_time);
arr.push_back(script_functions[i].total_time);
}
return arr;
}
bool DebuggerMarshalls::ServersProfilerFrame::deserialize(const Array &p_arr) {
CHECK_SIZE(p_arr, 7, "ServersProfilerFrame");
frame_number = p_arr[0];
frame_time = p_arr[1];
idle_time = p_arr[2];
physics_time = p_arr[3];
physics_frame_time = p_arr[4];
script_time = p_arr[5];
int servers_size = p_arr[6];
int idx = 7;
while (servers_size) {
CHECK_SIZE(p_arr, idx + 2, "ServersProfilerFrame");
servers_size--;
ServerInfo si;
si.name = p_arr[idx];
int sub_data_size = p_arr[idx + 1];
idx += 2;
CHECK_SIZE(p_arr, idx + sub_data_size, "ServersProfilerFrame");
for (int j = 0; j < sub_data_size / 2; j++) {
ServerFunctionInfo sf;
sf.name = p_arr[idx];
sf.time = p_arr[idx + 1];
idx += 2;
si.functions.push_back(sf);
}
servers.push_back(si);
}
CHECK_SIZE(p_arr, idx + 3, "ServersProfilerFrame");
int func_size = p_arr[idx];
idx += 1;
CHECK_SIZE(p_arr, idx + func_size, "ServersProfilerFrame");
for (int i = 0; i < func_size / 4; i++) {
ScriptFunctionInfo fi;
fi.sig_id = p_arr[idx];
fi.call_count = p_arr[idx + 1];
fi.self_time = p_arr[idx + 2];
fi.total_time = p_arr[idx + 3];
script_functions.push_back(fi);
idx += 4;
}
CHECK_END(p_arr, idx, "ServersProfilerFrame");
return true;
}
Array DebuggerMarshalls::ScriptStackDump::serialize() {
Array arr;
arr.push_back(frames.size() * 3);
for (int i = 0; i < frames.size(); i++) {
arr.push_back(frames[i].file);
arr.push_back(frames[i].line);
arr.push_back(frames[i].func);
}
return arr;
}
bool DebuggerMarshalls::ScriptStackDump::deserialize(const Array &p_arr) {
CHECK_SIZE(p_arr, 1, "ScriptStackDump");
uint32_t size = p_arr[0];
CHECK_SIZE(p_arr, size, "ScriptStackDump");
int idx = 1;
for (uint32_t i = 0; i < size / 3; i++) {
ScriptLanguage::StackInfo sf;
sf.file = p_arr[idx];
sf.line = p_arr[idx + 1];
sf.func = p_arr[idx + 2];
frames.push_back(sf);
idx += 3;
}
CHECK_END(p_arr, idx, "ScriptStackDump");
return true;
}
Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) {
Array arr;
arr.push_back(name);
arr.push_back(type);
Variant var = value;
if (value.get_type() == Variant::OBJECT && value.get_validated_object() == nullptr) {
var = Variant();
}
int len = 0;
Error err = encode_variant(var, NULL, len, true);
if (err != OK)
ERR_PRINT("Failed to encode variant.");
if (len > max_size) {
arr.push_back(Variant());
} else {
arr.push_back(var);
}
return arr;
}
bool DebuggerMarshalls::ScriptStackVariable::deserialize(const Array &p_arr) {
CHECK_SIZE(p_arr, 3, "ScriptStackVariable");
name = p_arr[0];
type = p_arr[1];
value = p_arr[2];
CHECK_END(p_arr, 3, "ScriptStackVariable");
return true;
}
Array DebuggerMarshalls::OutputError::serialize() {
Array arr;
arr.push_back(hr);
arr.push_back(min);
arr.push_back(sec);
arr.push_back(msec);
arr.push_back(source_file);
arr.push_back(source_func);
arr.push_back(source_line);
arr.push_back(error);
arr.push_back(error_descr);
arr.push_back(warning);
unsigned int size = callstack.size();
const ScriptLanguage::StackInfo *r = callstack.ptr();
arr.push_back(size * 3);
for (int i = 0; i < callstack.size(); i++) {
arr.push_back(r[i].file);
arr.push_back(r[i].func);
arr.push_back(r[i].line);
}
return arr;
}
bool DebuggerMarshalls::OutputError::deserialize(const Array &p_arr) {
CHECK_SIZE(p_arr, 11, "OutputError");
hr = p_arr[0];
min = p_arr[1];
sec = p_arr[2];
msec = p_arr[3];
source_file = p_arr[4];
source_func = p_arr[5];
source_line = p_arr[6];
error = p_arr[7];
error_descr = p_arr[8];
warning = p_arr[9];
unsigned int stack_size = p_arr[10];
CHECK_SIZE(p_arr, stack_size, "OutputError");
int idx = 11;
callstack.resize(stack_size / 3);
ScriptLanguage::StackInfo *w = callstack.ptrw();
for (unsigned int i = 0; i < stack_size / 3; i++) {
w[i].file = p_arr[idx];
w[i].func = p_arr[idx + 1];
w[i].line = p_arr[idx + 2];
idx += 3;
}
CHECK_END(p_arr, idx, "OutputError");
return true;
}
Array DebuggerMarshalls::VisualProfilerFrame::serialize() {
Array arr;
arr.push_back(frame_number);
arr.push_back(areas.size() * 3);
for (int i = 0; i < areas.size(); i++) {
arr.push_back(areas[i].name);
arr.push_back(areas[i].cpu_msec);
arr.push_back(areas[i].gpu_msec);
}
return arr;
}
bool DebuggerMarshalls::VisualProfilerFrame::deserialize(const Array &p_arr) {
CHECK_SIZE(p_arr, 2, "VisualProfilerFrame");
frame_number = p_arr[0];
int size = p_arr[1];
CHECK_SIZE(p_arr, size, "VisualProfilerFrame");
int idx = 2;
areas.resize(size / 3);
VS::FrameProfileArea *w = areas.ptrw();
for (int i = 0; i < size / 3; i++) {
w[i].name = p_arr[idx];
w[i].cpu_msec = p_arr[idx + 1];
w[i].gpu_msec = p_arr[idx + 2];
idx += 3;
}
CHECK_END(p_arr, idx, "VisualProfilerFrame");
return true;
}

View file

@ -0,0 +1,175 @@
/*************************************************************************/
/* debugger_marshalls.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef DEBUGGER_MARSHARLLS_H
#define DEBUGGER_MARSHARLLS_H
#include "core/script_language.h"
#include "servers/visual_server.h"
struct DebuggerMarshalls {
// Memory usage
struct ResourceInfo {
String path;
String format;
String type;
RID id;
int vram;
bool operator<(const ResourceInfo &p_img) const { return vram == p_img.vram ? id < p_img.id : vram > p_img.vram; }
ResourceInfo() {
vram = 0;
}
};
struct ResourceUsage {
List<ResourceInfo> infos;
Array serialize();
bool deserialize(const Array &p_arr);
};
// Network profiler
struct MultiplayerNodeInfo {
ObjectID node;
String node_path;
int incoming_rpc = 0;
int incoming_rset = 0;
int outgoing_rpc = 0;
int outgoing_rset = 0;
};
struct NetworkProfilerFrame {
Vector<MultiplayerNodeInfo> infos;
Array serialize();
bool deserialize(const Array &p_arr);
};
// Script Profiler
class ScriptFunctionSignature {
public:
StringName name;
int id = -1;
Array serialize();
bool deserialize(const Array &p_arr);
};
struct ScriptFunctionInfo {
StringName name;
int sig_id = -1;
int call_count = 0;
float self_time = 0;
float total_time = 0;
};
// Servers profiler
struct ServerFunctionInfo {
StringName name;
float time = 0;
};
struct ServerInfo {
StringName name;
List<ServerFunctionInfo> functions;
};
struct ServersProfilerFrame {
int frame_number = 0;
float frame_time = 0;
float idle_time = 0;
float physics_time = 0;
float physics_frame_time = 0;
float script_time = 0;
List<ServerInfo> servers;
Vector<ScriptFunctionInfo> script_functions;
Array serialize();
bool deserialize(const Array &p_arr);
};
struct ScriptStackVariable {
String name;
Variant value;
int type;
ScriptStackVariable() {
type = -1;
}
Array serialize(int max_size = 1 << 20); // 1 MiB default.
bool deserialize(const Array &p_arr);
};
struct ScriptStackDump {
List<ScriptLanguage::StackInfo> frames;
ScriptStackDump() {}
Array serialize();
bool deserialize(const Array &p_arr);
};
struct OutputError {
int hr;
int min;
int sec;
int msec;
String source_file;
String source_func;
int source_line;
String error;
String error_descr;
bool warning;
Vector<ScriptLanguage::StackInfo> callstack;
OutputError() {
hr = -1;
min = -1;
sec = -1;
msec = -1;
source_line = -1;
warning = false;
}
Array serialize();
bool deserialize(const Array &p_arr);
};
// Visual Profiler
struct VisualProfilerFrame {
uint64_t frame_number;
Vector<VS::FrameProfileArea> areas;
Array serialize();
bool deserialize(const Array &p_arr);
};
};
#endif // DEBUGGER_MARSHARLLS_H

View file

@ -0,0 +1,186 @@
/*************************************************************************/
/* engine_debugger.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "engine_debugger.h"
#include "core/debugger/local_debugger.h"
#include "core/debugger/remote_debugger.h"
#include "core/debugger/script_debugger.h"
#include "core/os/os.h"
EngineDebugger *EngineDebugger::singleton = NULL;
ScriptDebugger *EngineDebugger::script_debugger = NULL;
Map<StringName, EngineDebugger::Profiler> EngineDebugger::profilers;
Map<StringName, EngineDebugger::Capture> EngineDebugger::captures;
void EngineDebugger::register_profiler(const StringName &p_name, const Profiler &p_func) {
ERR_FAIL_COND_MSG(profilers.has(p_name), "Profiler already registered: " + p_name);
profilers.insert(p_name, p_func);
}
void EngineDebugger::unregister_profiler(const StringName &p_name) {
ERR_FAIL_COND_MSG(!profilers.has(p_name), "Profiler not registered: " + p_name);
Profiler &p = profilers[p_name];
if (p.active && p.toggle) {
p.toggle(p.data, false, Array());
p.active = false;
}
profilers.erase(p_name);
}
void EngineDebugger::register_message_capture(const StringName &p_name, Capture p_func) {
ERR_FAIL_COND_MSG(captures.has(p_name), "Capture already registered: " + p_name);
captures.insert(p_name, p_func);
}
void EngineDebugger::unregister_message_capture(const StringName &p_name) {
ERR_FAIL_COND_MSG(!captures.has(p_name), "Capture not registered: " + p_name);
captures.erase(p_name);
}
void EngineDebugger::profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts) {
ERR_FAIL_COND_MSG(!profilers.has(p_name), "Can't change profiler state, no profiler: " + p_name);
Profiler &p = profilers[p_name];
if (p.toggle) {
p.toggle(p.data, p_enabled, p_opts);
}
p.active = p_enabled;
}
void EngineDebugger::profiler_add_frame_data(const StringName &p_name, const Array &p_data) {
ERR_FAIL_COND_MSG(!profilers.has(p_name), "Can't add frame data, no profiler: " + p_name);
Profiler &p = profilers[p_name];
if (p.add) {
p.add(p.data, p_data);
}
}
bool EngineDebugger::is_profiling(const StringName &p_name) {
return profilers.has(p_name) && profilers[p_name].active;
}
bool EngineDebugger::has_profiler(const StringName &p_name) {
return profilers.has(p_name);
}
bool EngineDebugger::has_capture(const StringName &p_name) {
return captures.has(p_name);
}
Error EngineDebugger::capture_parse(const StringName &p_name, const String &p_msg, const Array &p_args, bool &r_captured) {
r_captured = false;
ERR_FAIL_COND_V_MSG(!captures.has(p_name), ERR_UNCONFIGURED, "Capture not registered: " + p_name);
const Capture &cap = captures[p_name];
return cap.capture(cap.data, p_msg, p_args, r_captured);
}
void EngineDebugger::line_poll() {
// The purpose of this is just processing events every now and then when the script might get too busy otherwise bugs like infinite loops can't be caught
if (poll_every % 2048 == 0)
poll_events(false);
poll_every++;
}
void EngineDebugger::iteration(uint64_t p_frame_ticks, uint64_t p_idle_ticks, uint64_t p_physics_ticks, float p_physics_frame_time) {
frame_time = USEC_TO_SEC(p_frame_ticks);
idle_time = USEC_TO_SEC(p_idle_ticks);
physics_time = USEC_TO_SEC(p_physics_ticks);
physics_frame_time = p_physics_frame_time;
// Notify tick to running profilers
for (Map<StringName, Profiler>::Element *E = profilers.front(); E; E = E->next()) {
Profiler &p = E->get();
if (!p.active || !p.tick)
continue;
p.tick(p.data, frame_time, idle_time, physics_time, physics_frame_time);
}
singleton->poll_events(true);
}
void EngineDebugger::initialize(const String &p_uri, bool p_skip_breakpoints, Vector<String> p_breakpoints) {
if (p_uri.empty())
return;
if (p_uri == "local://") {
singleton = memnew(LocalDebugger);
script_debugger = memnew(ScriptDebugger);
// Tell the OS that we want to handle termination signals.
OS::get_singleton()->initialize_debugging();
} else {
singleton = RemoteDebugger::create_for_uri(p_uri);
if (!singleton)
return;
script_debugger = memnew(ScriptDebugger);
// Notify editor of our pid (to allow focus stealing).
Array msg;
msg.push_back(OS::get_singleton()->get_process_id());
singleton->send_message("set_pid", msg);
}
if (!singleton)
return;
// There is a debugger, parse breakpoints.
ScriptDebugger *script_debugger = singleton->get_script_debugger();
script_debugger->set_skip_breakpoints(p_skip_breakpoints);
for (int i = 0; i < p_breakpoints.size(); i++) {
String bp = p_breakpoints[i];
int sp = bp.find_last(":");
ERR_CONTINUE_MSG(sp == -1, "Invalid breakpoint: '" + bp + "', expected file:line format.");
script_debugger->insert_breakpoint(bp.substr(sp + 1, bp.length()).to_int(), bp.substr(0, sp));
}
}
void EngineDebugger::deinitialize() {
if (!singleton)
return;
// Stop all profilers
for (Map<StringName, Profiler>::Element *E = profilers.front(); E; E = E->next()) {
if (E->get().active)
singleton->profiler_enable(E->key(), false);
}
// Flush any remaining message
singleton->poll_events(false);
memdelete(singleton);
singleton = NULL;
profilers.clear();
captures.clear();
}
EngineDebugger::~EngineDebugger() {
if (script_debugger)
memdelete(script_debugger);
script_debugger = NULL;
singleton = NULL;
}

View file

@ -0,0 +1,130 @@
/*************************************************************************/
/* engine_debugger.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef ENGINE_DEBUGGER_H
#define ENGINE_DEBUGGER_H
#include "core/array.h"
#include "core/map.h"
#include "core/string_name.h"
#include "core/ustring.h"
#include "core/variant.h"
#include "core/vector.h"
class ScriptDebugger;
class EngineDebugger {
public:
typedef void (*ProfilingToggle)(void *p_user, bool p_enable, const Array &p_opts);
typedef void (*ProfilingTick)(void *p_user, float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time);
typedef void (*ProfilingAdd)(void *p_user, const Array &p_arr);
typedef Error (*CaptureFunc)(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured);
class Profiler {
friend class EngineDebugger;
ProfilingToggle toggle = NULL;
ProfilingAdd add = NULL;
ProfilingTick tick = NULL;
void *data = NULL;
bool active = false;
public:
Profiler() {}
Profiler(void *p_data, ProfilingToggle p_toggle, ProfilingAdd p_add, ProfilingTick p_tick) {
data = p_data;
toggle = p_toggle;
add = p_add;
tick = p_tick;
}
};
class Capture {
friend class EngineDebugger;
CaptureFunc capture = NULL;
void *data = NULL;
public:
Capture() {}
Capture(void *p_data, CaptureFunc p_capture) {
data = p_data;
capture = p_capture;
}
};
private:
float frame_time = 0.0;
float idle_time = 0.0;
float physics_time = 0.0;
float physics_frame_time = 0.0;
uint32_t poll_every = 0;
protected:
static EngineDebugger *singleton;
static ScriptDebugger *script_debugger;
static Map<StringName, Profiler> profilers;
static Map<StringName, Capture> captures;
public:
_FORCE_INLINE_ static EngineDebugger *get_singleton() { return singleton; }
_FORCE_INLINE_ static bool is_active() { return singleton != NULL && script_debugger != NULL; }
_FORCE_INLINE_ static ScriptDebugger *get_script_debugger() { return script_debugger; };
static void initialize(const String &p_uri, bool p_skip_breakpoints, Vector<String> p_breakpoints);
static void deinitialize();
static void register_profiler(const StringName &p_name, const Profiler &p_profiler);
static void unregister_profiler(const StringName &p_name);
static bool is_profiling(const StringName &p_name);
static bool has_profiler(const StringName &p_name);
static void profiler_add_frame_data(const StringName &p_name, const Array &p_data);
static void register_message_capture(const StringName &p_name, Capture p_func);
static void unregister_message_capture(const StringName &p_name);
static bool has_capture(const StringName &p_name);
void iteration(uint64_t p_frame_ticks, uint64_t p_idle_ticks, uint64_t p_physics_ticks, float p_physics_frame_time);
void profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts = Array());
Error capture_parse(const StringName &p_name, const String &p_msg, const Array &p_args, bool &r_captured);
void line_poll();
virtual void poll_events(bool p_is_idle) {}
virtual void send_message(const String &p_msg, const Array &p_data) = 0;
virtual void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type) = 0;
virtual void debug(bool p_can_continue = true, bool p_is_error_breakpoint = false) = 0;
virtual ~EngineDebugger();
};
#endif // ENGINE_DEBUGGER_H

View file

@ -1,5 +1,5 @@
/*************************************************************************/
/* script_debugger_local.cpp */
/* local_debugger.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -28,28 +28,112 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "script_debugger_local.h"
#include "local_debugger.h"
#include "core/debugger/script_debugger.h"
#include "core/os/os.h"
#include "scene/main/scene_tree.h"
void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, bool p_is_error_breakpoint) {
struct LocalDebugger::ScriptsProfiler {
struct ProfileInfoSort {
bool operator()(const ScriptLanguage::ProfilingInfo &A, const ScriptLanguage::ProfilingInfo &B) const {
return A.total_time > B.total_time;
}
};
float frame_time = 0;
uint64_t idle_accum = 0;
Vector<ScriptLanguage::ProfilingInfo> pinfo;
void toggle(bool p_enable, const Array &p_opts) {
if (p_enable) {
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
ScriptServer::get_language(i)->profiling_start();
}
print_line("BEGIN PROFILING");
pinfo.resize(32768);
} else {
_print_frame_data(true);
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
ScriptServer::get_language(i)->profiling_stop();
}
}
}
void tick(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) {
frame_time = p_frame_time;
_print_frame_data(false);
}
void _print_frame_data(bool p_accumulated) {
uint64_t diff = OS::get_singleton()->get_ticks_usec() - idle_accum;
if (!p_accumulated && diff < 1000000) //show every one second
return;
idle_accum = OS::get_singleton()->get_ticks_usec();
int ofs = 0;
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
if (p_accumulated)
ofs += ScriptServer::get_language(i)->profiling_get_accumulated_data(&pinfo.write[ofs], pinfo.size() - ofs);
else
ofs += ScriptServer::get_language(i)->profiling_get_frame_data(&pinfo.write[ofs], pinfo.size() - ofs);
}
SortArray<ScriptLanguage::ProfilingInfo, ProfileInfoSort> sort;
sort.sort(pinfo.ptrw(), ofs);
// compute total script frame time
uint64_t script_time_us = 0;
for (int i = 0; i < ofs; i++) {
script_time_us += pinfo[i].self_time;
}
float script_time = USEC_TO_SEC(script_time_us);
float total_time = p_accumulated ? script_time : frame_time;
if (!p_accumulated) {
print_line("FRAME: total: " + rtos(total_time) + " script: " + rtos(script_time) + "/" + itos(script_time * 100 / total_time) + " %");
} else {
print_line("ACCUMULATED: total: " + rtos(total_time));
}
for (int i = 0; i < ofs; i++) {
print_line(itos(i) + ":" + pinfo[i].signature);
float tt = USEC_TO_SEC(pinfo[i].total_time);
float st = USEC_TO_SEC(pinfo[i].self_time);
print_line("\ttotal: " + rtos(tt) + "/" + itos(tt * 100 / total_time) + " % \tself: " + rtos(st) + "/" + itos(st * 100 / total_time) + " % tcalls: " + itos(pinfo[i].call_count));
}
}
ScriptsProfiler() {
idle_accum = OS::get_singleton()->get_ticks_usec();
}
};
void LocalDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
ScriptLanguage *script_lang = script_debugger->get_break_language();
if (!target_function.empty()) {
String current_function = p_script->debug_get_stack_level_function(0);
String current_function = script_lang->debug_get_stack_level_function(0);
if (current_function != target_function) {
set_depth(0);
set_lines_left(1);
script_debugger->set_depth(0);
script_debugger->set_lines_left(1);
return;
}
target_function = "";
}
print_line("\nDebugger Break, Reason: '" + p_script->debug_get_error() + "'");
print_line("*Frame " + itos(0) + " - " + p_script->debug_get_stack_level_source(0) + ":" + itos(p_script->debug_get_stack_level_line(0)) + " in function '" + p_script->debug_get_stack_level_function(0) + "'");
print_line("\nDebugger Break, Reason: '" + script_lang->debug_get_error() + "'");
print_line("*Frame " + itos(0) + " - " + script_lang->debug_get_stack_level_source(0) + ":" + itos(script_lang->debug_get_stack_level_line(0)) + " in function '" + script_lang->debug_get_stack_level_function(0) + "'");
print_line("Enter \"help\" for assistance.");
int current_frame = 0;
int total_frames = p_script->debug_get_stack_level_count();
int total_frames = script_lang->debug_get_stack_level_count();
while (true) {
OS::get_singleton()->print("debug> ");
@ -59,8 +143,8 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b
String variable_prefix = options["variable_prefix"];
if (line == "") {
print_line("\nDebugger Break, Reason: '" + p_script->debug_get_error() + "'");
print_line("*Frame " + itos(current_frame) + " - " + p_script->debug_get_stack_level_source(current_frame) + ":" + itos(p_script->debug_get_stack_level_line(current_frame)) + " in function '" + p_script->debug_get_stack_level_function(current_frame) + "'");
print_line("\nDebugger Break, Reason: '" + script_lang->debug_get_error() + "'");
print_line("*Frame " + itos(current_frame) + " - " + script_lang->debug_get_stack_level_source(current_frame) + ":" + itos(script_lang->debug_get_stack_level_line(current_frame)) + " in function '" + script_lang->debug_get_stack_level_function(current_frame) + "'");
print_line("Enter \"help\" for assistance.");
} else if (line == "c" || line == "continue")
break;
@ -69,20 +153,20 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b
for (int i = 0; i < total_frames; i++) {
String cfi = (current_frame == i) ? "*" : " "; //current frame indicator
print_line(cfi + "Frame " + itos(i) + " - " + p_script->debug_get_stack_level_source(i) + ":" + itos(p_script->debug_get_stack_level_line(i)) + " in function '" + p_script->debug_get_stack_level_function(i) + "'");
print_line(cfi + "Frame " + itos(i) + " - " + script_lang->debug_get_stack_level_source(i) + ":" + itos(script_lang->debug_get_stack_level_line(i)) + " in function '" + script_lang->debug_get_stack_level_function(i) + "'");
}
} else if (line.begins_with("fr") || line.begins_with("frame")) {
if (line.get_slice_count(" ") == 1) {
print_line("*Frame " + itos(current_frame) + " - " + p_script->debug_get_stack_level_source(current_frame) + ":" + itos(p_script->debug_get_stack_level_line(current_frame)) + " in function '" + p_script->debug_get_stack_level_function(current_frame) + "'");
print_line("*Frame " + itos(current_frame) + " - " + script_lang->debug_get_stack_level_source(current_frame) + ":" + itos(script_lang->debug_get_stack_level_line(current_frame)) + " in function '" + script_lang->debug_get_stack_level_function(current_frame) + "'");
} else {
int frame = line.get_slicec(' ', 1).to_int();
if (frame < 0 || frame >= total_frames) {
print_line("Error: Invalid frame.");
} else {
current_frame = frame;
print_line("*Frame " + itos(frame) + " - " + p_script->debug_get_stack_level_source(frame) + ":" + itos(p_script->debug_get_stack_level_line(frame)) + " in function '" + p_script->debug_get_stack_level_function(frame) + "'");
print_line("*Frame " + itos(frame) + " - " + script_lang->debug_get_stack_level_source(frame) + ":" + itos(script_lang->debug_get_stack_level_line(frame)) + " in function '" + script_lang->debug_get_stack_level_function(frame) + "'");
}
}
@ -120,21 +204,21 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b
List<String> locals;
List<Variant> values;
p_script->debug_get_stack_level_locals(current_frame, &locals, &values);
script_lang->debug_get_stack_level_locals(current_frame, &locals, &values);
print_variables(locals, values, variable_prefix);
} else if (line == "gv" || line == "globals") {
List<String> globals;
List<Variant> values;
p_script->debug_get_globals(&globals, &values);
script_lang->debug_get_globals(&globals, &values);
print_variables(globals, values, variable_prefix);
} else if (line == "mv" || line == "members") {
List<String> members;
List<Variant> values;
p_script->debug_get_stack_level_members(current_frame, &members, &values);
script_lang->debug_get_stack_level_members(current_frame, &members, &values);
print_variables(members, values, variable_prefix);
} else if (line.begins_with("p") || line.begins_with("print")) {
@ -144,29 +228,29 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b
} else {
String expr = line.get_slicec(' ', 2);
String res = p_script->debug_parse_stack_level_expression(current_frame, expr);
String res = script_lang->debug_parse_stack_level_expression(current_frame, expr);
print_line(res);
}
} else if (line == "s" || line == "step") {
set_depth(-1);
set_lines_left(1);
script_debugger->set_depth(-1);
script_debugger->set_lines_left(1);
break;
} else if (line == "n" || line == "next") {
set_depth(0);
set_lines_left(1);
script_debugger->set_depth(0);
script_debugger->set_lines_left(1);
break;
} else if (line == "fin" || line == "finish") {
String current_function = p_script->debug_get_stack_level_function(0);
String current_function = script_lang->debug_get_stack_level_function(0);
for (int i = 0; i < total_frames; i++) {
target_function = p_script->debug_get_stack_level_function(i);
target_function = script_lang->debug_get_stack_level_function(i);
if (target_function != current_function) {
set_depth(0);
set_lines_left(1);
script_debugger->set_depth(0);
script_debugger->set_lines_left(1);
return;
}
}
@ -178,7 +262,7 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b
if (line.get_slice_count(" ") <= 1) {
const Map<int, Set<StringName> > &breakpoints = get_breakpoints();
const Map<int, Set<StringName> > &breakpoints = script_debugger->get_breakpoints();
if (breakpoints.size() == 0) {
print_line("No Breakpoints.");
continue;
@ -199,7 +283,7 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b
if (source.empty())
continue;
insert_breakpoint(linenr, source);
script_debugger->insert_breakpoint(linenr, source);
print_line("Added breakpoint at " + source + ":" + itos(linenr));
}
@ -207,16 +291,16 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b
} else if (line == "q" || line == "quit") {
// Do not stop again on quit
clear_breakpoints();
ScriptDebugger::get_singleton()->set_depth(-1);
ScriptDebugger::get_singleton()->set_lines_left(-1);
script_debugger->clear_breakpoints();
script_debugger->set_depth(-1);
script_debugger->set_lines_left(-1);
SceneTree::get_singleton()->quit();
break;
} else if (line.begins_with("delete")) {
if (line.get_slice_count(" ") <= 1) {
clear_breakpoints();
script_debugger->clear_breakpoints();
} else {
Pair<String, int> breakpoint = to_breakpoint(line);
@ -227,7 +311,7 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b
if (source.empty())
continue;
remove_breakpoint(linenr, source);
script_debugger->remove_breakpoint(linenr, source);
print_line("Removed breakpoint at " + source + ":" + itos(linenr));
}
@ -255,7 +339,7 @@ void ScriptDebuggerLocal::debug(ScriptLanguage *p_script, bool p_can_continue, b
}
}
void ScriptDebuggerLocal::print_variables(const List<String> &names, const List<Variant> &values, const String &variable_prefix) {
void LocalDebugger::print_variables(const List<String> &names, const List<Variant> &values, const String &variable_prefix) {
String value;
Vector<String> value_lines;
@ -279,7 +363,7 @@ void ScriptDebuggerLocal::print_variables(const List<String> &names, const List<
}
}
Pair<String, int> ScriptDebuggerLocal::to_breakpoint(const String &p_line) {
Pair<String, int> LocalDebugger::to_breakpoint(const String &p_line) {
String breakpoint_part = p_line.get_slicec(' ', 1);
Pair<String, int> breakpoint;
@ -290,135 +374,43 @@ Pair<String, int> ScriptDebuggerLocal::to_breakpoint(const String &p_line) {
return breakpoint;
}
breakpoint.first = breakpoint_find_source(breakpoint_part.left(last_colon).strip_edges());
breakpoint.first = script_debugger->breakpoint_find_source(breakpoint_part.left(last_colon).strip_edges());
breakpoint.second = breakpoint_part.right(last_colon).strip_edges().to_int();
return breakpoint;
}
struct _ScriptDebuggerLocalProfileInfoSort {
bool operator()(const ScriptLanguage::ProfilingInfo &A, const ScriptLanguage::ProfilingInfo &B) const {
return A.total_time > B.total_time;
}
};
void ScriptDebuggerLocal::profiling_set_frame_times(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) {
frame_time = p_frame_time;
idle_time = p_idle_time;
physics_time = p_physics_time;
physics_frame_time = p_physics_frame_time;
}
void ScriptDebuggerLocal::idle_poll() {
if (!profiling)
return;
uint64_t diff = OS::get_singleton()->get_ticks_usec() - idle_accum;
if (diff < 1000000) //show every one second
return;
idle_accum = OS::get_singleton()->get_ticks_usec();
int ofs = 0;
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
ofs += ScriptServer::get_language(i)->profiling_get_frame_data(&pinfo.write[ofs], pinfo.size() - ofs);
}
SortArray<ScriptLanguage::ProfilingInfo, _ScriptDebuggerLocalProfileInfoSort> sort;
sort.sort(pinfo.ptrw(), ofs);
//falta el frame time
uint64_t script_time_us = 0;
for (int i = 0; i < ofs; i++) {
script_time_us += pinfo[i].self_time;
}
float script_time = USEC_TO_SEC(script_time_us);
float total_time = frame_time;
//print script total
print_line("FRAME: total: " + rtos(frame_time) + " script: " + rtos(script_time) + "/" + itos(script_time * 100 / total_time) + " %");
for (int i = 0; i < ofs; i++) {
print_line(itos(i) + ":" + pinfo[i].signature);
float tt = USEC_TO_SEC(pinfo[i].total_time);
float st = USEC_TO_SEC(pinfo[i].self_time);
print_line("\ttotal: " + rtos(tt) + "/" + itos(tt * 100 / total_time) + " % \tself: " + rtos(st) + "/" + itos(st * 100 / total_time) + " % tcalls: " + itos(pinfo[i].call_count));
}
}
void ScriptDebuggerLocal::profiling_start() {
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
ScriptServer::get_language(i)->profiling_start();
}
print_line("BEGIN PROFILING");
profiling = true;
pinfo.resize(32768);
frame_time = 0;
physics_time = 0;
idle_time = 0;
physics_frame_time = 0;
}
void ScriptDebuggerLocal::profiling_end() {
int ofs = 0;
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
ofs += ScriptServer::get_language(i)->profiling_get_accumulated_data(&pinfo.write[ofs], pinfo.size() - ofs);
}
SortArray<ScriptLanguage::ProfilingInfo, _ScriptDebuggerLocalProfileInfoSort> sort;
sort.sort(pinfo.ptrw(), ofs);
uint64_t total_us = 0;
for (int i = 0; i < ofs; i++) {
total_us += pinfo[i].self_time;
}
float total_time = total_us / 1000000.0;
for (int i = 0; i < ofs; i++) {
print_line(itos(i) + ":" + pinfo[i].signature);
float tt = USEC_TO_SEC(pinfo[i].total_time);
float st = USEC_TO_SEC(pinfo[i].self_time);
print_line("\ttotal_ms: " + rtos(tt) + "\tself_ms: " + rtos(st) + "total%: " + itos(tt * 100 / total_time) + "\tself%: " + itos(st * 100 / total_time) + "\tcalls: " + itos(pinfo[i].call_count));
}
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
ScriptServer::get_language(i)->profiling_stop();
}
profiling = false;
}
void ScriptDebuggerLocal::send_message(const String &p_message, const Array &p_args) {
void LocalDebugger::send_message(const String &p_message, const Array &p_args) {
// This needs to be cleaned up entirely.
// print_line("MESSAGE: '" + p_message + "' - " + String(Variant(p_args)));
}
void ScriptDebuggerLocal::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type, const Vector<ScriptLanguage::StackInfo> &p_stack_info) {
void LocalDebugger::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type) {
print_line("ERROR: '" + (p_descr.empty() ? p_err : p_descr) + "'");
}
ScriptDebuggerLocal::ScriptDebuggerLocal() {
LocalDebugger::LocalDebugger() {
profiling = false;
idle_accum = OS::get_singleton()->get_ticks_usec();
options["variable_prefix"] = "";
// Bind scripts profiler.
scripts_profiler = memnew(ScriptsProfiler);
Profiler scr_prof(
scripts_profiler,
[](void *p_user, bool p_enable, const Array &p_opts) {
((ScriptsProfiler *)p_user)->toggle(p_enable, p_opts);
},
NULL,
[](void *p_user, float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) {
((ScriptsProfiler *)p_user)->tick(p_frame_time, p_idle_time, p_physics_time, p_physics_frame_time);
});
register_profiler("scripts", scr_prof);
}
LocalDebugger::~LocalDebugger() {
unregister_profiler("scripts");
if (scripts_profiler)
memdelete(scripts_profiler);
}

View file

@ -1,5 +1,5 @@
/*************************************************************************/
/* script_debugger_peer.h */
/* local_debugger.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -28,21 +28,33 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef SCRIPT_DEBUGGER_PEER_H
#define SCRIPT_DEBUGGER_PEER_H
#ifndef LOCAL_DEBUGGER_H
#define LOCAL_DEBUGGER_H
#include "core/reference.h"
#include "core/ustring.h"
#include "core/debugger/engine_debugger.h"
#include "core/list.h"
#include "core/script_language.h"
class LocalDebugger : public EngineDebugger {
private:
struct ScriptsProfiler;
ScriptsProfiler *scripts_profiler = NULL;
String target_function;
Map<String, String> options;
Pair<String, int> to_breakpoint(const String &p_line);
void print_variables(const List<String> &names, const List<Variant> &values, const String &variable_prefix);
class ScriptDebuggerPeer : public Reference {
public:
static Ref<ScriptDebuggerPeer> create_from_uri(const String p_uri);
virtual bool is_peer_connected() = 0;
virtual bool has_message() = 0;
virtual Error put_message(const Array &p_arr) = 0;
virtual Array get_message() = 0;
virtual void close() = 0;
virtual void poll() = 0;
void debug(bool p_can_continue, bool p_is_error_breakpoint);
void send_message(const String &p_message, const Array &p_args);
void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type);
LocalDebugger();
~LocalDebugger();
};
#endif
#endif // LOCAL_DEBUGGER_H

View file

@ -0,0 +1,935 @@
/*************************************************************************/
/* remote_debugger.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "remote_debugger.h"
#include "core/debugger/debugger_marshalls.h"
#include "core/debugger/engine_debugger.h"
#include "core/debugger/script_debugger.h"
#include "core/os/input.h"
#include "core/os/os.h"
#include "core/project_settings.h"
#include "core/script_language.h"
#include "scene/main/node.h"
template <typename T>
void RemoteDebugger::_bind_profiler(const String &p_name, T *p_prof) {
EngineDebugger::Profiler prof(
p_prof,
[](void *p_user, bool p_enable, const Array &p_opts) {
((T *)p_user)->toggle(p_enable, p_opts);
},
[](void *p_user, const Array &p_data) {
((T *)p_user)->add(p_data);
},
[](void *p_user, float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) {
((T *)p_user)->tick(p_frame_time, p_idle_time, p_physics_time, p_physics_frame_time);
});
EngineDebugger::register_profiler(p_name, prof);
}
struct RemoteDebugger::NetworkProfiler {
public:
typedef DebuggerMarshalls::MultiplayerNodeInfo NodeInfo;
struct BandwidthFrame {
uint32_t timestamp;
int packet_size;
};
int bandwidth_in_ptr = 0;
Vector<BandwidthFrame> bandwidth_in;
int bandwidth_out_ptr = 0;
Vector<BandwidthFrame> bandwidth_out;
uint64_t last_bandwidth_time = 0;
Map<ObjectID, NodeInfo> multiplayer_node_data;
uint64_t last_profile_time = 0;
NetworkProfiler() {}
int bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) {
int total_bandwidth = 0;
uint32_t timestamp = OS::get_singleton()->get_ticks_msec();
uint32_t final_timestamp = timestamp - 1000;
int i = (p_pointer + p_buffer.size() - 1) % p_buffer.size();
while (i != p_pointer && p_buffer[i].packet_size > 0) {
if (p_buffer[i].timestamp < final_timestamp) {
return total_bandwidth;
}
total_bandwidth += p_buffer[i].packet_size;
i = (i + p_buffer.size() - 1) % p_buffer.size();
}
ERR_FAIL_COND_V_MSG(i == p_pointer, total_bandwidth, "Reached the end of the bandwidth profiler buffer, values might be inaccurate.");
return total_bandwidth;
}
void init_node(const ObjectID p_node) {
if (multiplayer_node_data.has(p_node))
return;
multiplayer_node_data.insert(p_node, DebuggerMarshalls::MultiplayerNodeInfo());
multiplayer_node_data[p_node].node = p_node;
multiplayer_node_data[p_node].node_path = Object::cast_to<Node>(ObjectDB::get_instance(p_node))->get_path();
multiplayer_node_data[p_node].incoming_rpc = 0;
multiplayer_node_data[p_node].incoming_rset = 0;
multiplayer_node_data[p_node].outgoing_rpc = 0;
multiplayer_node_data[p_node].outgoing_rset = 0;
}
void toggle(bool p_enable, const Array &p_opts) {
multiplayer_node_data.clear();
if (!p_enable) {
bandwidth_in.clear();
bandwidth_out.clear();
} else {
bandwidth_in_ptr = 0;
bandwidth_in.resize(16384); // ~128kB
for (int i = 0; i < bandwidth_in.size(); ++i) {
bandwidth_in.write[i].packet_size = -1;
}
bandwidth_out_ptr = 0;
bandwidth_out.resize(16384); // ~128kB
for (int i = 0; i < bandwidth_out.size(); ++i) {
bandwidth_out.write[i].packet_size = -1;
}
}
}
void add(const Array &p_data) {
ERR_FAIL_COND(p_data.size() < 1);
const String type = p_data[0];
if (type == "node") {
ERR_FAIL_COND(p_data.size() < 3);
const ObjectID id = p_data[1];
const String what = p_data[2];
init_node(id);
NodeInfo &info = multiplayer_node_data[id];
if (what == "rpc_in") {
info.incoming_rpc++;
} else if (what == "rpc_out") {
info.outgoing_rpc++;
} else if (what == "rset_in") {
info.incoming_rset = 0;
} else if (what == "rset_out") {
info.outgoing_rset++;
}
} else if (type == "bandwidth") {
ERR_FAIL_COND(p_data.size() < 4);
const String inout = p_data[1];
int time = p_data[2];
int size = p_data[3];
if (inout == "in") {
bandwidth_in.write[bandwidth_in_ptr].timestamp = time;
bandwidth_in.write[bandwidth_in_ptr].packet_size = size;
bandwidth_in_ptr = (bandwidth_in_ptr + 1) % bandwidth_in.size();
} else if (inout == "out") {
bandwidth_out.write[bandwidth_out_ptr].timestamp = time;
bandwidth_out.write[bandwidth_out_ptr].packet_size = size;
bandwidth_out_ptr = (bandwidth_out_ptr + 1) % bandwidth_out.size();
}
}
}
void tick(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) {
uint64_t pt = OS::get_singleton()->get_ticks_msec();
if (pt - last_bandwidth_time > 200) {
last_bandwidth_time = pt;
int incoming_bandwidth = bandwidth_usage(bandwidth_in, bandwidth_in_ptr);
int outgoing_bandwidth = bandwidth_usage(bandwidth_out, bandwidth_out_ptr);
Array arr;
arr.push_back(incoming_bandwidth);
arr.push_back(outgoing_bandwidth);
EngineDebugger::get_singleton()->send_message("network:bandwidth", arr);
}
if (pt - last_profile_time > 100) {
last_profile_time = pt;
DebuggerMarshalls::NetworkProfilerFrame frame;
for (Map<ObjectID, NodeInfo>::Element *E = multiplayer_node_data.front(); E; E = E->next()) {
frame.infos.push_back(E->get());
}
multiplayer_node_data.clear();
EngineDebugger::get_singleton()->send_message("network:profile_frame", frame.serialize());
}
}
};
struct RemoteDebugger::ScriptsProfiler {
typedef DebuggerMarshalls::ScriptFunctionSignature FunctionSignature;
typedef DebuggerMarshalls::ScriptFunctionInfo FunctionInfo;
struct ProfileInfoSort {
bool operator()(ScriptLanguage::ProfilingInfo *A, ScriptLanguage::ProfilingInfo *B) const {
return A->total_time < B->total_time;
}
};
Vector<ScriptLanguage::ProfilingInfo> info;
Vector<ScriptLanguage::ProfilingInfo *> ptrs;
Map<StringName, int> sig_map;
int max_frame_functions = 16;
void toggle(bool p_enable, const Array &p_opts) {
if (p_enable) {
sig_map.clear();
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
ScriptServer::get_language(i)->profiling_start();
}
if (p_opts.size() == 1 && p_opts[0].get_type() == Variant::INT) {
max_frame_functions = MAX(0, int(p_opts[0]));
}
} else {
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
ScriptServer::get_language(i)->profiling_stop();
}
}
}
void write_frame_data(Vector<FunctionInfo> &r_funcs, uint64_t &r_total, bool p_accumulated) {
int ofs = 0;
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
if (p_accumulated)
ofs += ScriptServer::get_language(i)->profiling_get_accumulated_data(&info.write[ofs], info.size() - ofs);
else
ofs += ScriptServer::get_language(i)->profiling_get_frame_data(&info.write[ofs], info.size() - ofs);
}
for (int i = 0; i < ofs; i++) {
ptrs.write[i] = &info.write[i];
}
SortArray<ScriptLanguage::ProfilingInfo *, ProfileInfoSort> sa;
sa.sort(ptrs.ptrw(), ofs);
int to_send = MIN(ofs, max_frame_functions);
// Check signatures first, and compute total time.
r_total = 0;
for (int i = 0; i < to_send; i++) {
if (!sig_map.has(ptrs[i]->signature)) {
int idx = sig_map.size();
FunctionSignature sig;
sig.name = ptrs[i]->signature;
sig.id = idx;
EngineDebugger::get_singleton()->send_message("servers:function_signature", sig.serialize());
sig_map[ptrs[i]->signature] = idx;
}
r_total += ptrs[i]->self_time;
}
// Send frame, script time, functions information then
r_funcs.resize(to_send);
FunctionInfo *w = r_funcs.ptrw();
for (int i = 0; i < to_send; i++) {
if (sig_map.has(ptrs[i]->signature)) {
w[i].sig_id = sig_map[ptrs[i]->signature];
}
w[i].call_count = ptrs[i]->call_count;
w[i].total_time = ptrs[i]->total_time / 1000000.0;
w[i].self_time = ptrs[i]->self_time / 1000000.0;
}
}
ScriptsProfiler() {
info.resize(GLOBAL_GET("debug/settings/profiler/max_functions"));
ptrs.resize(info.size());
}
};
struct RemoteDebugger::ServersProfiler {
bool skip_profile_frame = false;
typedef DebuggerMarshalls::ServerInfo ServerInfo;
typedef DebuggerMarshalls::ServerFunctionInfo ServerFunctionInfo;
Map<StringName, ServerInfo> server_data;
ScriptsProfiler scripts_profiler;
float frame_time = 0;
float idle_time = 0;
float physics_time = 0;
float physics_frame_time = 0;
void toggle(bool p_enable, const Array &p_opts) {
skip_profile_frame = false;
if (p_enable) {
server_data.clear(); // Clear old profiling data.
} else {
_send_frame_data(true); // Send final frame.
}
scripts_profiler.toggle(p_enable, p_opts);
}
void add(const Array &p_data) {
String name = p_data[0];
if (!server_data.has(name)) {
ServerInfo info;
info.name = name;
server_data[name] = info;
}
ServerInfo &srv = server_data[name];
ServerFunctionInfo fi;
fi.name = p_data[1];
fi.time = p_data[2];
srv.functions.push_back(fi);
}
void tick(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) {
frame_time = p_frame_time;
idle_time = p_idle_time;
physics_time = p_physics_time;
physics_frame_time = p_physics_frame_time;
_send_frame_data(false);
}
void _send_frame_data(bool p_final) {
DebuggerMarshalls::ServersProfilerFrame frame;
frame.frame_number = Engine::get_singleton()->get_frames_drawn();
frame.frame_time = frame_time;
frame.idle_time = idle_time;
frame.physics_time = physics_time;
frame.physics_frame_time = physics_frame_time;
Map<StringName, ServerInfo>::Element *E = server_data.front();
while (E) {
if (!p_final) {
frame.servers.push_back(E->get());
}
E->get().functions.clear();
E = E->next();
}
uint64_t time = 0;
scripts_profiler.write_frame_data(frame.script_functions, time, p_final);
frame.script_time = USEC_TO_SEC(time);
if (skip_profile_frame) {
skip_profile_frame = false;
return;
}
if (p_final) {
EngineDebugger::get_singleton()->send_message("servers:profile_total", frame.serialize());
} else {
EngineDebugger::get_singleton()->send_message("servers:profile_frame", frame.serialize());
}
}
};
struct RemoteDebugger::VisualProfiler {
typedef DebuggerMarshalls::ServerInfo ServerInfo;
typedef DebuggerMarshalls::ServerFunctionInfo ServerFunctionInfo;
Map<StringName, ServerInfo> server_data;
void toggle(bool p_enable, const Array &p_opts) {
VS::get_singleton()->set_frame_profiling_enabled(p_enable);
}
void add(const Array &p_data) {}
void tick(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) {
Vector<VS::FrameProfileArea> profile_areas = VS::get_singleton()->get_frame_profile();
DebuggerMarshalls::VisualProfilerFrame frame;
if (!profile_areas.size())
return;
frame.frame_number = VS::get_singleton()->get_frame_profile_frame();
frame.areas.append_array(profile_areas);
EngineDebugger::get_singleton()->send_message("visual:profile_frame", frame.serialize());
}
};
struct RemoteDebugger::PerformanceProfiler {
Object *performance = NULL;
int last_perf_time = 0;
void toggle(bool p_enable, const Array &p_opts) {}
void add(const Array &p_data) {}
void tick(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) {
if (!performance)
return;
uint64_t pt = OS::get_singleton()->get_ticks_msec();
if (pt - last_perf_time < 1000)
return;
last_perf_time = pt;
int max = performance->get("MONITOR_MAX");
Array arr;
arr.resize(max);
for (int i = 0; i < max; i++) {
arr[i] = performance->call("get_monitor", i);
}
EngineDebugger::get_singleton()->send_message("performance:profile_frame", arr);
}
PerformanceProfiler(Object *p_performance) {
performance = p_performance;
}
};
void RemoteDebugger::_send_resource_usage() {
DebuggerMarshalls::ResourceUsage usage;
List<VS::TextureInfo> tinfo;
VS::get_singleton()->texture_debug_usage(&tinfo);
for (List<VS::TextureInfo>::Element *E = tinfo.front(); E; E = E->next()) {
DebuggerMarshalls::ResourceInfo info;
info.path = E->get().path;
info.vram = E->get().bytes;
info.id = E->get().texture;
info.type = "Texture";
if (E->get().depth == 0) {
info.format = itos(E->get().width) + "x" + itos(E->get().height) + " " + Image::get_format_name(E->get().format);
} else {
info.format = itos(E->get().width) + "x" + itos(E->get().height) + "x" + itos(E->get().depth) + " " + Image::get_format_name(E->get().format);
}
usage.infos.push_back(info);
}
EngineDebugger::get_singleton()->send_message("memory:usage", usage.serialize());
}
Error RemoteDebugger::_put_msg(String p_message, Array p_data) {
Array msg;
msg.push_back(p_message);
msg.push_back(p_data);
Error err = peer->put_message(msg);
if (err != OK)
n_messages_dropped++;
return err;
}
void RemoteDebugger::_err_handler(void *p_this, const char *p_func, const char *p_file, int p_line, const char *p_err, const char *p_descr, ErrorHandlerType p_type) {
if (p_type == ERR_HANDLER_SCRIPT)
return; //ignore script errors, those go through debugger
RemoteDebugger *rd = (RemoteDebugger *)p_this;
if (rd->flushing && Thread::get_caller_id() == rd->flush_thread) // Can't handle recursive errors during flush.
return;
Vector<ScriptLanguage::StackInfo> si;
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
si = ScriptServer::get_language(i)->debug_get_current_stack_info();
if (si.size())
break;
}
// send_error will lock internally.
rd->script_debugger->send_error(p_func, p_file, p_line, p_err, p_descr, p_type, si);
}
void RemoteDebugger::_print_handler(void *p_this, const String &p_string, bool p_error) {
RemoteDebugger *rd = (RemoteDebugger *)p_this;
if (rd->flushing && Thread::get_caller_id() == rd->flush_thread) // Can't handle recursive prints during flush.
return;
String s = p_string;
int allowed_chars = MIN(MAX(rd->max_chars_per_second - rd->char_count, 0), s.length());
if (allowed_chars == 0)
return;
if (allowed_chars < s.length()) {
s = s.substr(0, allowed_chars);
}
MutexLock lock(rd->mutex);
rd->char_count += allowed_chars;
bool overflowed = rd->char_count >= rd->max_chars_per_second;
if (rd->is_peer_connected()) {
if (overflowed)
s += "[...]";
rd->output_strings.push_back(s);
if (overflowed) {
rd->output_strings.push_back("[output overflow, print less text!]");
}
}
}
RemoteDebugger::ErrorMessage RemoteDebugger::_create_overflow_error(const String &p_what, const String &p_descr) {
ErrorMessage oe;
oe.error = p_what;
oe.error_descr = p_descr;
oe.warning = false;
uint64_t time = OS::get_singleton()->get_ticks_msec();
oe.hr = time / 3600000;
oe.min = (time / 60000) % 60;
oe.sec = (time / 1000) % 60;
oe.msec = time % 1000;
return oe;
}
void RemoteDebugger::flush_output() {
flush_thread = Thread::get_caller_id();
flushing = true;
MutexLock lock(mutex);
if (!is_peer_connected())
return;
if (n_messages_dropped > 0) {
ErrorMessage err_msg = _create_overflow_error("TOO_MANY_MESSAGES", "Too many messages! " + String::num_int64(n_messages_dropped) + " messages were dropped. Profiling might misbheave, try raising 'network/limits/debugger/max_queued_messages' in project setting.");
if (_put_msg("error", err_msg.serialize()) == OK)
n_messages_dropped = 0;
}
if (output_strings.size()) {
// Join output strings so we generate less messages.
Vector<String> strings;
strings.resize(output_strings.size());
String *w = strings.ptrw();
for (int i = 0; i < output_strings.size(); i++) {
w[i] = output_strings[i];
}
Array arr;
arr.push_back(strings);
_put_msg("output", arr);
output_strings.clear();
}
while (errors.size()) {
ErrorMessage oe = errors.front()->get();
_put_msg("error", oe.serialize());
errors.pop_front();
}
// Update limits
uint64_t ticks = OS::get_singleton()->get_ticks_usec() / 1000;
if (ticks - last_reset > 1000) {
last_reset = ticks;
char_count = 0;
err_count = 0;
n_errors_dropped = 0;
warn_count = 0;
n_warnings_dropped = 0;
}
flushing = false;
}
void RemoteDebugger::send_message(const String &p_message, const Array &p_args) {
MutexLock lock(mutex);
if (is_peer_connected()) {
_put_msg(p_message, p_args);
}
}
void RemoteDebugger::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type) {
ErrorMessage oe;
oe.error = p_err;
oe.error_descr = p_descr;
oe.source_file = p_file;
oe.source_line = p_line;
oe.source_func = p_func;
oe.warning = p_type == ERR_HANDLER_WARNING;
uint64_t time = OS::get_singleton()->get_ticks_msec();
oe.hr = time / 3600000;
oe.min = (time / 60000) % 60;
oe.sec = (time / 1000) % 60;
oe.msec = time % 1000;
oe.callstack.append_array(script_debugger->get_error_stack_info());
if (flushing && Thread::get_caller_id() == flush_thread) // Can't handle recursive errors during flush.
return;
MutexLock lock(mutex);
if (oe.warning) {
warn_count++;
} else {
err_count++;
}
if (is_peer_connected()) {
if (oe.warning) {
if (warn_count > max_warnings_per_second) {
n_warnings_dropped++;
if (n_warnings_dropped == 1) {
// Only print one message about dropping per second
ErrorMessage overflow = _create_overflow_error("TOO_MANY_WARNINGS", "Too many warnings! Ignoring warnings for up to 1 second.");
errors.push_back(overflow);
}
} else {
errors.push_back(oe);
}
} else {
if (err_count > max_errors_per_second) {
n_errors_dropped++;
if (n_errors_dropped == 1) {
// Only print one message about dropping per second
ErrorMessage overflow = _create_overflow_error("TOO_MANY_ERRORS", "Too many errors! Ignoring errors for up to 1 second.");
errors.push_back(overflow);
}
} else {
errors.push_back(oe);
}
}
}
}
void RemoteDebugger::_send_stack_vars(List<String> &p_names, List<Variant> &p_vals, int p_type) {
DebuggerMarshalls::ScriptStackVariable stvar;
List<String>::Element *E = p_names.front();
List<Variant>::Element *F = p_vals.front();
while (E) {
stvar.name = E->get();
stvar.value = F->get();
stvar.type = p_type;
send_message("stack_frame_var", stvar.serialize());
E = E->next();
F = F->next();
}
}
Error RemoteDebugger::_try_capture(const String &p_msg, const Array &p_data, bool &r_captured) {
const int idx = p_msg.find(":");
r_captured = false;
if (idx < 0) { // No prefix, unknown message.
return OK;
}
const String cap = p_msg.substr(0, idx);
if (!has_capture(cap))
return ERR_UNAVAILABLE; // Unknown message...
const String msg = p_msg.substr(idx + 1);
return capture_parse(cap, msg, p_data, r_captured);
}
void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
//this function is called when there is a debugger break (bug on script)
//or when execution is paused from editor
if (script_debugger->is_skipping_breakpoints() && !p_is_error_breakpoint)
return;
ERR_FAIL_COND_MSG(!is_peer_connected(), "Script Debugger failed to connect, but being used anyway.");
ScriptLanguage *script_lang = script_debugger->get_break_language();
const String error_str = script_lang ? script_lang->debug_get_error() : "";
Array msg;
msg.push_back(p_can_continue);
msg.push_back(error_str);
send_message("debug_enter", msg);
servers_profiler->skip_profile_frame = true; // Avoid frame time spike in debug.
Input::MouseMode mouse_mode = Input::get_singleton()->get_mouse_mode();
if (mouse_mode != Input::MOUSE_MODE_VISIBLE)
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
uint64_t loop_begin_usec = 0;
uint64_t loop_time_sec = 0;
while (is_peer_connected()) {
loop_begin_usec = OS::get_singleton()->get_ticks_usec();
flush_output();
peer->poll();
if (peer->has_message()) {
Array cmd = peer->get_message();
ERR_CONTINUE(cmd.size() != 2);
ERR_CONTINUE(cmd[0].get_type() != Variant::STRING);
ERR_CONTINUE(cmd[1].get_type() != Variant::ARRAY);
String command = cmd[0];
Array data = cmd[1];
if (command == "step") {
script_debugger->set_depth(-1);
script_debugger->set_lines_left(1);
break;
} else if (command == "next") {
script_debugger->set_depth(0);
script_debugger->set_lines_left(1);
break;
} else if (command == "continue") {
script_debugger->set_depth(-1);
script_debugger->set_lines_left(-1);
OS::get_singleton()->move_window_to_foreground();
break;
} else if (command == "break") {
ERR_PRINT("Got break when already broke!");
break;
} else if (command == "get_stack_dump") {
ERR_FAIL_COND(!script_lang);
DebuggerMarshalls::ScriptStackDump dump;
int slc = script_lang->debug_get_stack_level_count();
for (int i = 0; i < slc; i++) {
ScriptLanguage::StackInfo frame;
frame.file = script_lang->debug_get_stack_level_source(i);
frame.line = script_lang->debug_get_stack_level_line(i);
frame.func = script_lang->debug_get_stack_level_function(i);
dump.frames.push_back(frame);
}
send_message("stack_dump", dump.serialize());
} else if (command == "get_stack_frame_vars") {
ERR_FAIL_COND(data.size() != 1);
ERR_FAIL_COND(!script_lang);
int lv = data[0];
List<String> members;
List<Variant> member_vals;
if (ScriptInstance *inst = script_lang->debug_get_stack_level_instance(lv)) {
members.push_back("self");
member_vals.push_back(inst->get_owner());
}
script_lang->debug_get_stack_level_members(lv, &members, &member_vals);
ERR_FAIL_COND(members.size() != member_vals.size());
List<String> locals;
List<Variant> local_vals;
script_lang->debug_get_stack_level_locals(lv, &locals, &local_vals);
ERR_FAIL_COND(locals.size() != local_vals.size());
List<String> globals;
List<Variant> globals_vals;
script_lang->debug_get_globals(&globals, &globals_vals);
ERR_FAIL_COND(globals.size() != globals_vals.size());
send_message("stack_frame_vars", Array());
_send_stack_vars(locals, local_vals, 0);
_send_stack_vars(members, member_vals, 1);
_send_stack_vars(globals, globals_vals, 2);
} else if (command == "reload_scripts") {
reload_all_scripts = true;
} else if (command == "breakpoint") {
ERR_FAIL_COND(data.size() < 3);
bool set = data[2];
if (set)
script_debugger->insert_breakpoint(data[1], data[0]);
else
script_debugger->remove_breakpoint(data[1], data[0]);
} else if (command == "set_skip_breakpoints") {
ERR_FAIL_COND(data.size() < 1);
script_debugger->set_skip_breakpoints(data[0]);
} else {
bool captured = false;
ERR_CONTINUE(_try_capture(command, data, captured) != OK);
if (!captured)
WARN_PRINT("Unknown message received from debugger: " + command);
}
} else {
OS::get_singleton()->delay_usec(10000);
OS::get_singleton()->process_and_drop_events();
}
// This is for the camera override to stay live even when the game is paused from the editor
loop_time_sec = (OS::get_singleton()->get_ticks_usec() - loop_begin_usec) / 1000000.0f;
VisualServer::get_singleton()->sync();
if (VisualServer::get_singleton()->has_changed()) {
VisualServer::get_singleton()->draw(true, loop_time_sec * Engine::get_singleton()->get_time_scale());
}
}
send_message("debug_exit", Array());
if (mouse_mode != Input::MOUSE_MODE_VISIBLE)
Input::get_singleton()->set_mouse_mode(mouse_mode);
}
void RemoteDebugger::poll_events(bool p_is_idle) {
if (peer.is_null())
return;
flush_output();
peer->poll();
while (peer->has_message()) {
Array arr = peer->get_message();
ERR_CONTINUE(arr.size() != 2);
ERR_CONTINUE(arr[0].get_type() != Variant::STRING);
ERR_CONTINUE(arr[1].get_type() != Variant::ARRAY);
const String cmd = arr[0];
const int idx = cmd.find(":");
bool parsed = false;
if (idx < 0) { // Not prefix, use scripts capture.
capture_parse("core", cmd, arr[1], parsed);
continue;
}
const String cap = cmd.substr(0, idx);
if (!has_capture(cap))
continue; // Unknown message...
const String msg = cmd.substr(idx + 1);
capture_parse(cap, msg, arr[1], parsed);
}
// Reload scripts during idle poll only.
if (p_is_idle && reload_all_scripts) {
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
ScriptServer::get_language(i)->reload_all_scripts();
}
reload_all_scripts = false;
}
}
Error RemoteDebugger::_core_capture(const String &p_cmd, const Array &p_data, bool &r_captured) {
r_captured = true;
if (p_cmd == "reload_scripts") {
reload_all_scripts = true;
} else if (p_cmd == "breakpoint") {
ERR_FAIL_COND_V(p_data.size() < 3, ERR_INVALID_DATA);
bool set = p_data[2];
if (set)
script_debugger->insert_breakpoint(p_data[1], p_data[0]);
else
script_debugger->remove_breakpoint(p_data[1], p_data[0]);
} else if (p_cmd == "set_skip_breakpoints") {
ERR_FAIL_COND_V(p_data.size() < 1, ERR_INVALID_DATA);
script_debugger->set_skip_breakpoints(p_data[0]);
} else if (p_cmd == "memory") {
_send_resource_usage();
} else if (p_cmd == "break") {
script_debugger->debug(script_debugger->get_break_language());
} else {
r_captured = false;
}
return OK;
}
Error RemoteDebugger::_profiler_capture(const String &p_cmd, const Array &p_data, bool &r_captured) {
r_captured = false;
ERR_FAIL_COND_V(p_data.size() < 1, ERR_INVALID_DATA);
ERR_FAIL_COND_V(p_data[0].get_type() != Variant::BOOL, ERR_INVALID_DATA);
ERR_FAIL_COND_V(!has_profiler(p_cmd), ERR_UNAVAILABLE);
Array opts;
if (p_data.size() > 1) { // Optional profiler parameters.
ERR_FAIL_COND_V(p_data[1].get_type() != Variant::ARRAY, ERR_INVALID_DATA);
opts = p_data[1];
}
r_captured = true;
profiler_enable(p_cmd, p_data[0], opts);
return OK;
}
RemoteDebugger *RemoteDebugger::create_for_uri(const String &p_uri) {
Ref<RemoteDebuggerPeer> peer = RemoteDebuggerPeer::create_from_uri(p_uri);
if (peer.is_valid())
return memnew(RemoteDebugger(peer));
return NULL;
}
RemoteDebugger::RemoteDebugger(Ref<RemoteDebuggerPeer> p_peer) {
peer = p_peer;
max_chars_per_second = GLOBAL_GET("network/limits/debugger/max_chars_per_second");
max_errors_per_second = GLOBAL_GET("network/limits/debugger/max_errors_per_second");
max_warnings_per_second = GLOBAL_GET("network/limits/debugger/max_warnings_per_second");
// Network Profiler
network_profiler = memnew(NetworkProfiler);
_bind_profiler("network", network_profiler);
// Servers Profiler (audio/physics/...)
servers_profiler = memnew(ServersProfiler);
_bind_profiler("servers", servers_profiler);
// Visual Profiler (cpu/gpu times)
visual_profiler = memnew(VisualProfiler);
_bind_profiler("visual", visual_profiler);
// Perfromance Profiler
Object *perf = Engine::get_singleton()->get_singleton_object("Performance");
if (perf) {
performance_profiler = memnew(PerformanceProfiler(perf));
_bind_profiler("performance", performance_profiler);
profiler_enable("performance", true);
}
// Core and profiler captures.
Capture core_cap(this,
[](void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) {
return ((RemoteDebugger *)p_user)->_core_capture(p_cmd, p_data, r_captured);
});
register_message_capture("core", core_cap);
Capture profiler_cap(this,
[](void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) {
return ((RemoteDebugger *)p_user)->_profiler_capture(p_cmd, p_data, r_captured);
});
register_message_capture("profiler", profiler_cap);
// Error handlers
phl.printfunc = _print_handler;
phl.userdata = this;
add_print_handler(&phl);
eh.errfunc = _err_handler;
eh.userdata = this;
add_error_handler(&eh);
}
RemoteDebugger::~RemoteDebugger() {
remove_print_handler(&phl);
remove_error_handler(&eh);
EngineDebugger::get_singleton()->unregister_profiler("servers");
EngineDebugger::get_singleton()->unregister_profiler("network");
EngineDebugger::get_singleton()->unregister_profiler("visual");
if (EngineDebugger::has_profiler("performance")) {
EngineDebugger::get_singleton()->unregister_profiler("performance");
}
memdelete(servers_profiler);
memdelete(network_profiler);
memdelete(visual_profiler);
if (performance_profiler)
memdelete(performance_profiler);
}

View file

@ -0,0 +1,114 @@
/*************************************************************************/
/* remote_debugger.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef REMOTE_DEBUGGER_H
#define REMOTE_DEBUGGER_H
#include "core/array.h"
#include "core/debugger/debugger_marshalls.h"
#include "core/debugger/engine_debugger.h"
#include "core/debugger/remote_debugger_peer.h"
#include "core/object.h"
#include "core/string_name.h"
#include "core/ustring.h"
class RemoteDebugger : public EngineDebugger {
private:
typedef DebuggerMarshalls::OutputError ErrorMessage;
struct NetworkProfiler;
struct ServersProfiler;
struct ScriptsProfiler;
struct VisualProfiler;
struct PerformanceProfiler;
NetworkProfiler *network_profiler = NULL;
ServersProfiler *servers_profiler = NULL;
VisualProfiler *visual_profiler = NULL;
PerformanceProfiler *performance_profiler = NULL;
Ref<RemoteDebuggerPeer> peer;
List<String> output_strings;
List<ErrorMessage> errors;
int n_messages_dropped = 0;
int max_errors_per_second = 0;
int max_chars_per_second = 0;
int max_warnings_per_second = 0;
int n_errors_dropped = 0;
int n_warnings_dropped = 0;
int char_count = 0;
int err_count = 0;
int warn_count = 0;
int last_reset = 0;
bool reload_all_scripts = false;
// Make handlers and send_message thread safe.
Mutex mutex;
bool flushing = false;
Thread::ID flush_thread = 0;
PrintHandlerList phl;
static void _print_handler(void *p_this, const String &p_string, bool p_error);
ErrorHandlerList eh;
static void _err_handler(void *p_this, const char *p_func, const char *p_file, int p_line, const char *p_err, const char *p_descr, ErrorHandlerType p_type);
ErrorMessage _create_overflow_error(const String &p_what, const String &p_descr);
Error _put_msg(String p_message, Array p_data);
bool is_peer_connected() { return peer->is_peer_connected(); }
void flush_output();
void _send_resource_usage();
void _send_stack_vars(List<String> &p_names, List<Variant> &p_vals, int p_type);
Error _profiler_capture(const String &p_cmd, const Array &p_data, bool &r_captured);
Error _core_capture(const String &p_cmd, const Array &p_data, bool &r_captured);
template <typename T>
void _bind_profiler(const String &p_name, T *p_prof);
Error _try_capture(const String &p_name, const Array &p_data, bool &r_captured);
public:
static RemoteDebugger *create_for_uri(const String &p_uri);
// Overrides
void poll_events(bool p_is_idle);
void send_message(const String &p_message, const Array &p_args);
void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type);
void debug(bool p_can_continue = true, bool p_is_error_breakpoint = false);
RemoteDebugger(Ref<RemoteDebuggerPeer> p_peer);
~RemoteDebugger();
};
#endif // REMOTE_DEBUGGER_H

View file

@ -0,0 +1,242 @@
/*************************************************************************/
/* remote_debugger_peer.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "remote_debugger_peer.h"
#include "core/io/marshalls.h"
#include "core/os/os.h"
#include "core/project_settings.h"
bool RemoteDebuggerPeerTCP::is_peer_connected() {
return connected;
}
bool RemoteDebuggerPeerTCP::has_message() {
return in_queue.size() > 0;
}
Array RemoteDebuggerPeerTCP::get_message() {
MutexLock lock(mutex);
ERR_FAIL_COND_V(!has_message(), Array());
Array out = in_queue[0];
in_queue.pop_front();
return out;
}
Error RemoteDebuggerPeerTCP::put_message(const Array &p_arr) {
MutexLock lock(mutex);
if (out_queue.size() >= max_queued_messages)
return ERR_OUT_OF_MEMORY;
out_queue.push_back(p_arr);
return OK;
}
int RemoteDebuggerPeerTCP::get_max_message_size() const {
return 8 << 20; // 8 MiB
}
void RemoteDebuggerPeerTCP::close() {
if (thread) {
running = false;
Thread::wait_to_finish(thread);
memdelete(thread);
thread = NULL;
}
tcp_client->disconnect_from_host();
out_buf.resize(0);
in_buf.resize(0);
}
RemoteDebuggerPeerTCP::RemoteDebuggerPeerTCP(Ref<StreamPeerTCP> p_tcp) {
// This means remote debugger takes 16 MiB just because it exists...
in_buf.resize((8 << 20) + 4); // 8 MiB should be way more than enough (need 4 extra bytes for encoding packet size).
out_buf.resize(8 << 20); // 8 MiB should be way more than enough
tcp_client = p_tcp;
if (tcp_client.is_valid()) { // Attaching to an already connected stream.
connected = true;
#ifndef NO_THREADS
running = true;
thread = Thread::create(_thread_func, this);
#endif
} else {
tcp_client.instance();
}
}
RemoteDebuggerPeerTCP::~RemoteDebuggerPeerTCP() {
close();
}
void RemoteDebuggerPeerTCP::_write_out() {
while (tcp_client->poll(NetSocket::POLL_TYPE_OUT) == OK) {
uint8_t *buf = out_buf.ptrw();
if (out_left <= 0) {
if (out_queue.size() == 0)
break; // Nothing left to send
mutex.lock();
Variant var = out_queue[0];
out_queue.pop_front();
mutex.unlock();
int size = 0;
Error err = encode_variant(var, NULL, size);
ERR_CONTINUE(err != OK || size > out_buf.size() - 4); // 4 bytes separator.
encode_uint32(size, buf);
encode_variant(var, buf + 4, size);
out_left = size + 4;
out_pos = 0;
}
int sent = 0;
tcp_client->put_partial_data(buf + out_pos, out_left, sent);
out_left -= sent;
out_pos += sent;
}
}
void RemoteDebuggerPeerTCP::_read_in() {
while (tcp_client->poll(NetSocket::POLL_TYPE_IN) == OK) {
uint8_t *buf = in_buf.ptrw();
if (in_left <= 0) {
if (in_queue.size() > max_queued_messages) {
break; // Too many messages already in queue.
}
if (tcp_client->get_available_bytes() < 4) {
break; // Need 4 more bytes.
}
uint32_t size = 0;
int read = 0;
Error err = tcp_client->get_partial_data((uint8_t *)&size, 4, read);
ERR_CONTINUE(read != 4 || err != OK || size > (uint32_t)in_buf.size());
in_left = size;
in_pos = 0;
}
int read = 0;
tcp_client->get_partial_data(buf + in_pos, in_left, read);
in_left -= read;
in_pos += read;
if (in_left == 0) {
Variant var;
Error err = decode_variant(var, buf, in_pos, &read);
ERR_CONTINUE(read != in_pos || err != OK);
ERR_CONTINUE_MSG(var.get_type() != Variant::ARRAY, "Malformed packet received, not an Array.");
mutex.lock();
in_queue.push_back(var);
mutex.unlock();
}
}
}
Error RemoteDebuggerPeerTCP::connect_to_host(const String &p_host, uint16_t p_port) {
IP_Address ip;
if (p_host.is_valid_ip_address())
ip = p_host;
else
ip = IP::get_singleton()->resolve_hostname(p_host);
int port = p_port;
const int tries = 6;
int waits[tries] = { 1, 10, 100, 1000, 1000, 1000 };
tcp_client->connect_to_host(ip, port);
for (int i = 0; i < tries; i++) {
if (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED) {
print_verbose("Remote Debugger: Connected!");
break;
} else {
const int ms = waits[i];
OS::get_singleton()->delay_usec(ms * 1000);
print_verbose("Remote Debugger: Connection failed with status: '" + String::num(tcp_client->get_status()) + "', retrying in " + String::num(ms) + " msec.");
};
};
if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
ERR_PRINT("Remote Debugger: Unable to connect. Status: " + String::num(tcp_client->get_status()) + ".");
return FAILED;
};
connected = true;
#ifndef NO_THREADS
running = true;
thread = Thread::create(_thread_func, this);
#endif
return OK;
}
void RemoteDebuggerPeerTCP::_thread_func(void *p_ud) {
RemoteDebuggerPeerTCP *peer = (RemoteDebuggerPeerTCP *)p_ud;
while (peer->running && peer->is_peer_connected()) {
peer->_poll();
if (!peer->is_peer_connected())
break;
peer->tcp_client->poll(NetSocket::POLL_TYPE_IN_OUT, 1);
}
}
void RemoteDebuggerPeerTCP::poll() {
#ifdef NO_THREADS
_poll();
#endif
}
void RemoteDebuggerPeerTCP::_poll() {
if (connected) {
_write_out();
_read_in();
connected = tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED;
}
}
Ref<RemoteDebuggerPeer> RemoteDebuggerPeer::create_from_uri(const String p_uri) {
if (!p_uri.begins_with("tcp://"))
return Ref<RemoteDebuggerPeer>(); // Only TCP supported for now, more to come.
String debug_host = p_uri.replace("tcp://", "");
uint16_t debug_port = 6007;
if (debug_host.find(":") != -1) {
int sep_pos = debug_host.find_last(":");
debug_port = debug_host.substr(sep_pos + 1).to_int();
debug_host = debug_host.substr(0, sep_pos);
}
Ref<RemoteDebuggerPeerTCP> peer = Ref<RemoteDebuggerPeer>(memnew(RemoteDebuggerPeerTCP));
Error err = peer->connect_to_host(debug_host, debug_port);
if (err != OK)
return Ref<RemoteDebuggerPeer>();
return peer;
}
RemoteDebuggerPeer::RemoteDebuggerPeer() {
max_queued_messages = (int)GLOBAL_GET("network/limits/debugger/max_queued_messages");
}

View file

@ -1,5 +1,5 @@
/*************************************************************************/
/* script_debugger_local.h */
/* remote_debugger_peer.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -28,40 +28,67 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef SCRIPT_DEBUGGER_LOCAL_H
#define SCRIPT_DEBUGGER_LOCAL_H
#ifndef REMOTE_DEBUGGER_PEER_H
#define REMOTE_DEBUGGER_PEER_H
#include "core/list.h"
#include "core/script_language.h"
#include "core/io/stream_peer_tcp.h"
#include "core/os/mutex.h"
#include "core/os/thread.h"
#include "core/reference.h"
#include "core/ustring.h"
class ScriptDebuggerLocal : public ScriptDebugger {
bool profiling;
float frame_time, idle_time, physics_time, physics_frame_time;
uint64_t idle_accum;
String target_function;
Map<String, String> options;
Vector<ScriptLanguage::ProfilingInfo> pinfo;
Pair<String, int> to_breakpoint(const String &p_line);
void print_variables(const List<String> &names, const List<Variant> &values, const String &variable_prefix);
class RemoteDebuggerPeer : public Reference {
protected:
int max_queued_messages = 4096;
public:
void debug(ScriptLanguage *p_script, bool p_can_continue, bool p_is_error_breakpoint);
virtual void send_message(const String &p_message, const Array &p_args);
virtual void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type, const Vector<ScriptLanguage::StackInfo> &p_stack_info);
static Ref<RemoteDebuggerPeer> create_from_uri(const String p_uri);
virtual bool is_peer_connected() = 0;
virtual bool has_message() = 0;
virtual Error put_message(const Array &p_arr) = 0;
virtual Array get_message() = 0;
virtual void close() = 0;
virtual void poll() = 0;
virtual int get_max_message_size() const = 0;
virtual bool is_profiling() const { return profiling; }
virtual void add_profiling_frame_data(const StringName &p_name, const Array &p_data) {}
virtual void idle_poll();
virtual void profiling_start();
virtual void profiling_end();
virtual void profiling_set_frame_times(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time);
ScriptDebuggerLocal();
RemoteDebuggerPeer();
};
#endif // SCRIPT_DEBUGGER_LOCAL_H
class RemoteDebuggerPeerTCP : public RemoteDebuggerPeer {
private:
Ref<StreamPeerTCP> tcp_client;
Mutex mutex;
Thread *thread = NULL;
List<Array> in_queue;
List<Array> out_queue;
int out_left = 0;
int out_pos = 0;
Vector<uint8_t> out_buf;
int in_left = 0;
int in_pos = 0;
Vector<uint8_t> in_buf;
bool connected = false;
bool running = false;
static void _thread_func(void *p_ud);
void _poll();
void _write_out();
void _read_in();
public:
Error connect_to_host(const String &p_host, uint16_t p_port);
void poll();
bool is_peer_connected();
bool has_message();
Array get_message();
Error put_message(const Array &p_arr);
int get_max_message_size() const;
void close();
RemoteDebuggerPeerTCP(Ref<StreamPeerTCP> p_stream = Ref<StreamPeerTCP>());
~RemoteDebuggerPeerTCP();
};
#endif // REMOTE_DEBUGGER_PEER_H

View file

@ -0,0 +1,123 @@
/*************************************************************************/
/* script_debugger.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "script_debugger.h"
#include "core/debugger/engine_debugger.h"
void ScriptDebugger::set_lines_left(int p_left) {
lines_left = p_left;
}
int ScriptDebugger::get_lines_left() const {
return lines_left;
}
void ScriptDebugger::set_depth(int p_depth) {
depth = p_depth;
}
int ScriptDebugger::get_depth() const {
return depth;
}
void ScriptDebugger::insert_breakpoint(int p_line, const StringName &p_source) {
if (!breakpoints.has(p_line))
breakpoints[p_line] = Set<StringName>();
breakpoints[p_line].insert(p_source);
}
void ScriptDebugger::remove_breakpoint(int p_line, const StringName &p_source) {
if (!breakpoints.has(p_line))
return;
breakpoints[p_line].erase(p_source);
if (breakpoints[p_line].size() == 0)
breakpoints.erase(p_line);
}
bool ScriptDebugger::is_breakpoint(int p_line, const StringName &p_source) const {
if (!breakpoints.has(p_line))
return false;
return breakpoints[p_line].has(p_source);
}
bool ScriptDebugger::is_breakpoint_line(int p_line) const {
return breakpoints.has(p_line);
}
String ScriptDebugger::breakpoint_find_source(const String &p_source) const {
return p_source;
}
void ScriptDebugger::clear_breakpoints() {
breakpoints.clear();
}
void ScriptDebugger::set_skip_breakpoints(bool p_skip_breakpoints) {
skip_breakpoints = p_skip_breakpoints;
}
bool ScriptDebugger::is_skipping_breakpoints() {
return skip_breakpoints;
}
void ScriptDebugger::debug(ScriptLanguage *p_lang, bool p_can_continue, bool p_is_error_breakpoint) {
ScriptLanguage *prev = break_lang;
break_lang = p_lang;
EngineDebugger::get_singleton()->debug(p_can_continue, p_is_error_breakpoint);
break_lang = prev;
}
void ScriptDebugger::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type, const Vector<StackInfo> &p_stack_info) {
// Store stack info, this is ugly, but allows us to separate EngineDebugger and ScriptDebugger. There might be a better way.
error_stack_info.append_array(p_stack_info);
EngineDebugger::get_singleton()->send_error(p_func, p_file, p_line, p_err, p_descr, p_type);
error_stack_info.resize(0);
}
Vector<ScriptLanguage::StackInfo> ScriptDebugger::get_error_stack_info() const {
return error_stack_info;
}
ScriptLanguage *ScriptDebugger::get_break_language() const {
return break_lang;
}

View file

@ -0,0 +1,80 @@
/*************************************************************************/
/* script_debugger.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef SCRIPT_DEBUGGER_H
#define SCRIPT_DEBUGGER_H
#include "core/map.h"
#include "core/script_language.h"
#include "core/set.h"
#include "core/string_name.h"
#include "core/vector.h"
class ScriptDebugger {
typedef ScriptLanguage::StackInfo StackInfo;
int lines_left = -1;
int depth = -1;
bool skip_breakpoints = false;
Map<int, Set<StringName> > breakpoints;
ScriptLanguage *break_lang = NULL;
Vector<StackInfo> error_stack_info;
public:
void set_lines_left(int p_left);
int get_lines_left() const;
void set_depth(int p_depth);
int get_depth() const;
String breakpoint_find_source(const String &p_source) const;
void set_break_language(ScriptLanguage *p_lang) { break_lang = p_lang; }
ScriptLanguage *get_break_language() { return break_lang; }
void set_skip_breakpoints(bool p_skip_breakpoints);
bool is_skipping_breakpoints();
void insert_breakpoint(int p_line, const StringName &p_source);
void remove_breakpoint(int p_line, const StringName &p_source);
bool is_breakpoint(int p_line, const StringName &p_source) const;
bool is_breakpoint_line(int p_line) const;
void clear_breakpoints();
const Map<int, Set<StringName> > &get_breakpoints() const { return breakpoints; }
void debug(ScriptLanguage *p_lang, bool p_can_continue = true, bool p_is_error_breakpoint = false);
ScriptLanguage *get_break_language() const;
void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type, const Vector<StackInfo> &p_stack_info);
Vector<StackInfo> get_error_stack_info() const;
ScriptDebugger() {}
};
#endif

View file

@ -30,6 +30,7 @@
#include "multiplayer_api.h"
#include "core/debugger/engine_debugger.h"
#include "core/io/marshalls.h"
#include "scene/main/node.h"
#include <stdint.h>
@ -172,6 +173,28 @@ Ref<NetworkedMultiplayerPeer> MultiplayerAPI::get_network_peer() const {
return network_peer;
}
#ifdef DEBUG_ENABLED
void _profile_node_data(const String &p_what, ObjectID p_id) {
if (EngineDebugger::is_profiling("multiplayer")) {
Array values;
values.push_back("node");
values.push_back(p_id);
values.push_back(p_what);
EngineDebugger::profiler_add_frame_data("multiplayer", values);
}
}
void _profile_bandwidth_data(const String &p_inout, int p_size) {
if (EngineDebugger::is_profiling("multiplayer")) {
Array values;
values.push_back("bandwidth");
values.push_back(p_inout);
values.push_back(OS::get_singleton()->get_ticks_msec());
values.push_back(p_size);
EngineDebugger::profiler_add_frame_data("multiplayer", values);
}
}
#endif
// Returns the packet size stripping the node path added when the node is not yet cached.
int get_packet_len(uint32_t p_node_target, int p_packet_len) {
if (p_node_target & 0x80000000) {
@ -188,11 +211,7 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_
ERR_FAIL_COND_MSG(p_packet_len < 1, "Invalid packet received. Size too small.");
#ifdef DEBUG_ENABLED
if (profiling) {
bandwidth_incoming_data.write[bandwidth_incoming_pointer].timestamp = OS::get_singleton()->get_ticks_msec();
bandwidth_incoming_data.write[bandwidth_incoming_pointer].packet_size = p_packet_len;
bandwidth_incoming_pointer = (bandwidth_incoming_pointer + 1) % bandwidth_incoming_data.size();
}
_profile_bandwidth_data("in", p_packet_len);
#endif
// Extract the `packet_type` from the LSB three bits:
@ -381,11 +400,7 @@ void MultiplayerAPI::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id,
argp.resize(argc);
#ifdef DEBUG_ENABLED
if (profiling) {
ObjectID id = p_node->get_instance_id();
_init_node_profile(id);
profiler_frame_data[id].incoming_rpc += 1;
}
_profile_node_data("in_rpc", p_node->get_instance_id());
#endif
if (byte_only) {
@ -437,11 +452,7 @@ void MultiplayerAPI::_process_rset(Node *p_node, const uint16_t p_rpc_property_i
ERR_FAIL_COND_MSG(!can_call, "RSET '" + String(name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)rset_mode) + ", master is " + itos(p_node->get_network_master()) + ".");
#ifdef DEBUG_ENABLED
if (profiling) {
ObjectID id = p_node->get_instance_id();
_init_node_profile(id);
profiler_frame_data[id].incoming_rset += 1;
}
_profile_node_data("in_rset", p_node->get_instance_id());
#endif
Variant value;
@ -912,11 +923,7 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, bool p_unreliable, bool p
packet_cache.write[0] = command_type + (node_id_compression << NODE_ID_COMPRESSION_SHIFT) + (name_id_compression << NAME_ID_COMPRESSION_SHIFT) + ((byte_only_or_no_args ? 1 : 0) << BYTE_ONLY_OR_NO_ARGS_SHIFT);
#ifdef DEBUG_ENABLED
if (profiling) {
bandwidth_outgoing_data.write[bandwidth_outgoing_pointer].timestamp = OS::get_singleton()->get_ticks_msec();
bandwidth_outgoing_data.write[bandwidth_outgoing_pointer].packet_size = ofs;
bandwidth_outgoing_pointer = (bandwidth_outgoing_pointer + 1) % bandwidth_outgoing_data.size();
}
_profile_bandwidth_data("out", ofs);
#endif
// Take chance and set transfer mode, since all send methods will use it.
@ -1031,11 +1038,7 @@ void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const
if (!skip_rpc) {
#ifdef DEBUG_ENABLED
if (profiling) {
ObjectID id = p_node->get_instance_id();
_init_node_profile(id);
profiler_frame_data[id].outgoing_rpc += 1;
}
_profile_node_data("out_rpc", p_node->get_instance_id());
#endif
_send_rpc(p_node, p_peer_id, p_unreliable, false, p_method, p_arg, p_argcount);
@ -1130,11 +1133,7 @@ void MultiplayerAPI::rsetp(Node *p_node, int p_peer_id, bool p_unreliable, const
}
#ifdef DEBUG_ENABLED
if (profiling) {
ObjectID id = p_node->get_instance_id();
_init_node_profile(id);
profiler_frame_data[id].outgoing_rset += 1;
}
_profile_node_data("out_rset", p_node->get_instance_id());
#endif
const Variant *vptr = &p_value;
@ -1220,95 +1219,6 @@ bool MultiplayerAPI::is_object_decoding_allowed() const {
return allow_object_decoding;
}
void MultiplayerAPI::profiling_start() {
#ifdef DEBUG_ENABLED
profiling = true;
profiler_frame_data.clear();
bandwidth_incoming_pointer = 0;
bandwidth_incoming_data.resize(16384); // ~128kB
for (int i = 0; i < bandwidth_incoming_data.size(); ++i) {
bandwidth_incoming_data.write[i].packet_size = -1;
}
bandwidth_outgoing_pointer = 0;
bandwidth_outgoing_data.resize(16384); // ~128kB
for (int i = 0; i < bandwidth_outgoing_data.size(); ++i) {
bandwidth_outgoing_data.write[i].packet_size = -1;
}
#endif
}
void MultiplayerAPI::profiling_end() {
#ifdef DEBUG_ENABLED
profiling = false;
bandwidth_incoming_data.clear();
bandwidth_outgoing_data.clear();
#endif
}
int MultiplayerAPI::get_profiling_frame(ProfilingInfo *r_info) {
int i = 0;
#ifdef DEBUG_ENABLED
for (Map<ObjectID, ProfilingInfo>::Element *E = profiler_frame_data.front(); E; E = E->next()) {
r_info[i] = E->get();
++i;
}
profiler_frame_data.clear();
#endif
return i;
}
int MultiplayerAPI::get_incoming_bandwidth_usage() {
#ifdef DEBUG_ENABLED
return _get_bandwidth_usage(bandwidth_incoming_data, bandwidth_incoming_pointer);
#else
return 0;
#endif
}
int MultiplayerAPI::get_outgoing_bandwidth_usage() {
#ifdef DEBUG_ENABLED
return _get_bandwidth_usage(bandwidth_outgoing_data, bandwidth_outgoing_pointer);
#else
return 0;
#endif
}
#ifdef DEBUG_ENABLED
int MultiplayerAPI::_get_bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) {
int total_bandwidth = 0;
uint32_t timestamp = OS::get_singleton()->get_ticks_msec();
uint32_t final_timestamp = timestamp - 1000;
int i = (p_pointer + p_buffer.size() - 1) % p_buffer.size();
while (i != p_pointer && p_buffer[i].packet_size > 0) {
if (p_buffer[i].timestamp < final_timestamp) {
return total_bandwidth;
}
total_bandwidth += p_buffer[i].packet_size;
i = (i + p_buffer.size() - 1) % p_buffer.size();
}
ERR_FAIL_COND_V_MSG(i == p_pointer, total_bandwidth, "Reached the end of the bandwidth profiler buffer, values might be inaccurate.");
return total_bandwidth;
}
void MultiplayerAPI::_init_node_profile(ObjectID p_node) {
if (profiler_frame_data.has(p_node))
return;
profiler_frame_data.insert(p_node, ProfilingInfo());
profiler_frame_data[p_node].node = p_node;
profiler_frame_data[p_node].node_path = Object::cast_to<Node>(ObjectDB::get_instance(p_node))->get_path();
profiler_frame_data[p_node].incoming_rpc = 0;
profiler_frame_data[p_node].incoming_rset = 0;
profiler_frame_data[p_node].outgoing_rpc = 0;
profiler_frame_data[p_node].outgoing_rset = 0;
}
#endif
void MultiplayerAPI::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_root_node", "node"), &MultiplayerAPI::set_root_node);
ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode"), &MultiplayerAPI::send_bytes, DEFVAL(NetworkedMultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE));
@ -1352,9 +1262,6 @@ MultiplayerAPI::MultiplayerAPI() :
allow_object_decoding(false) {
rpc_sender_id = 0;
root_node = NULL;
#ifdef DEBUG_ENABLED
profiling = false;
#endif
clear();
}

View file

@ -38,16 +38,6 @@ class MultiplayerAPI : public Reference {
GDCLASS(MultiplayerAPI, Reference);
public:
struct ProfilingInfo {
ObjectID node;
String node_path;
int incoming_rpc;
int incoming_rset;
int outgoing_rpc;
int outgoing_rset;
};
private:
//path sent caches
struct PathSentCache {
@ -65,23 +55,6 @@ private:
Map<int, NodeInfo> nodes;
};
#ifdef DEBUG_ENABLED
struct BandwidthFrame {
uint32_t timestamp;
int packet_size;
};
int bandwidth_incoming_pointer;
Vector<BandwidthFrame> bandwidth_incoming_data;
int bandwidth_outgoing_pointer;
Vector<BandwidthFrame> bandwidth_outgoing_data;
Map<ObjectID, ProfilingInfo> profiler_frame_data;
bool profiling;
void _init_node_profile(ObjectID p_node);
int _get_bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer);
#endif
Ref<NetworkedMultiplayerPeer> network_peer;
int rpc_sender_id;
Set<int> connected_peers;
@ -169,13 +142,6 @@ public:
void set_allow_object_decoding(bool p_enable);
bool is_object_decoding_allowed() const;
void profiling_start();
void profiling_end();
int get_profiling_frame(ProfilingInfo *r_info);
int get_incoming_bandwidth_usage();
int get_outgoing_bandwidth_usage();
MultiplayerAPI();
~MultiplayerAPI();
};

View file

@ -1,209 +0,0 @@
/*************************************************************************/
/* script_debugger_peer.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "script_debugger_peer.h"
#include "core/io/packet_peer.h"
#include "core/io/stream_peer_tcp.h"
#include "core/os/mutex.h"
#include "core/os/os.h"
#include "core/os/thread.h"
class ScriptDebuggerPeerTCP : public ScriptDebuggerPeer {
private:
enum {
QUEUE_MAX = 2048,
POLL_USEC_MAX = 100,
};
Ref<StreamPeerTCP> tcp_client = Ref<StreamPeerTCP>(memnew(StreamPeerTCP));
Ref<PacketPeerStream> packet_peer = Ref<PacketPeerStream>(memnew(PacketPeerStream));
Mutex mutex;
Thread *thread = NULL;
List<Array> in_queue;
List<Array> out_queue;
bool connected = false;
bool running = false;
static void _thread_func(void *p_ud);
void _poll();
public:
void poll();
Error connect_to_host(const String &p_host, uint16_t p_port);
bool is_peer_connected() {
return connected;
}
bool has_message() {
return in_queue.size() > 0;
}
Array get_message() {
MutexLock lock(mutex);
ERR_FAIL_COND_V(!has_message(), Array());
Array out = in_queue[0];
in_queue.pop_front();
return out;
}
Error put_message(const Array &p_arr) {
MutexLock lock(mutex);
if (out_queue.size() >= 2048) // XXX Should we keep track of size instead?
return ERR_OUT_OF_MEMORY;
out_queue.push_back(p_arr);
return OK;
}
void close() {
if (thread) {
running = false;
Thread::wait_to_finish(thread);
memdelete(thread);
thread = NULL;
}
MutexLock lock(mutex);
tcp_client->disconnect_from_host();
packet_peer->set_stream_peer(Ref<StreamPeer>());
}
ScriptDebuggerPeerTCP() {
packet_peer->set_output_buffer_max_size((1024 * 1024 * 8) - 4); // 8 MiB should be way more than enough, minus 4 bytes for separator.
}
~ScriptDebuggerPeerTCP() {
close();
}
};
Error ScriptDebuggerPeerTCP::connect_to_host(const String &p_host, uint16_t p_port) {
IP_Address ip;
if (p_host.is_valid_ip_address())
ip = p_host;
else
ip = IP::get_singleton()->resolve_hostname(p_host);
int port = p_port;
const int tries = 6;
int waits[tries] = { 1, 10, 100, 1000, 1000, 1000 };
tcp_client->connect_to_host(ip, port);
for (int i = 0; i < tries; i++) {
if (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED) {
print_verbose("Remote Debugger: Connected!");
break;
} else {
const int ms = waits[i];
OS::get_singleton()->delay_usec(ms * 1000);
print_verbose("Remote Debugger: Connection failed with status: '" + String::num(tcp_client->get_status()) + "', retrying in " + String::num(ms) + " msec.");
};
};
if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
ERR_PRINT("Remote Debugger: Unable to connect. Status: " + String::num(tcp_client->get_status()) + ".");
return FAILED;
};
packet_peer->set_stream_peer(tcp_client);
connected = true;
#ifndef NO_THREADS
running = true;
thread = Thread::create(_thread_func, this);
#endif
return OK;
}
void ScriptDebuggerPeerTCP::_thread_func(void *p_ud) {
ScriptDebuggerPeerTCP *peer = (ScriptDebuggerPeerTCP *)p_ud;
while (peer->running && peer->is_peer_connected()) {
peer->_poll();
if (!peer->is_peer_connected())
break;
OS::get_singleton()->delay_usec(100);
}
}
void ScriptDebuggerPeerTCP::poll() {
#ifdef NO_THREADS
_poll();
#endif
}
void ScriptDebuggerPeerTCP::_poll() {
MutexLock lock(mutex);
// Poll in
uint64_t ticks = OS::get_singleton()->get_ticks_usec();
while (connected && packet_peer->get_available_packet_count() > 0 && in_queue.size() < QUEUE_MAX && OS::get_singleton()->get_ticks_usec() - ticks < POLL_USEC_MAX) {
Variant var;
const Error err = packet_peer->get_var(var);
connected = tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED;
if (err != OK) {
ERR_PRINT("Error reading variant from peer");
break;
}
ERR_CONTINUE_MSG(var.get_type() != Variant::ARRAY, "Malformed packet received, not an Array.");
in_queue.push_back(var);
}
// Poll out
ticks = OS::get_singleton()->get_ticks_usec();
while (connected && out_queue.size() > 0 && OS::get_singleton()->get_ticks_usec() - ticks < POLL_USEC_MAX) {
Array arr = out_queue[0];
out_queue.pop_front();
const Error err = packet_peer->put_var(arr);
connected = tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED;
if (err != OK) {
ERR_PRINT("Error writing variant to peer");
break;
}
}
}
Ref<ScriptDebuggerPeer> ScriptDebuggerPeer::create_from_uri(const String p_uri) {
String debug_host = p_uri;
uint16_t debug_port = 6007;
if (debug_host.find(":") != -1) {
int sep_pos = debug_host.find_last(":");
debug_port = debug_host.substr(sep_pos + 1, debug_host.length()).to_int();
debug_host = debug_host.substr(0, sep_pos);
}
Ref<ScriptDebuggerPeerTCP> peer = Ref<ScriptDebuggerPeer>(memnew(ScriptDebuggerPeerTCP));
Error err = peer->connect_to_host(debug_host, debug_port);
if (err != OK)
return Ref<ScriptDebuggerPeer>();
return peer;
}

File diff suppressed because it is too large Load diff

View file

@ -1,315 +0,0 @@
/*************************************************************************/
/* script_debugger_remote.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef SCRIPT_DEBUGGER_REMOTE_H
#define SCRIPT_DEBUGGER_REMOTE_H
#include "core/list.h"
#include "core/os/os.h"
#include "core/script_debugger_peer.h"
#include "core/script_language.h"
class ScriptDebuggerRemote : public ScriptDebugger {
public:
class ResourceInfo {
public:
String path;
String format;
String type;
RID id;
int vram;
bool operator<(const ResourceInfo &p_img) const { return vram == p_img.vram ? id < p_img.id : vram > p_img.vram; }
ResourceInfo() {
vram = 0;
}
};
class ResourceUsage {
public:
List<ResourceInfo> infos;
Array serialize();
bool deserialize(const Array &p_arr);
};
class FrameInfo {
public:
StringName name;
float self_time;
float total_time;
FrameInfo() {
self_time = 0;
total_time = 0;
}
};
class FrameFunction {
public:
int sig_id;
int call_count;
StringName name;
float self_time;
float total_time;
FrameFunction() {
sig_id = -1;
call_count = 0;
self_time = 0;
total_time = 0;
}
};
class ScriptStackVariable {
public:
String name;
Variant value;
int type;
ScriptStackVariable() {
type = -1;
}
Array serialize(int max_size = 1 << 20); // 1 MiB default.
bool deserialize(const Array &p_arr);
};
class ScriptStackDump {
public:
List<ScriptLanguage::StackInfo> frames;
ScriptStackDump() {}
Array serialize();
bool deserialize(const Array &p_arr);
};
class Message {
public:
String message;
Array data;
Message() {}
};
class OutputError {
public:
int hr;
int min;
int sec;
int msec;
String source_file;
String source_func;
int source_line;
String error;
String error_descr;
bool warning;
Vector<ScriptLanguage::StackInfo> callstack;
OutputError() {
hr = -1;
min = -1;
sec = -1;
msec = -1;
source_line = -1;
warning = false;
}
Array serialize();
bool deserialize(const Array &p_arr);
};
struct FrameData {
StringName name;
Array data;
};
class ProfilerSignature {
public:
StringName name;
int id;
Array serialize();
bool deserialize(const Array &p_arr);
ProfilerSignature() {
id = -1;
};
};
class ProfilerFrame {
public:
int frame_number;
float frame_time;
float idle_time;
float physics_time;
float physics_frame_time;
float script_time;
Vector<FrameData> frames_data;
Vector<FrameFunction> frame_functions;
ProfilerFrame() {
frame_number = 0;
frame_time = 0;
idle_time = 0;
physics_time = 0;
physics_frame_time = 0;
}
Array serialize();
bool deserialize(const Array &p_arr);
};
class NetworkProfilerFrame {
public:
Vector<MultiplayerAPI::ProfilingInfo> infos;
Array serialize();
bool deserialize(const Array &p_arr);
NetworkProfilerFrame(){};
};
protected:
struct ProfileInfoSort {
bool operator()(ScriptLanguage::ProfilingInfo *A, ScriptLanguage::ProfilingInfo *B) const {
return A->total_time < B->total_time;
}
};
Vector<ScriptLanguage::ProfilingInfo> profile_info;
Vector<ScriptLanguage::ProfilingInfo *> profile_info_ptrs;
Vector<MultiplayerAPI::ProfilingInfo> network_profile_info;
Map<StringName, int> profiler_function_signature_map;
float frame_time, idle_time, physics_time, physics_frame_time;
bool profiling;
bool visual_profiling;
bool network_profiling;
int max_frame_functions;
bool skip_profile_frame;
bool reload_all_scripts;
Ref<ScriptDebuggerPeer> peer;
uint64_t last_perf_time;
uint64_t last_net_prof_time;
uint64_t last_net_bandwidth_time;
Object *performance;
bool requested_quit;
Mutex mutex;
List<String> output_strings;
List<Message> messages;
int max_messages_per_frame;
int n_messages_dropped;
List<OutputError> errors;
int max_errors_per_second;
int max_warnings_per_second;
int n_errors_dropped;
int n_warnings_dropped;
int max_cps;
int char_count;
int err_count;
int warn_count;
uint64_t last_msec;
uint64_t msec_count;
bool locking; //hack to avoid a deadloop
static void _print_handler(void *p_this, const String &p_string, bool p_error);
PrintHandlerList phl;
void _get_output();
void _poll_events();
uint32_t poll_every;
void _parse_message(const String p_command, const Array &p_data, ScriptLanguage *p_script = NULL);
void _set_object_property(ObjectID p_id, const String &p_property, const Variant &p_value);
void _send_object_id(ObjectID p_id);
void _send_video_memory();
Ref<MultiplayerAPI> multiplayer;
ErrorHandlerList eh;
static void _err_handler(void *, const char *, const char *, int p_line, const char *, const char *, ErrorHandlerType p_type);
void _put_msg(String p_message, Array p_data);
void _send_profiling_data(bool p_for_frame);
void _send_network_profiling_data();
void _send_network_bandwidth_usage();
Vector<FrameData> profile_frame_data;
bool skip_breakpoints;
public:
typedef void (*ResourceUsageFunc)(ResourceUsage *);
typedef Error (*ParseMessageFunc)(const String &p_name, const Array &p_msg); // Returns true if something was found (stopping propagation).
static ResourceUsageFunc resource_usage_func;
static ParseMessageFunc scene_tree_parse_func; // Could be made into list, extensible...
static ScriptDebuggerRemote *create_for_uri(const String &p_uri);
bool is_peer_connected();
virtual void debug(ScriptLanguage *p_script, bool p_can_continue = true, bool p_is_error_breakpoint = false);
virtual void idle_poll();
virtual void line_poll();
virtual bool is_remote() const { return true; }
virtual void request_quit();
virtual void send_message(const String &p_message, const Array &p_args);
virtual void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type, const Vector<ScriptLanguage::StackInfo> &p_stack_info);
virtual void set_multiplayer(Ref<MultiplayerAPI> p_multiplayer);
virtual bool is_profiling() const;
virtual void add_profiling_frame_data(const StringName &p_name, const Array &p_data);
virtual void profiling_start();
virtual void profiling_end();
virtual void profiling_set_frame_times(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time);
virtual void set_skip_breakpoints(bool p_skip_breakpoints);
ScriptDebuggerRemote(Ref<ScriptDebuggerPeer> p_peer);
~ScriptDebuggerRemote();
};
#endif // SCRIPT_DEBUGGER_REMOTE_H

View file

@ -31,6 +31,8 @@
#include "script_language.h"
#include "core/core_string_names.h"
#include "core/debugger/engine_debugger.h"
#include "core/debugger/script_debugger.h"
#include "core/project_settings.h"
#include <stdint.h>
@ -46,8 +48,8 @@ void Script::_notification(int p_what) {
if (p_what == NOTIFICATION_POSTINITIALIZE) {
if (ScriptDebugger::get_singleton())
ScriptDebugger::get_singleton()->set_break_language(get_language());
if (EngineDebugger::is_active())
EngineDebugger::get_script_debugger()->set_break_language(get_language());
}
}
@ -356,89 +358,6 @@ ScriptCodeCompletionCache::ScriptCodeCompletionCache() {
void ScriptLanguage::frame() {
}
ScriptDebugger *ScriptDebugger::singleton = NULL;
void ScriptDebugger::set_lines_left(int p_left) {
lines_left = p_left;
}
int ScriptDebugger::get_lines_left() const {
return lines_left;
}
void ScriptDebugger::set_depth(int p_depth) {
depth = p_depth;
}
int ScriptDebugger::get_depth() const {
return depth;
}
void ScriptDebugger::insert_breakpoint(int p_line, const StringName &p_source) {
if (!breakpoints.has(p_line))
breakpoints[p_line] = Set<StringName>();
breakpoints[p_line].insert(p_source);
}
void ScriptDebugger::remove_breakpoint(int p_line, const StringName &p_source) {
if (!breakpoints.has(p_line))
return;
breakpoints[p_line].erase(p_source);
if (breakpoints[p_line].size() == 0)
breakpoints.erase(p_line);
}
bool ScriptDebugger::is_breakpoint(int p_line, const StringName &p_source) const {
if (!breakpoints.has(p_line))
return false;
return breakpoints[p_line].has(p_source);
}
bool ScriptDebugger::is_breakpoint_line(int p_line) const {
return breakpoints.has(p_line);
}
String ScriptDebugger::breakpoint_find_source(const String &p_source) const {
return p_source;
}
void ScriptDebugger::clear_breakpoints() {
breakpoints.clear();
}
void ScriptDebugger::idle_poll() {
}
void ScriptDebugger::line_poll() {
}
void ScriptDebugger::set_break_language(ScriptLanguage *p_lang) {
break_lang = p_lang;
}
ScriptLanguage *ScriptDebugger::get_break_language() const {
return break_lang;
}
ScriptDebugger::ScriptDebugger() {
singleton = this;
lines_left = -1;
depth = -1;
break_lang = NULL;
}
bool PlaceHolderScriptInstance::set(const StringName &p_name, const Variant &p_value) {
if (script->is_placeholder_fallback_enabled())

View file

@ -350,6 +350,11 @@ public:
virtual void thread_exit() {}
/* DEBUGGER FUNCTIONS */
struct StackInfo {
String file;
String func;
int line;
};
virtual String debug_get_error() const = 0;
virtual int debug_get_stack_level_count() const = 0;
@ -362,12 +367,6 @@ public:
virtual void debug_get_globals(List<String> *p_globals, List<Variant> *p_values, int p_max_subitems = -1, int p_max_depth = -1) = 0;
virtual String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems = -1, int p_max_depth = -1) = 0;
struct StackInfo {
String file;
String func;
int line;
};
virtual Vector<StackInfo> debug_get_current_stack_info() { return Vector<StackInfo>(); }
virtual void reload_all_scripts() = 0;
@ -460,56 +459,4 @@ public:
PlaceHolderScriptInstance(ScriptLanguage *p_language, Ref<Script> p_script, Object *p_owner);
~PlaceHolderScriptInstance();
};
class ScriptDebugger {
int lines_left;
int depth;
static ScriptDebugger *singleton;
Map<int, Set<StringName> > breakpoints;
ScriptLanguage *break_lang;
public:
_FORCE_INLINE_ static ScriptDebugger *get_singleton() { return singleton; }
void set_lines_left(int p_left);
int get_lines_left() const;
void set_depth(int p_depth);
int get_depth() const;
String breakpoint_find_source(const String &p_source) const;
void insert_breakpoint(int p_line, const StringName &p_source);
void remove_breakpoint(int p_line, const StringName &p_source);
bool is_breakpoint(int p_line, const StringName &p_source) const;
bool is_breakpoint_line(int p_line) const;
void clear_breakpoints();
const Map<int, Set<StringName> > &get_breakpoints() const { return breakpoints; }
virtual void debug(ScriptLanguage *p_script, bool p_can_continue = true, bool p_is_error_breakpoint = false) = 0;
virtual void idle_poll();
virtual void line_poll();
void set_break_language(ScriptLanguage *p_lang);
ScriptLanguage *get_break_language() const;
virtual void send_message(const String &p_message, const Array &p_args) = 0;
virtual void send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type, const Vector<ScriptLanguage::StackInfo> &p_stack_info) = 0;
virtual bool is_remote() const { return false; }
virtual void request_quit() {}
virtual void set_multiplayer(Ref<MultiplayerAPI> p_multiplayer) {}
virtual bool is_profiling() const = 0;
virtual void add_profiling_frame_data(const StringName &p_name, const Array &p_data) = 0;
virtual void profiling_start() = 0;
virtual void profiling_end() = 0;
virtual void profiling_set_frame_times(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) = 0;
ScriptDebugger();
virtual ~ScriptDebugger() { singleton = NULL; }
};
#endif

View file

@ -31,6 +31,7 @@
#include "variant.h"
#include "core/core_string_names.h"
#include "core/debugger/engine_debugger.h"
#include "core/io/marshalls.h"
#include "core/math/math_funcs.h"
#include "core/print_string.h"
@ -2221,7 +2222,7 @@ Variant::operator RID() const {
return RID();
} else if (type == OBJECT && _get_obj().obj) {
#ifdef DEBUG_ENABLED
if (ScriptDebugger::get_singleton()) {
if (EngineDebugger::is_active()) {
ERR_FAIL_COND_V_MSG(ObjectDB::get_instance(_get_obj().id) == nullptr, RID(), "Invalid pointer (object was freed).");
};
#endif

View file

@ -33,10 +33,10 @@
#include "core/color_names.inc"
#include "core/core_string_names.h"
#include "core/crypto/crypto_core.h"
#include "core/debugger/engine_debugger.h"
#include "core/io/compression.h"
#include "core/object.h"
#include "core/os/os.h"
#include "core/script_language.h"
typedef void (*VariantFunc)(Variant &r_ret, Variant &p_self, const Variant **p_args);
typedef void (*VariantConstructFunc)(Variant &r_ret, const Variant **p_args);
@ -1213,7 +1213,7 @@ void Variant::call_ptr(const StringName &p_method, const Variant **p_args, int p
return;
}
#ifdef DEBUG_ENABLED
if (ScriptDebugger::get_singleton() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
if (EngineDebugger::is_active() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
return;
}

View file

@ -31,8 +31,8 @@
#include "variant.h"
#include "core/core_string_names.h"
#include "core/debugger/engine_debugger.h"
#include "core/object.h"
#include "core/script_language.h"
#define CASE_TYPE_ALL(PREFIX, OP) \
CASE_TYPE(PREFIX, OP, INT) \
@ -1739,7 +1739,7 @@ void Variant::set_named(const StringName &p_index, const Variant &p_value, bool
#ifdef DEBUG_ENABLED
if (!_get_obj().obj) {
break;
} else if (ScriptDebugger::get_singleton() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
} else if (EngineDebugger::is_active() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
break;
}
@ -1941,7 +1941,7 @@ Variant Variant::get_named(const StringName &p_index, bool *r_valid) const {
return "Instance base is null.";
} else {
if (ScriptDebugger::get_singleton() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
if (EngineDebugger::is_active() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
if (r_valid)
*r_valid = false;
return "Attempted use of stray pointer object.";
@ -2556,7 +2556,7 @@ void Variant::set(const Variant &p_index, const Variant &p_value, bool *r_valid)
if (obj) {
#ifdef DEBUG_ENABLED
if (ScriptDebugger::get_singleton() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
if (EngineDebugger::is_active() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
WARN_PRINT("Attempted use of previously freed pointer object.");
valid = false;
@ -3011,7 +3011,7 @@ Variant Variant::get(const Variant &p_index, bool *r_valid) const {
#ifdef DEBUG_ENABLED
if (ScriptDebugger::get_singleton() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
if (EngineDebugger::is_active() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
valid = false;
return "Attempted get on previously freed instance.";
}
@ -3076,7 +3076,7 @@ bool Variant::in(const Variant &p_index, bool *r_valid) const {
bool valid = false;
#ifdef DEBUG_ENABLED
if (ScriptDebugger::get_singleton() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
if (EngineDebugger::is_active() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
if (r_valid) {
*r_valid = false;
}
@ -3405,7 +3405,7 @@ void Variant::get_property_list(List<PropertyInfo> *p_list) const {
if (obj) {
#ifdef DEBUG_ENABLED
if (ScriptDebugger::get_singleton() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
if (EngineDebugger::is_active() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
WARN_PRINT("Attempted get_property list on previously freed instance.");
return;
}
@ -3490,7 +3490,7 @@ bool Variant::iter_init(Variant &r_iter, bool &valid) const {
#ifdef DEBUG_ENABLED
if (ScriptDebugger::get_singleton() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
if (EngineDebugger::is_active() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
valid = false;
return false;
}
@ -3676,7 +3676,7 @@ bool Variant::iter_next(Variant &r_iter, bool &valid) const {
#ifdef DEBUG_ENABLED
if (ScriptDebugger::get_singleton() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
if (EngineDebugger::is_active() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
valid = false;
return false;
}
@ -3855,7 +3855,7 @@ Variant Variant::iter_get(const Variant &r_iter, bool &r_valid) const {
return Variant();
}
#ifdef DEBUG_ENABLED
if (ScriptDebugger::get_singleton() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
if (EngineDebugger::is_active() && !_get_obj().id.is_reference() && ObjectDB::get_instance(_get_obj().id) == nullptr) {
r_valid = false;
return Variant();
}

View file

@ -824,17 +824,17 @@
</member>
<member name="mono/unhandled_exception_policy" type="int" setter="" getter="" default="0">
</member>
<member name="network/limits/debugger_stdout/max_chars_per_second" type="int" setter="" getter="" default="2048">
<member name="network/limits/debugger/max_chars_per_second" type="int" setter="" getter="" default="2048">
Maximum amount of characters allowed to send as output from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection.
</member>
<member name="network/limits/debugger_stdout/max_errors_per_second" type="int" setter="" getter="" default="100">
Maximum number of errors allowed to be sent as output from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection.
<member name="network/limits/debugger/max_errors_per_second" type="int" setter="" getter="" default="100">
Maximum number of errors allowed to be sent from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection.
</member>
<member name="network/limits/debugger_stdout/max_messages_per_frame" type="int" setter="" getter="" default="10">
Maximum amount of messages allowed to send as output from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection.
<member name="network/limits/debugger/max_queued_messages" type="int" setter="" getter="" default="10">
Maximum amount of messages in the debugger queue. Over this value, content is dropped. This helps to limit the debugger memory usage.
</member>
<member name="network/limits/debugger_stdout/max_warnings_per_second" type="int" setter="" getter="" default="100">
Maximum number of warnings allowed to be sent as output from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection.
<member name="network/limits/debugger/max_warnings_per_second" type="int" setter="" getter="" default="100">
Maximum number of warnings allowed to be sent from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection.
</member>
<member name="network/limits/packet_peer_stream/max_buffer_po2" type="int" setter="" getter="" default="16">
Default size of packet peer stream for deserializing Godot data. Over this size, data is dropped.

View file

@ -32,6 +32,8 @@
#ifdef UNIX_ENABLED
#include "core/debugger/engine_debugger.h"
#include "core/debugger/script_debugger.h"
#include "core/os/thread_dummy.h"
#include "core/project_settings.h"
#include "drivers/unix/dir_access_unix.h"
@ -94,16 +96,16 @@ void OS_Unix::debug_break() {
};
static void handle_interrupt(int sig) {
if (ScriptDebugger::get_singleton() == NULL)
if (!EngineDebugger::is_active())
return;
ScriptDebugger::get_singleton()->set_depth(-1);
ScriptDebugger::get_singleton()->set_lines_left(1);
EngineDebugger::get_script_debugger()->set_depth(-1);
EngineDebugger::get_script_debugger()->set_lines_left(1);
}
void OS_Unix::initialize_debugging() {
if (ScriptDebugger::get_singleton() != NULL) {
if (EngineDebugger::is_active()) {
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = handle_interrupt;

View file

@ -31,6 +31,7 @@
#include "main.h"
#include "core/crypto/crypto.h"
#include "core/debugger/engine_debugger.h"
#include "core/input_map.h"
#include "core/io/file_access_network.h"
#include "core/io/file_access_pack.h"
@ -43,9 +44,6 @@
#include "core/os/os.h"
#include "core/project_settings.h"
#include "core/register_core_types.h"
#include "core/script_debugger_local.h"
#include "core/script_debugger_remote.h"
#include "core/script_language.h"
#include "core/translation.h"
#include "core/version.h"
#include "core/version_hash.gen.h"
@ -96,7 +94,6 @@ static PackedData *packed_data = NULL;
static ZipArchive *zip_packed_data = NULL;
#endif
static FileAccessNetworkClient *file_access_network_client = NULL;
static ScriptDebugger *script_debugger = NULL;
static MessageQueue *message_queue = NULL;
// Initialized in setup2()
@ -410,8 +407,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
String audio_driver = "";
String project_path = ".";
bool upwards = false;
String debug_mode;
String debug_host;
String debug_uri = "";
bool skip_breakpoints = false;
String main_pack;
bool quiet_stdout = false;
@ -784,7 +780,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
};
} else if (I->get() == "-d" || I->get() == "--debug") {
debug_mode = "local";
debug_uri = "local://";
#if defined(DEBUG_ENABLED) && !defined(SERVER_ENABLED)
} else if (I->get() == "--debug-collisions") {
debug_collisions = true;
@ -794,12 +790,12 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
} else if (I->get() == "--remote-debug") {
if (I->next()) {
debug_mode = "remote";
debug_host = I->next()->get();
if (debug_host.find(":") == -1) { // wrong address
debug_uri = I->next()->get();
if (debug_uri.find(":") == -1) { // wrong address
OS::get_singleton()->print("Invalid debug host address, it should be of the form <host/IP>:<port>.\n");
goto error;
}
debug_uri = "tcp://" + debug_uri; // will support multiple protocols eventually.
N = I->next()->next();
} else {
OS::get_singleton()->print("Missing remote debug host address, aborting.\n");
@ -886,39 +882,16 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF("memory/limits/multithreaded_server/rid_pool_prealloc", 60);
ProjectSettings::get_singleton()->set_custom_property_info("memory/limits/multithreaded_server/rid_pool_prealloc", PropertyInfo(Variant::INT, "memory/limits/multithreaded_server/rid_pool_prealloc", PROPERTY_HINT_RANGE, "0,500,1")); // No negative and limit to 500 due to crashes
GLOBAL_DEF("network/limits/debugger_stdout/max_chars_per_second", 2048);
ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger_stdout/max_chars_per_second", PropertyInfo(Variant::INT, "network/limits/debugger_stdout/max_chars_per_second", PROPERTY_HINT_RANGE, "0, 4096, 1, or_greater"));
GLOBAL_DEF("network/limits/debugger_stdout/max_messages_per_frame", 10);
ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger_stdout/max_messages_per_frame", PropertyInfo(Variant::INT, "network/limits/debugger_stdout/max_messages_per_frame", PROPERTY_HINT_RANGE, "0, 20, 1, or_greater"));
GLOBAL_DEF("network/limits/debugger_stdout/max_errors_per_second", 100);
ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger_stdout/max_errors_per_second", PropertyInfo(Variant::INT, "network/limits/debugger_stdout/max_errors_per_second", PROPERTY_HINT_RANGE, "0, 200, 1, or_greater"));
GLOBAL_DEF("network/limits/debugger_stdout/max_warnings_per_second", 100);
ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger_stdout/max_warnings_per_second", PropertyInfo(Variant::INT, "network/limits/debugger_stdout/max_warnings_per_second", PROPERTY_HINT_RANGE, "0, 200, 1, or_greater"));
GLOBAL_DEF("network/limits/debugger/max_chars_per_second", 32768);
ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_chars_per_second", PropertyInfo(Variant::INT, "network/limits/debugger/max_chars_per_second", PROPERTY_HINT_RANGE, "0, 4096, 1, or_greater"));
GLOBAL_DEF("network/limits/debugger/max_queued_messages", 2048);
ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_queued_messages", PropertyInfo(Variant::INT, "network/limits/debugger/max_queued_messages", PROPERTY_HINT_RANGE, "0, 8192, 1, or_greater"));
GLOBAL_DEF("network/limits/debugger/max_errors_per_second", 400);
ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_errors_per_second", PropertyInfo(Variant::INT, "network/limits/debugger/max_errors_per_second", PROPERTY_HINT_RANGE, "0, 200, 1, or_greater"));
GLOBAL_DEF("network/limits/debugger/max_warnings_per_second", 400);
ProjectSettings::get_singleton()->set_custom_property_info("network/limits/debugger/max_warnings_per_second", PropertyInfo(Variant::INT, "network/limits/debugger/max_warnings_per_second", PROPERTY_HINT_RANGE, "0, 200, 1, or_greater"));
if (debug_mode == "remote") {
ScriptDebuggerRemote *sdr = ScriptDebuggerRemote::create_for_uri(debug_host);
if (sdr) {
sdr->set_skip_breakpoints(skip_breakpoints);
script_debugger = sdr;
}
} else if (debug_mode == "local") {
script_debugger = memnew(ScriptDebuggerLocal);
OS::get_singleton()->initialize_debugging();
}
if (script_debugger) {
//there is a debugger, parse breakpoints
for (int i = 0; i < breakpoints.size(); i++) {
String bp = breakpoints[i];
int sp = bp.find_last(":");
ERR_CONTINUE_MSG(sp == -1, "Invalid breakpoint: '" + bp + "', expected file:line format.");
script_debugger->insert_breakpoint(bp.substr(sp + 1, bp.length()).to_int(), bp.substr(0, sp));
}
}
EngineDebugger::initialize(debug_uri, skip_breakpoints, breakpoints);
#ifdef TOOLS_ENABLED
if (editor) {
@ -1168,6 +1141,8 @@ error:
if (show_help)
print_help(execpath);
EngineDebugger::deinitialize();
if (performance)
memdelete(performance);
if (input_map)
@ -1178,8 +1153,6 @@ error:
memdelete(globals);
if (engine)
memdelete(engine);
if (script_debugger)
memdelete(script_debugger);
if (packed_data)
memdelete(packed_data);
if (file_access_network_client)
@ -1390,8 +1363,10 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
audio_server->load_default_bus_layout();
if (use_debug_profiler && script_debugger) {
script_debugger->profiling_start();
if (use_debug_profiler && EngineDebugger::is_active()) {
// Start the "scripts" profiler, used in local debugging.
// We could add more, and make the CLI arg require a comma-separated list of profilers.
EngineDebugger::get_singleton()->profiler_enable("scripts", true);
}
_start_success = true;
locale = String();
@ -2079,12 +2054,8 @@ bool Main::iteration() {
AudioServer::get_singleton()->update();
if (script_debugger) {
if (script_debugger->is_profiling()) {
script_debugger->profiling_set_frame_times(USEC_TO_SEC(frame_time), USEC_TO_SEC(idle_process_ticks), USEC_TO_SEC(physics_process_ticks), frame_slice);
}
script_debugger->idle_poll();
}
if (EngineDebugger::is_active())
EngineDebugger::get_singleton()->iteration(frame_time, idle_process_ticks, physics_process_ticks, frame_slice);
frames++;
Engine::get_singleton()->_idle_frames++;
@ -2162,10 +2133,7 @@ void Main::cleanup() {
ERR_FAIL_COND(!_start_success);
if (script_debugger) {
// Flush any remaining messages
script_debugger->idle_poll();
}
EngineDebugger::deinitialize();
ResourceLoader::remove_custom_loaders();
ResourceSaver::remove_custom_savers();
@ -2173,14 +2141,6 @@ void Main::cleanup() {
message_queue->flush();
memdelete(message_queue);
if (script_debugger) {
if (use_debug_profiler) {
script_debugger->profiling_end();
}
memdelete(script_debugger);
}
OS::get_singleton()->delete_main_loop();
OS::get_singleton()->_cmdline.clear();

View file

@ -194,7 +194,7 @@ ScriptInstance *PluginScript::instance_create(Object *p_this) {
if (!ClassDB::is_parent_class(p_this->get_class_name(), base_type)) {
String msg = "Script inherits from native type '" + String(base_type) + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'";
// TODO: implement PluginscriptLanguage::debug_break_parse
// if (ScriptDebugger::get_singleton()) {
// if (EngineDebugger::is_active()) {
// _language->debug_break_parse(get_path(), 0, msg);
// }
ERR_FAIL_V_MSG(NULL, msg);

View file

@ -315,7 +315,7 @@ ScriptInstance *GDScript::instance_create(Object *p_this) {
if (top->native.is_valid()) {
if (!ClassDB::is_parent_class(p_this->get_class_name(), top->native->get_name())) {
if (ScriptDebugger::get_singleton()) {
if (EngineDebugger::is_active()) {
GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), 1, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'");
}
ERR_FAIL_V_MSG(NULL, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instanced in object of type '" + p_this->get_class() + "'" + ".");
@ -556,7 +556,7 @@ Error GDScript::reload(bool p_keep_state) {
GDScriptParser parser;
Error err = parser.parse(source, basedir, false, path);
if (err) {
if (ScriptDebugger::get_singleton()) {
if (EngineDebugger::is_active()) {
GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_error_line(), "Parser Error: " + parser.get_error());
}
_err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_error_line(), ("Parse Error: " + parser.get_error()).utf8().get_data(), ERR_HANDLER_SCRIPT);
@ -571,7 +571,7 @@ Error GDScript::reload(bool p_keep_state) {
if (err) {
if (can_run) {
if (ScriptDebugger::get_singleton()) {
if (EngineDebugger::is_active()) {
GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), compiler.get_error_line(), "Parser Error: " + compiler.get_error());
}
_err_print_error("GDScript::reload", path.empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), ERR_HANDLER_SCRIPT);
@ -583,9 +583,9 @@ Error GDScript::reload(bool p_keep_state) {
#ifdef DEBUG_ENABLED
for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E; E = E->next()) {
const GDScriptWarning &warning = E->get();
if (ScriptDebugger::get_singleton()) {
if (EngineDebugger::is_active()) {
Vector<ScriptLanguage::StackInfo> si;
ScriptDebugger::get_singleton()->send_error("", get_path(), warning.line, warning.get_name(), warning.get_message(), ERR_HANDLER_WARNING, si);
EngineDebugger::get_script_debugger()->send_error("", get_path(), warning.line, warning.get_name(), warning.get_message(), ERR_HANDLER_WARNING, si);
}
}
#endif
@ -2201,7 +2201,7 @@ GDScriptLanguage::GDScriptLanguage() {
int dmcs = GLOBAL_DEF("debug/settings/gdscript/max_call_stack", 1024);
ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/gdscript/max_call_stack", PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "1024,4096,1,or_greater")); //minimum is 1024
if (ScriptDebugger::get_singleton()) {
if (EngineDebugger::is_active()) {
//debugging enabled!
_debug_max_call_stack = dmcs;

View file

@ -31,6 +31,8 @@
#ifndef GDSCRIPT_H
#define GDSCRIPT_H
#include "core/debugger/engine_debugger.h"
#include "core/debugger/script_debugger.h"
#include "core/io/resource_loader.h"
#include "core/io/resource_saver.h"
#include "core/script_language.h"
@ -393,13 +395,13 @@ public:
if (Thread::get_main_id() != Thread::get_caller_id())
return; //no support for other threads than main for now
if (ScriptDebugger::get_singleton()->get_lines_left() > 0 && ScriptDebugger::get_singleton()->get_depth() >= 0)
ScriptDebugger::get_singleton()->set_depth(ScriptDebugger::get_singleton()->get_depth() + 1);
if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0)
EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() + 1);
if (_debug_call_stack_pos >= _debug_max_call_stack) {
//stack overflow
_debug_error = "Stack Overflow (Stack Size: " + itos(_debug_max_call_stack) + ")";
ScriptDebugger::get_singleton()->debug(this);
EngineDebugger::get_script_debugger()->debug(this);
return;
}
@ -416,13 +418,13 @@ public:
if (Thread::get_main_id() != Thread::get_caller_id())
return; //no support for other threads than main for now
if (ScriptDebugger::get_singleton()->get_lines_left() > 0 && ScriptDebugger::get_singleton()->get_depth() >= 0)
ScriptDebugger::get_singleton()->set_depth(ScriptDebugger::get_singleton()->get_depth() - 1);
if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0)
EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() - 1);
if (_debug_call_stack_pos == 0) {
_debug_error = "Stack Underflow (Engine Bug)";
ScriptDebugger::get_singleton()->debug(this);
EngineDebugger::get_script_debugger()->debug(this);
return;
}

View file

@ -1579,7 +1579,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
codegen.stack_max = 0;
codegen.current_line = 0;
codegen.call_max = 0;
codegen.debug_stack = ScriptDebugger::get_singleton() != NULL;
codegen.debug_stack = EngineDebugger::is_active();
Vector<StringName> argnames;
int stack_level = 0;
@ -1765,7 +1765,7 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
gdfunc->_call_size = codegen.call_max;
gdfunc->name = func_name;
#ifdef DEBUG_ENABLED
if (ScriptDebugger::get_singleton()) {
if (EngineDebugger::is_active()) {
String signature;
//path
if (p_script->get_path() != String())

View file

@ -221,12 +221,12 @@ Script *GDScriptLanguage::create_script() const {
bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) {
//break because of parse error
if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) {
if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) {
_debug_parse_err_line = p_line;
_debug_parse_err_file = p_file;
_debug_error = p_error;
ScriptDebugger::get_singleton()->debug(this, false, true);
EngineDebugger::get_script_debugger()->debug(this, false, true);
return true;
} else {
return false;
@ -235,13 +235,13 @@ bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const
bool GDScriptLanguage::debug_break(const String &p_error, bool p_allow_continue) {
if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) {
if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) {
_debug_parse_err_line = -1;
_debug_parse_err_file = "";
_debug_error = p_error;
bool is_error_breakpoint = p_error != "Breakpoint";
ScriptDebugger::get_singleton()->debug(this, p_allow_continue, is_error_breakpoint);
EngineDebugger::get_script_debugger()->debug(this, p_allow_continue, is_error_breakpoint);
return true;
} else {
return false;

View file

@ -391,7 +391,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#ifdef DEBUG_ENABLED
if (ScriptDebugger::get_singleton())
if (EngineDebugger::is_active())
GDScriptLanguage::get_singleton()->enter_function(p_instance, this, stack, &ip, &line);
#define GD_ERR_BREAK(m_cond) \
@ -1522,7 +1522,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
OPCODE(OPCODE_BREAKPOINT) {
#ifdef DEBUG_ENABLED
if (ScriptDebugger::get_singleton()) {
if (EngineDebugger::is_active()) {
GDScriptLanguage::get_singleton()->debug_break("Breakpoint Statement", true);
}
#endif
@ -1536,26 +1536,26 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
line = _code_ptr[ip + 1];
ip += 2;
if (ScriptDebugger::get_singleton()) {
if (EngineDebugger::is_active()) {
// line
bool do_break = false;
if (ScriptDebugger::get_singleton()->get_lines_left() > 0) {
if (EngineDebugger::get_script_debugger()->get_lines_left() > 0) {
if (ScriptDebugger::get_singleton()->get_depth() <= 0)
ScriptDebugger::get_singleton()->set_lines_left(ScriptDebugger::get_singleton()->get_lines_left() - 1);
if (ScriptDebugger::get_singleton()->get_lines_left() <= 0)
if (EngineDebugger::get_script_debugger()->get_depth() <= 0)
EngineDebugger::get_script_debugger()->set_lines_left(EngineDebugger::get_script_debugger()->get_lines_left() - 1);
if (EngineDebugger::get_script_debugger()->get_lines_left() <= 0)
do_break = true;
}
if (ScriptDebugger::get_singleton()->is_breakpoint(line, source))
if (EngineDebugger::get_script_debugger()->is_breakpoint(line, source))
do_break = true;
if (do_break) {
GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true);
}
ScriptDebugger::get_singleton()->line_poll();
EngineDebugger::get_singleton()->line_poll();
}
}
DISPATCH_OPCODE;
@ -1622,7 +1622,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
// When it's the last resume it will postpone the exit from stack,
// so the debugger knows which function triggered the resume of the next function (if any)
if (!p_state || yielded) {
if (ScriptDebugger::get_singleton())
if (EngineDebugger::is_active())
GDScriptLanguage::get_singleton()->exit_function();
#endif
@ -1884,7 +1884,7 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) {
}
#ifdef DEBUG_ENABLED
if (ScriptDebugger::get_singleton())
if (EngineDebugger::is_active())
GDScriptLanguage::get_singleton()->exit_function();
if (state.stack_size) {
//free stack

View file

@ -33,6 +33,8 @@
#include <mono/metadata/threads.h>
#include <stdint.h>
#include "core/debugger/engine_debugger.h"
#include "core/debugger/script_debugger.h"
#include "core/io/json.h"
#include "core/os/file_access.h"
#include "core/os/mutex.h"
@ -1134,11 +1136,11 @@ void CSharpLanguage::thread_exit() {
bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) {
// Not a parser error in our case, but it's still used for other type of errors
if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) {
if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) {
_debug_parse_err_line = p_line;
_debug_parse_err_file = p_file;
_debug_error = p_error;
ScriptDebugger::get_singleton()->debug(this, false, true);
EngineDebugger::get_script_debugger()->debug(this, false, true);
return true;
} else {
return false;
@ -1147,11 +1149,11 @@ bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const S
bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) {
if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) {
if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) {
_debug_parse_err_line = -1;
_debug_parse_err_file = "";
_debug_error = p_error;
ScriptDebugger::get_singleton()->debug(this, p_allow_continue);
EngineDebugger::get_script_debugger()->debug(this, p_allow_continue);
return true;
} else {
return false;
@ -2998,7 +3000,7 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) {
if (native) {
String native_name = NATIVE_GDMONOCLASS_NAME(native);
if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) {
if (ScriptDebugger::get_singleton()) {
if (EngineDebugger::is_active()) {
CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, "Script inherits from native type '" + native_name + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'");
}
ERR_FAIL_V_MSG(NULL, "Script inherits from native type '" + native_name +

View file

@ -37,6 +37,7 @@
#include <mono/metadata/mono-gc.h>
#include <mono/metadata/profiler.h>
#include "core/debugger/engine_debugger.h"
#include "core/os/dir_access.h"
#include "core/os/file_access.h"
#include "core/os/os.h"
@ -1183,8 +1184,8 @@ void GDMono::unhandled_exception_hook(MonoObject *p_exc, void *) {
#ifdef DEBUG_ENABLED
GDMonoUtils::debug_send_unhandled_exception_error((MonoException *)p_exc);
if (ScriptDebugger::get_singleton())
ScriptDebugger::get_singleton()->idle_poll();
if (EngineDebugger::is_active())
EngineDebugger::get_singleton()->poll_events(false);
#endif
exit(mono_environment_exitcode_get());

View file

@ -38,6 +38,9 @@
#include "gd_mono_marshal.h"
#include "gd_mono_utils.h"
#include "core/debugger/engine_debugger.h"
#include "core/debugger/script_debugger.h"
#include <mono/metadata/exception.h>
namespace GDMonoInternals {
@ -120,8 +123,8 @@ void unhandled_exception(MonoException *p_exc) {
} else {
#ifdef DEBUG_ENABLED
GDMonoUtils::debug_send_unhandled_exception_error((MonoException *)p_exc);
if (ScriptDebugger::get_singleton())
ScriptDebugger::get_singleton()->idle_poll();
if (EngineDebugger::is_active())
EngineDebugger::get_singleton()->poll_events(false);
#endif
}
}

View file

@ -32,6 +32,8 @@
#include <mono/metadata/exception.h>
#include "core/debugger/engine_debugger.h"
#include "core/debugger/script_debugger.h"
#include "core/os/dir_access.h"
#include "core/os/mutex.h"
#include "core/os/os.h"
@ -351,7 +353,7 @@ void debug_print_unhandled_exception(MonoException *p_exc) {
void debug_send_unhandled_exception_error(MonoException *p_exc) {
#ifdef DEBUG_ENABLED
if (!ScriptDebugger::get_singleton()) {
if (!EngineDebugger::is_active()) {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
ERR_PRINT(GDMonoUtils::get_exception_name_and_message(p_exc));
@ -410,7 +412,7 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) {
int line = si.size() ? si[0].line : __LINE__;
String error_msg = "Unhandled exception";
ScriptDebugger::get_singleton()->send_error(func, file, line, error_msg, exc_msg, ERR_HANDLER_ERROR, si);
EngineDebugger::get_script_debugger()->send_error(func, file, line, error_msg, exc_msg, ERR_HANDLER_ERROR, si);
#endif
}

View file

@ -1629,7 +1629,7 @@ Variant VisualScriptInstance::_call_internal(const StringName &p_method, void *p
int flow_stack_pos = p_flow_stack_pos;
#ifdef DEBUG_ENABLED
if (ScriptDebugger::get_singleton()) {
if (EngineDebugger::is_active()) {
VisualScriptLanguage::singleton->enter_function(this, &p_method, variant_stack, &working_mem, &current_node_id);
}
#endif
@ -1766,7 +1766,7 @@ Variant VisualScriptInstance::_call_internal(const StringName &p_method, void *p
#ifdef DEBUG_ENABLED
//will re-enter later, so exiting
if (ScriptDebugger::get_singleton()) {
if (EngineDebugger::is_active()) {
VisualScriptLanguage::singleton->exit_function();
}
#endif
@ -1776,26 +1776,26 @@ Variant VisualScriptInstance::_call_internal(const StringName &p_method, void *p
}
#ifdef DEBUG_ENABLED
if (ScriptDebugger::get_singleton()) {
if (EngineDebugger::is_active()) {
// line
bool do_break = false;
if (ScriptDebugger::get_singleton()->get_lines_left() > 0) {
if (EngineDebugger::get_script_debugger()->get_lines_left() > 0) {
if (ScriptDebugger::get_singleton()->get_depth() <= 0)
ScriptDebugger::get_singleton()->set_lines_left(ScriptDebugger::get_singleton()->get_lines_left() - 1);
if (ScriptDebugger::get_singleton()->get_lines_left() <= 0)
if (EngineDebugger::get_script_debugger()->get_depth() <= 0)
EngineDebugger::get_script_debugger()->set_lines_left(EngineDebugger::get_script_debugger()->get_lines_left() - 1);
if (EngineDebugger::get_script_debugger()->get_lines_left() <= 0)
do_break = true;
}
if (ScriptDebugger::get_singleton()->is_breakpoint(current_node_id, source))
if (EngineDebugger::get_script_debugger()->is_breakpoint(current_node_id, source))
do_break = true;
if (do_break) {
VisualScriptLanguage::singleton->debug_break("Breakpoint", true);
}
ScriptDebugger::get_singleton()->line_poll();
EngineDebugger::get_singleton()->line_poll();
}
#endif
int output = ret & VisualScriptNodeInstance::STEP_MASK;
@ -1983,7 +1983,7 @@ Variant VisualScriptInstance::_call_internal(const StringName &p_method, void *p
}
#ifdef DEBUG_ENABLED
if (ScriptDebugger::get_singleton()) {
if (EngineDebugger::is_active()) {
VisualScriptLanguage::singleton->exit_function();
}
#endif
@ -2593,12 +2593,12 @@ void VisualScriptLanguage::add_global_constant(const StringName &p_variable, con
bool VisualScriptLanguage::debug_break_parse(const String &p_file, int p_node, const String &p_error) {
//break because of parse error
if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) {
if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) {
_debug_parse_err_node = p_node;
_debug_parse_err_file = p_file;
_debug_error = p_error;
ScriptDebugger::get_singleton()->debug(this, false, true);
EngineDebugger::get_script_debugger()->debug(this, false, true);
return true;
} else {
return false;
@ -2607,12 +2607,12 @@ bool VisualScriptLanguage::debug_break_parse(const String &p_file, int p_node, c
bool VisualScriptLanguage::debug_break(const String &p_error, bool p_allow_continue) {
if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) {
if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) {
_debug_parse_err_node = -1;
_debug_parse_err_file = "";
_debug_error = p_error;
ScriptDebugger::get_singleton()->debug(this, p_allow_continue, true);
EngineDebugger::get_script_debugger()->debug(this, p_allow_continue, true);
return true;
} else {
return false;
@ -2837,7 +2837,7 @@ VisualScriptLanguage::VisualScriptLanguage() {
int dmcs = GLOBAL_DEF("debug/settings/visual_script/max_call_stack", 1024);
ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/visual_script/max_call_stack", PropertyInfo(Variant::INT, "debug/settings/visual_script/max_call_stack", PROPERTY_HINT_RANGE, "1024,4096,1,or_greater")); //minimum is 1024
if (ScriptDebugger::get_singleton()) {
if (EngineDebugger::is_active()) {
//debugging enabled!
_debug_max_call_stack = dmcs;
_call_stack = memnew_arr(CallLevel, _debug_max_call_stack + 1);

View file

@ -31,6 +31,8 @@
#ifndef VISUAL_SCRIPT_H
#define VISUAL_SCRIPT_H
#include "core/debugger/engine_debugger.h"
#include "core/debugger/script_debugger.h"
#include "core/os/thread.h"
#include "core/script_language.h"
@ -540,13 +542,13 @@ public:
if (Thread::get_main_id() != Thread::get_caller_id())
return; //no support for other threads than main for now
if (ScriptDebugger::get_singleton()->get_lines_left() > 0 && ScriptDebugger::get_singleton()->get_depth() >= 0)
ScriptDebugger::get_singleton()->set_depth(ScriptDebugger::get_singleton()->get_depth() + 1);
if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0)
EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() + 1);
if (_debug_call_stack_pos >= _debug_max_call_stack) {
//stack overflow
_debug_error = "Stack Overflow (Stack Size: " + itos(_debug_max_call_stack) + ")";
ScriptDebugger::get_singleton()->debug(this);
EngineDebugger::get_script_debugger()->debug(this);
return;
}
@ -563,13 +565,13 @@ public:
if (Thread::get_main_id() != Thread::get_caller_id())
return; //no support for other threads than main for now
if (ScriptDebugger::get_singleton()->get_lines_left() > 0 && ScriptDebugger::get_singleton()->get_depth() >= 0)
ScriptDebugger::get_singleton()->set_depth(ScriptDebugger::get_singleton()->get_depth() - 1);
if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0)
EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() - 1);
if (_debug_call_stack_pos == 0) {
_debug_error = "Stack Underflow (Engine Bug)";
ScriptDebugger::get_singleton()->debug(this);
EngineDebugger::get_script_debugger()->debug(this);
return;
}

View file

@ -33,8 +33,9 @@
#include "os_windows.h"
#include "core/debugger/engine_debugger.h"
#include "core/debugger/script_debugger.h"
#include "core/io/marshalls.h"
#include "core/script_language.h"
#include "core/version_generated.gen.h"
#if defined(OPENGL_ENABLED)
@ -194,13 +195,13 @@ void RedirectIOToConsole() {
}
BOOL WINAPI HandlerRoutine(_In_ DWORD dwCtrlType) {
if (ScriptDebugger::get_singleton() == NULL)
if (!EngineDebugger::is_active())
return FALSE;
switch (dwCtrlType) {
case CTRL_C_EVENT:
ScriptDebugger::get_singleton()->set_depth(-1);
ScriptDebugger::get_singleton()->set_lines_left(1);
EngineDebugger::get_script_debugger()->set_depth(-1);
EngineDebugger::get_script_debugger()->set_lines_left(1);
return TRUE;
default:
return FALSE;

View file

@ -30,8 +30,9 @@
#include "scene_debugger.h"
#include "core/debugger/engine_debugger.h"
#include "core/io/marshalls.h"
#include "core/script_debugger_remote.h"
#include "core/script_language.h"
#include "scene/main/scene_tree.h"
#include "scene/main/viewport.h"
#include "scene/resources/packed_scene.h"
@ -39,13 +40,16 @@
void SceneDebugger::initialize() {
#ifdef DEBUG_ENABLED
LiveEditor::singleton = memnew(LiveEditor);
ScriptDebuggerRemote::scene_tree_parse_func = SceneDebugger::parse_message;
EngineDebugger::register_message_capture("scene", EngineDebugger::Capture(NULL, SceneDebugger::parse_message));
#endif
}
void SceneDebugger::deinitialize() {
#ifdef DEBUG_ENABLED
if (LiveEditor::singleton) {
// Should be removed automatically when deiniting debugger, but just in case
if (EngineDebugger::has_capture("scene"))
EngineDebugger::unregister_message_capture("scene");
memdelete(LiveEditor::singleton);
LiveEditor::singleton = NULL;
}
@ -53,13 +57,15 @@ void SceneDebugger::deinitialize() {
}
#ifdef DEBUG_ENABLED
Error SceneDebugger::parse_message(const String &p_msg, const Array &p_args) {
Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured) {
SceneTree *scene_tree = SceneTree::get_singleton();
if (!scene_tree)
return ERR_UNCONFIGURED;
LiveEditor *live_editor = LiveEditor::get_singleton();
if (!live_editor)
return ERR_UNCONFIGURED;
r_captured = true;
if (p_msg == "request_scene_tree") { // Scene tree
live_editor->_send_tree();
@ -171,7 +177,7 @@ Error SceneDebugger::parse_message(const String &p_msg, const Array &p_args) {
ERR_FAIL_COND_V(p_args.size() < 4, ERR_INVALID_DATA);
live_editor->_reparent_node_func(p_args[0], p_args[1], p_args[2], p_args[3]);
} else {
return ERR_SKIP;
r_captured = false;
}
return OK;
}
@ -180,7 +186,6 @@ void SceneDebugger::_save_node(ObjectID id, const String &p_path) {
Node *node = Object::cast_to<Node>(ObjectDB::get_instance(id));
ERR_FAIL_COND(!node);
WARN_PRINT("SAVING " + itos(id) + " TO " + p_path);
Ref<PackedScene> ps = memnew(PackedScene);
ps->pack(node);
ResourceSaver::save(p_path, ps);
@ -193,7 +198,7 @@ void SceneDebugger::_send_object_id(ObjectID p_id, int p_max_size) {
Array arr;
obj.serialize(arr);
ScriptDebugger::get_singleton()->send_message("inspect_object", arr);
EngineDebugger::get_singleton()->send_message("scene:inspect_object", arr);
}
void SceneDebugger::_set_object_property(ObjectID p_id, const String &p_property, const Variant &p_value) {
@ -216,7 +221,7 @@ void SceneDebugger::add_to_cache(const String &p_filename, Node *p_node) {
if (!debugger)
return;
if (ScriptDebugger::get_singleton() && p_filename != String()) {
if (EngineDebugger::get_script_debugger() && p_filename != String()) {
debugger->live_scene_edit_cache[p_filename].insert(p_node);
}
}
@ -487,7 +492,7 @@ void LiveEditor::_send_tree() {
// Encoded as a flat list depth fist.
SceneDebuggerTree tree(scene_tree->root);
tree.serialize(arr);
ScriptDebugger::get_singleton()->send_message("scene_tree", arr);
EngineDebugger::get_singleton()->send_message("scene:scene_tree", arr);
}
void LiveEditor::_node_path_func(const NodePath &p_path, int p_id) {

View file

@ -34,9 +34,10 @@
#include "core/array.h"
#include "core/object.h"
#include "core/pair.h"
#include "core/script_language.h"
#include "core/ustring.h"
class Script;
class SceneDebugger {
public:
@ -50,7 +51,7 @@ private:
static void _send_object_id(ObjectID p_id, int p_max_size = 1 << 20);
public:
static Error parse_message(const String &p_msg, const Array &p_args);
static Error parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured);
static void add_to_cache(const String &p_filename, Node *p_node);
static void remove_from_cache(const String &p_filename, Node *p_node);
#endif

View file

@ -30,6 +30,7 @@
#include "scene_tree.h"
#include "core/debugger/engine_debugger.h"
#include "core/io/marshalls.h"
#include "core/io/resource_loader.h"
#include "core/message_queue.h"
@ -38,7 +39,6 @@
#include "core/os/os.h"
#include "core/print_string.h"
#include "core/project_settings.h"
#include "core/script_debugger_remote.h"
#include "main/input_default.h"
#include "node.h"
#include "scene/debugger/scene_debugger.h"
@ -432,11 +432,11 @@ void SceneTree::input_event(const Ref<InputEvent> &p_event) {
call_group_flags(GROUP_CALL_REALTIME, "_viewports", "_vp_input", ev); //special one for GUI, as controls use their own process check
if (ScriptDebugger::get_singleton() && ScriptDebugger::get_singleton()->is_remote()) {
if (EngineDebugger::is_active()) {
//quit from game window using F8
Ref<InputEventKey> k = ev;
if (k.is_valid() && k->is_pressed() && !k->is_echo() && k->get_keycode() == KEY_F8) {
ScriptDebugger::get_singleton()->request_quit();
EngineDebugger::get_singleton()->send_message("request_quit", Array());
}
}
@ -1737,10 +1737,6 @@ SceneTree::SceneTree() {
last_screen_size = Size2(OS::get_singleton()->get_window_size().width, OS::get_singleton()->get_window_size().height);
_update_root_rect();
if (ScriptDebugger::get_singleton()) {
ScriptDebugger::get_singleton()->set_multiplayer(multiplayer);
}
root->set_physics_object_picking(GLOBAL_DEF("physics/common/enable_object_picking", true));
#ifdef TOOLS_ENABLED

View file

@ -31,6 +31,7 @@
#include "viewport.h"
#include "core/core_string_names.h"
#include "core/debugger/engine_debugger.h"
#include "core/os/input.h"
#include "core/os/os.h"
#include "core/project_settings.h"
@ -1927,12 +1928,12 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
mb->set_position(pos);
#ifdef DEBUG_ENABLED
if (ScriptDebugger::get_singleton() && gui.mouse_focus) {
if (EngineDebugger::get_singleton() && gui.mouse_focus) {
Array arr;
arr.push_back(gui.mouse_focus->get_path());
arr.push_back(gui.mouse_focus->get_class());
ScriptDebugger::get_singleton()->send_message("click_ctrl", arr);
EngineDebugger::get_singleton()->send_message("scene:click_ctrl", arr);
}
#endif

View file

@ -30,6 +30,7 @@
#include "audio_server.h"
#include "core/debugger/engine_debugger.h"
#include "core/io/resource_loader.h"
#include "core/os/file_access.h"
#include "core/os/os.h"
@ -992,7 +993,7 @@ void AudioServer::init() {
void AudioServer::update() {
#ifdef DEBUG_ENABLED
if (ScriptDebugger::get_singleton() && ScriptDebugger::get_singleton()->is_profiling()) {
if (EngineDebugger::is_profiling("servers")) {
// Driver time includes server time + effects times
// Server time includes effects times
@ -1030,7 +1031,8 @@ void AudioServer::update() {
values.push_back("audio_driver");
values.push_back(USEC_TO_SEC(driver_time));
ScriptDebugger::get_singleton()->add_profiling_frame_data("audio_thread", values);
values.push_front("audio_thread");
EngineDebugger::profiler_add_frame_data("servers", values);
}
// Reset profiling times

View file

@ -32,8 +32,8 @@
#include "broad_phase_basic.h"
#include "broad_phase_octree.h"
#include "core/debugger/engine_debugger.h"
#include "core/os/os.h"
#include "core/script_language.h"
#include "joints/cone_twist_joint_sw.h"
#include "joints/generic_6dof_joint_sw.h"
#include "joints/hinge_joint_sw.h"
@ -1467,7 +1467,7 @@ void PhysicsServerSW::flush_queries() {
flushing_queries = false;
if (ScriptDebugger::get_singleton() && ScriptDebugger::get_singleton()->is_profiling()) {
if (EngineDebugger::is_profiling("servers")) {
uint64_t total_time[SpaceSW::ELAPSED_TIME_MAX];
static const char *time_name[SpaceSW::ELAPSED_TIME_MAX] = {
@ -1498,7 +1498,8 @@ void PhysicsServerSW::flush_queries() {
values.push_back("flush_queries");
values.push_back(USEC_TO_SEC(OS::get_singleton()->get_ticks_usec() - time_beg));
ScriptDebugger::get_singleton()->add_profiling_frame_data("physics", values);
values.push_front("physics");
EngineDebugger::profiler_add_frame_data("server", values);
}
#endif
};

View file

@ -32,9 +32,9 @@
#include "broad_phase_2d_basic.h"
#include "broad_phase_2d_hash_grid.h"
#include "collision_solver_2d_sw.h"
#include "core/debugger/engine_debugger.h"
#include "core/os/os.h"
#include "core/project_settings.h"
#include "core/script_language.h"
#define FLUSH_QUERY_CHECK(m_object) \
ERR_FAIL_COND_MSG(m_object->get_space() && flushing_queries, "Can't change this state while flushing queries. Use call_deferred() or set_deferred() to change monitoring state instead.");
@ -1369,7 +1369,7 @@ void Physics2DServerSW::flush_queries() {
flushing_queries = false;
if (ScriptDebugger::get_singleton() && ScriptDebugger::get_singleton()->is_profiling()) {
if (EngineDebugger::is_profiling("servers")) {
uint64_t total_time[Space2DSW::ELAPSED_TIME_MAX];
static const char *time_name[Space2DSW::ELAPSED_TIME_MAX] = {
@ -1400,7 +1400,8 @@ void Physics2DServerSW::flush_queries() {
values.push_back("flush_queries");
values.push_back(USEC_TO_SEC(OS::get_singleton()->get_ticks_usec() - time_beg));
ScriptDebugger::get_singleton()->add_profiling_frame_data("physics_2d", values);
values.push_front("physics_2d");
EngineDebugger::profiler_add_frame_data("servers", values);
}
}

View file

@ -56,7 +56,6 @@
#include "audio_server.h"
#include "camera/camera_feed.h"
#include "camera_server.h"
#include "core/script_debugger_remote.h"
#include "navigation_2d_server.h"
#include "navigation_server.h"
#include "physics/physics_server_sw.h"
@ -67,27 +66,6 @@
#include "visual/shader_types.h"
#include "visual_server.h"
static void _debugger_get_resource_usage(ScriptDebuggerRemote::ResourceUsage *r_usage) {
List<VS::TextureInfo> tinfo;
VS::get_singleton()->texture_debug_usage(&tinfo);
for (List<VS::TextureInfo>::Element *E = tinfo.front(); E; E = E->next()) {
ScriptDebuggerRemote::ResourceInfo usage;
usage.path = E->get().path;
usage.vram = E->get().bytes;
usage.id = E->get().texture;
usage.type = "Texture";
if (E->get().depth == 0) {
usage.format = itos(E->get().width) + "x" + itos(E->get().height) + " " + Image::get_format_name(E->get().format);
} else {
usage.format = itos(E->get().width) + "x" + itos(E->get().height) + "x" + itos(E->get().depth) + " " + Image::get_format_name(E->get().format);
}
r_usage->infos.push_back(usage);
}
}
ShaderTypes *shader_types = NULL;
PhysicsServer *_createGodotPhysicsCallback() {
@ -189,8 +167,6 @@ void register_server_types() {
ClassDB::register_virtual_class<PhysicsDirectSpaceState>();
ClassDB::register_virtual_class<PhysicsShapeQueryResult>();
ScriptDebuggerRemote::resource_usage_func = _debugger_get_resource_usage;
// Physics 2D
GLOBAL_DEF(Physics2DServerManager::setting_property_name, "DEFAULT");
ProjectSettings::get_singleton()->set_custom_property_info(Physics2DServerManager::setting_property_name, PropertyInfo(Variant::STRING, Physics2DServerManager::setting_property_name, PROPERTY_HINT_ENUM, "DEFAULT"));