Merge pull request #76582 from reduz/threaded-debugger
Support threads in the script debugger
This commit is contained in:
commit
c4e582262f
@ -67,6 +67,7 @@ Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) {
|
|||||||
Array arr;
|
Array arr;
|
||||||
arr.push_back(name);
|
arr.push_back(name);
|
||||||
arr.push_back(type);
|
arr.push_back(type);
|
||||||
|
arr.push_back(value.get_type());
|
||||||
|
|
||||||
Variant var = value;
|
Variant var = value;
|
||||||
if (value.get_type() == Variant::OBJECT && value.get_validated_object() == nullptr) {
|
if (value.get_type() == Variant::OBJECT && value.get_validated_object() == nullptr) {
|
||||||
@ -74,7 +75,7 @@ Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int len = 0;
|
int len = 0;
|
||||||
Error err = encode_variant(var, nullptr, len, true);
|
Error err = encode_variant(var, nullptr, len, false);
|
||||||
if (err != OK) {
|
if (err != OK) {
|
||||||
ERR_PRINT("Failed to encode variant.");
|
ERR_PRINT("Failed to encode variant.");
|
||||||
}
|
}
|
||||||
@ -88,11 +89,12 @@ Array DebuggerMarshalls::ScriptStackVariable::serialize(int max_size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool DebuggerMarshalls::ScriptStackVariable::deserialize(const Array &p_arr) {
|
bool DebuggerMarshalls::ScriptStackVariable::deserialize(const Array &p_arr) {
|
||||||
CHECK_SIZE(p_arr, 3, "ScriptStackVariable");
|
CHECK_SIZE(p_arr, 4, "ScriptStackVariable");
|
||||||
name = p_arr[0];
|
name = p_arr[0];
|
||||||
type = p_arr[1];
|
type = p_arr[1];
|
||||||
value = p_arr[2];
|
var_type = p_arr[2];
|
||||||
CHECK_END(p_arr, 3, "ScriptStackVariable");
|
value = p_arr[3];
|
||||||
|
CHECK_END(p_arr, 4, "ScriptStackVariable");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ struct DebuggerMarshalls {
|
|||||||
String name;
|
String name;
|
||||||
Variant value;
|
Variant value;
|
||||||
int type = -1;
|
int type = -1;
|
||||||
|
int var_type = -1;
|
||||||
|
|
||||||
Array serialize(int max_size = 1 << 20); // 1 MiB default.
|
Array serialize(int max_size = 1 << 20); // 1 MiB default.
|
||||||
bool deserialize(const Array &p_arr);
|
bool deserialize(const Array &p_arr);
|
||||||
|
@ -111,14 +111,6 @@ Error EngineDebugger::capture_parse(const StringName &p_name, const String &p_ms
|
|||||||
return cap.capture(cap.data, p_msg, p_args, r_captured);
|
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_process_ticks, uint64_t p_physics_ticks, double p_physics_frame_time) {
|
void EngineDebugger::iteration(uint64_t p_frame_ticks, uint64_t p_process_ticks, uint64_t p_physics_ticks, double p_physics_frame_time) {
|
||||||
frame_time = USEC_TO_SEC(p_frame_ticks);
|
frame_time = USEC_TO_SEC(p_frame_ticks);
|
||||||
process_time = USEC_TO_SEC(p_process_ticks);
|
process_time = USEC_TO_SEC(p_process_ticks);
|
||||||
|
@ -126,7 +126,13 @@ public:
|
|||||||
void profiler_enable(const StringName &p_name, bool p_enabled, const Array &p_opts = Array());
|
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);
|
Error capture_parse(const StringName &p_name, const String &p_msg, const Array &p_args, bool &r_captured);
|
||||||
|
|
||||||
void line_poll();
|
void 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 (unlikely(poll_every % 2048) == 0) {
|
||||||
|
poll_events(false);
|
||||||
|
}
|
||||||
|
poll_every++;
|
||||||
|
}
|
||||||
|
|
||||||
virtual void poll_events(bool p_is_idle) {}
|
virtual void poll_events(bool p_is_idle) {}
|
||||||
virtual void send_message(const String &p_msg, const Array &p_data) = 0;
|
virtual void send_message(const String &p_msg, const Array &p_data) = 0;
|
||||||
|
@ -94,6 +94,7 @@ public:
|
|||||||
Error RemoteDebugger::_put_msg(String p_message, Array p_data) {
|
Error RemoteDebugger::_put_msg(String p_message, Array p_data) {
|
||||||
Array msg;
|
Array msg;
|
||||||
msg.push_back(p_message);
|
msg.push_back(p_message);
|
||||||
|
msg.push_back(Thread::get_caller_id());
|
||||||
msg.push_back(p_data);
|
msg.push_back(p_data);
|
||||||
Error err = peer->put_message(msg);
|
Error err = peer->put_message(msg);
|
||||||
if (err != OK) {
|
if (err != OK) {
|
||||||
@ -185,9 +186,9 @@ RemoteDebugger::ErrorMessage RemoteDebugger::_create_overflow_error(const String
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RemoteDebugger::flush_output() {
|
void RemoteDebugger::flush_output() {
|
||||||
|
MutexLock lock(mutex);
|
||||||
flush_thread = Thread::get_caller_id();
|
flush_thread = Thread::get_caller_id();
|
||||||
flushing = true;
|
flushing = true;
|
||||||
MutexLock lock(mutex);
|
|
||||||
if (!is_peer_connected()) {
|
if (!is_peer_connected()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -348,18 +349,65 @@ Error RemoteDebugger::_try_capture(const String &p_msg, const Array &p_data, boo
|
|||||||
return capture_parse(cap, msg, p_data, r_captured);
|
return capture_parse(cap, msg, p_data, r_captured);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RemoteDebugger::_poll_messages() {
|
||||||
|
MutexLock mutex_lock(mutex);
|
||||||
|
|
||||||
|
peer->poll();
|
||||||
|
while (peer->has_message()) {
|
||||||
|
Array cmd = peer->get_message();
|
||||||
|
ERR_CONTINUE(cmd.size() != 3);
|
||||||
|
ERR_CONTINUE(cmd[0].get_type() != Variant::STRING);
|
||||||
|
ERR_CONTINUE(cmd[1].get_type() != Variant::INT);
|
||||||
|
ERR_CONTINUE(cmd[2].get_type() != Variant::ARRAY);
|
||||||
|
|
||||||
|
Thread::ID thread = cmd[1];
|
||||||
|
|
||||||
|
if (!messages.has(thread)) {
|
||||||
|
continue; // This thread is not around to receive the messages
|
||||||
|
}
|
||||||
|
|
||||||
|
Message msg;
|
||||||
|
msg.message = cmd[0];
|
||||||
|
msg.data = cmd[2];
|
||||||
|
messages[thread].push_back(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RemoteDebugger::_has_messages() {
|
||||||
|
MutexLock mutex_lock(mutex);
|
||||||
|
return messages.has(Thread::get_caller_id()) && !messages[Thread::get_caller_id()].is_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
Array RemoteDebugger::_get_message() {
|
||||||
|
MutexLock mutex_lock(mutex);
|
||||||
|
ERR_FAIL_COND_V(!messages.has(Thread::get_caller_id()), Array());
|
||||||
|
List<Message> &message_list = messages[Thread::get_caller_id()];
|
||||||
|
ERR_FAIL_COND_V(message_list.is_empty(), Array());
|
||||||
|
|
||||||
|
Array msg;
|
||||||
|
msg.resize(2);
|
||||||
|
msg[0] = message_list.front()->get().message;
|
||||||
|
msg[1] = message_list.front()->get().data;
|
||||||
|
message_list.pop_front();
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
|
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)
|
//this function is called when there is a debugger break (bug on script)
|
||||||
//or when execution is paused from editor
|
//or when execution is paused from editor
|
||||||
|
|
||||||
if (script_debugger->is_skipping_breakpoints() && !p_is_error_breakpoint) {
|
{
|
||||||
return;
|
MutexLock lock(mutex);
|
||||||
}
|
// Tests that require mutex.
|
||||||
|
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.");
|
ERR_FAIL_COND_MSG(!is_peer_connected(), "Script Debugger failed to connect, but being used anyway.");
|
||||||
|
|
||||||
if (!peer->can_block()) {
|
if (!peer->can_block()) {
|
||||||
return; // Peer does not support blocking IO. We could at least send the error though.
|
return; // Peer does not support blocking IO. We could at least send the error though.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptLanguage *script_lang = script_debugger->get_break_language();
|
ScriptLanguage *script_lang = script_debugger->get_break_language();
|
||||||
@ -369,22 +417,33 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
|
|||||||
msg.push_back(error_str);
|
msg.push_back(error_str);
|
||||||
ERR_FAIL_COND(!script_lang);
|
ERR_FAIL_COND(!script_lang);
|
||||||
msg.push_back(script_lang->debug_get_stack_level_count() > 0);
|
msg.push_back(script_lang->debug_get_stack_level_count() > 0);
|
||||||
|
msg.push_back(Thread::get_caller_id() == Thread::get_main_id() ? String(RTR("Main Thread")) : itos(Thread::get_caller_id()));
|
||||||
if (allow_focus_steal_fn) {
|
if (allow_focus_steal_fn) {
|
||||||
allow_focus_steal_fn();
|
allow_focus_steal_fn();
|
||||||
}
|
}
|
||||||
send_message("debug_enter", msg);
|
send_message("debug_enter", msg);
|
||||||
|
|
||||||
Input::MouseMode mouse_mode = Input::get_singleton()->get_mouse_mode();
|
Input::MouseMode mouse_mode = Input::MOUSE_MODE_VISIBLE;
|
||||||
if (mouse_mode != Input::MOUSE_MODE_VISIBLE) {
|
|
||||||
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
|
if (Thread::get_caller_id() == Thread::get_main_id()) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MutexLock mutex_lock(mutex);
|
||||||
|
messages.insert(Thread::get_caller_id(), List<Message>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutex.lock();
|
||||||
while (is_peer_connected()) {
|
while (is_peer_connected()) {
|
||||||
|
mutex.unlock();
|
||||||
flush_output();
|
flush_output();
|
||||||
peer->poll();
|
|
||||||
|
|
||||||
if (peer->has_message()) {
|
_poll_messages();
|
||||||
Array cmd = peer->get_message();
|
|
||||||
|
if (_has_messages()) {
|
||||||
|
Array cmd = _get_message();
|
||||||
|
|
||||||
ERR_CONTINUE(cmd.size() != 2);
|
ERR_CONTINUE(cmd.size() != 2);
|
||||||
ERR_CONTINUE(cmd[0].get_type() != Variant::STRING);
|
ERR_CONTINUE(cmd[0].get_type() != Variant::STRING);
|
||||||
@ -479,14 +538,22 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
OS::get_singleton()->delay_usec(10000);
|
OS::get_singleton()->delay_usec(10000);
|
||||||
OS::get_singleton()->process_and_drop_events();
|
if (Thread::get_caller_id() == Thread::get_main_id()) {
|
||||||
|
// If this is a busy loop on the main thread, events still need to be processed.
|
||||||
|
OS::get_singleton()->process_and_drop_events();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
send_message("debug_exit", Array());
|
send_message("debug_exit", Array());
|
||||||
|
|
||||||
if (mouse_mode != Input::MOUSE_MODE_VISIBLE) {
|
if (Thread::get_caller_id() == Thread::get_main_id()) {
|
||||||
Input::get_singleton()->set_mouse_mode(mouse_mode);
|
if (mouse_mode != Input::MOUSE_MODE_VISIBLE) {
|
||||||
|
Input::get_singleton()->set_mouse_mode(mouse_mode);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MutexLock mutex_lock(mutex);
|
||||||
|
messages.erase(Thread::get_caller_id());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -496,9 +563,11 @@ void RemoteDebugger::poll_events(bool p_is_idle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
flush_output();
|
flush_output();
|
||||||
peer->poll();
|
|
||||||
while (peer->has_message()) {
|
_poll_messages();
|
||||||
Array arr = peer->get_message();
|
|
||||||
|
while (_has_messages()) {
|
||||||
|
Array arr = _get_message();
|
||||||
|
|
||||||
ERR_CONTINUE(arr.size() != 2);
|
ERR_CONTINUE(arr.size() != 2);
|
||||||
ERR_CONTINUE(arr[0].get_type() != Variant::STRING);
|
ERR_CONTINUE(arr[0].get_type() != Variant::STRING);
|
||||||
@ -604,6 +673,8 @@ RemoteDebugger::RemoteDebugger(Ref<RemoteDebuggerPeer> p_peer) {
|
|||||||
eh.errfunc = _err_handler;
|
eh.errfunc = _err_handler;
|
||||||
eh.userdata = this;
|
eh.userdata = this;
|
||||||
add_error_handler(&eh);
|
add_error_handler(&eh);
|
||||||
|
|
||||||
|
messages.insert(Thread::get_main_id(), List<Message>());
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoteDebugger::~RemoteDebugger() {
|
RemoteDebugger::~RemoteDebugger() {
|
||||||
|
@ -80,6 +80,17 @@ private:
|
|||||||
bool flushing = false;
|
bool flushing = false;
|
||||||
Thread::ID flush_thread = 0;
|
Thread::ID flush_thread = 0;
|
||||||
|
|
||||||
|
struct Message {
|
||||||
|
String message;
|
||||||
|
Array data;
|
||||||
|
};
|
||||||
|
|
||||||
|
HashMap<Thread::ID, List<Message>> messages;
|
||||||
|
|
||||||
|
void _poll_messages();
|
||||||
|
bool _has_messages();
|
||||||
|
Array _get_message();
|
||||||
|
|
||||||
PrintHandlerList phl;
|
PrintHandlerList phl;
|
||||||
static void _print_handler(void *p_this, const String &p_string, bool p_error, bool p_rich);
|
static void _print_handler(void *p_this, const String &p_string, bool p_error, bool p_rich);
|
||||||
ErrorHandlerList eh;
|
ErrorHandlerList eh;
|
||||||
|
@ -32,22 +32,19 @@
|
|||||||
|
|
||||||
#include "core/debugger/engine_debugger.h"
|
#include "core/debugger/engine_debugger.h"
|
||||||
|
|
||||||
|
thread_local int ScriptDebugger::lines_left = -1;
|
||||||
|
thread_local int ScriptDebugger::depth = -1;
|
||||||
|
thread_local ScriptLanguage *ScriptDebugger::break_lang = nullptr;
|
||||||
|
thread_local Vector<ScriptDebugger::StackInfo> ScriptDebugger::error_stack_info;
|
||||||
|
|
||||||
void ScriptDebugger::set_lines_left(int p_left) {
|
void ScriptDebugger::set_lines_left(int p_left) {
|
||||||
lines_left = p_left;
|
lines_left = p_left;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ScriptDebugger::get_lines_left() const {
|
|
||||||
return lines_left;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptDebugger::set_depth(int p_depth) {
|
void ScriptDebugger::set_depth(int p_depth) {
|
||||||
depth = p_depth;
|
depth = p_depth;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ScriptDebugger::get_depth() const {
|
|
||||||
return depth;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptDebugger::insert_breakpoint(int p_line, const StringName &p_source) {
|
void ScriptDebugger::insert_breakpoint(int p_line, const StringName &p_source) {
|
||||||
if (!breakpoints.has(p_line)) {
|
if (!breakpoints.has(p_line)) {
|
||||||
breakpoints[p_line] = HashSet<StringName>();
|
breakpoints[p_line] = HashSet<StringName>();
|
||||||
@ -66,13 +63,6 @@ void ScriptDebugger::remove_breakpoint(int p_line, const StringName &p_source) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
String ScriptDebugger::breakpoint_find_source(const String &p_source) const {
|
String ScriptDebugger::breakpoint_find_source(const String &p_source) const {
|
||||||
return p_source;
|
return p_source;
|
||||||
}
|
}
|
||||||
@ -100,7 +90,7 @@ void ScriptDebugger::send_error(const String &p_func, const String &p_file, int
|
|||||||
// Store stack info, this is ugly, but allows us to separate EngineDebugger and ScriptDebugger. There might be a better way.
|
// 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);
|
error_stack_info.append_array(p_stack_info);
|
||||||
EngineDebugger::get_singleton()->send_error(p_func, p_file, p_line, p_err, p_descr, p_editor_notify, p_type);
|
EngineDebugger::get_singleton()->send_error(p_func, p_file, p_line, p_err, p_descr, p_editor_notify, p_type);
|
||||||
error_stack_info.clear();
|
error_stack_info.clear(); // Clear because this is thread local
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<ScriptLanguage::StackInfo> ScriptDebugger::get_error_stack_info() const {
|
Vector<ScriptLanguage::StackInfo> ScriptDebugger::get_error_stack_info() const {
|
||||||
|
@ -40,21 +40,25 @@
|
|||||||
class ScriptDebugger {
|
class ScriptDebugger {
|
||||||
typedef ScriptLanguage::StackInfo StackInfo;
|
typedef ScriptLanguage::StackInfo StackInfo;
|
||||||
|
|
||||||
int lines_left = -1;
|
|
||||||
int depth = -1;
|
|
||||||
bool skip_breakpoints = false;
|
bool skip_breakpoints = false;
|
||||||
|
|
||||||
HashMap<int, HashSet<StringName>> breakpoints;
|
HashMap<int, HashSet<StringName>> breakpoints;
|
||||||
|
|
||||||
ScriptLanguage *break_lang = nullptr;
|
static thread_local int lines_left;
|
||||||
Vector<StackInfo> error_stack_info;
|
static thread_local int depth;
|
||||||
|
static thread_local ScriptLanguage *break_lang;
|
||||||
|
static thread_local Vector<StackInfo> error_stack_info;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void set_lines_left(int p_left);
|
void set_lines_left(int p_left);
|
||||||
int get_lines_left() const;
|
_ALWAYS_INLINE_ int get_lines_left() const {
|
||||||
|
return lines_left;
|
||||||
|
}
|
||||||
|
|
||||||
void set_depth(int p_depth);
|
void set_depth(int p_depth);
|
||||||
int get_depth() const;
|
_ALWAYS_INLINE_ int get_depth() const {
|
||||||
|
return depth;
|
||||||
|
}
|
||||||
|
|
||||||
String breakpoint_find_source(const String &p_source) const;
|
String breakpoint_find_source(const String &p_source) const;
|
||||||
void set_break_language(ScriptLanguage *p_lang) { break_lang = p_lang; }
|
void set_break_language(ScriptLanguage *p_lang) { break_lang = p_lang; }
|
||||||
@ -63,7 +67,12 @@ public:
|
|||||||
bool is_skipping_breakpoints();
|
bool is_skipping_breakpoints();
|
||||||
void insert_breakpoint(int p_line, const StringName &p_source);
|
void insert_breakpoint(int p_line, const StringName &p_source);
|
||||||
void remove_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;
|
_ALWAYS_INLINE_ bool is_breakpoint(int p_line, const StringName &p_source) const {
|
||||||
|
if (likely(!breakpoints.has(p_line))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return breakpoints[p_line].has(p_source);
|
||||||
|
}
|
||||||
void clear_breakpoints();
|
void clear_breakpoints();
|
||||||
const HashMap<int, HashSet<StringName>> &get_breakpoints() const { return breakpoints; }
|
const HashMap<int, HashSet<StringName>> &get_breakpoints() const { return breakpoints; }
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
</brief_description>
|
</brief_description>
|
||||||
<description>
|
<description>
|
||||||
A unit of execution in a process. Can run methods on [Object]s simultaneously. The use of synchronization via [Mutex] or [Semaphore] is advised if working with shared objects.
|
A unit of execution in a process. Can run methods on [Object]s simultaneously. The use of synchronization via [Mutex] or [Semaphore] is advised if working with shared objects.
|
||||||
[b]Note:[/b] Breakpoints won't break on code if it's running in a thread. This is a current limitation of the GDScript debugger.
|
|
||||||
[b]Warning:[/b]
|
[b]Warning:[/b]
|
||||||
To ensure proper cleanup without crashes or deadlocks, when a [Thread]'s reference count reaches zero and it is therefore destroyed, the following conditions must be met:
|
To ensure proper cleanup without crashes or deadlocks, when a [Thread]'s reference count reaches zero and it is therefore destroyed, the following conditions must be met:
|
||||||
- It must not have any [Mutex] objects locked.
|
- It must not have any [Mutex] objects locked.
|
||||||
|
@ -232,7 +232,7 @@ void EditorDebuggerInspector::add_stack_variable(const Array &p_array) {
|
|||||||
PropertyHint h = PROPERTY_HINT_NONE;
|
PropertyHint h = PROPERTY_HINT_NONE;
|
||||||
String hs;
|
String hs;
|
||||||
|
|
||||||
if (v.get_type() == Variant::OBJECT) {
|
if (var.var_type == Variant::OBJECT) {
|
||||||
v = Object::cast_to<EncodedObjectAsID>(v)->get_object_id();
|
v = Object::cast_to<EncodedObjectAsID>(v)->get_object_id();
|
||||||
h = PROPERTY_HINT_OBJECT_ID;
|
h = PROPERTY_HINT_OBJECT_ID;
|
||||||
hs = "Object";
|
hs = "Object";
|
||||||
|
@ -71,10 +71,12 @@
|
|||||||
|
|
||||||
using CameraOverride = EditorDebuggerNode::CameraOverride;
|
using CameraOverride = EditorDebuggerNode::CameraOverride;
|
||||||
|
|
||||||
void ScriptEditorDebugger::_put_msg(String p_message, Array p_data) {
|
void ScriptEditorDebugger::_put_msg(String p_message, Array p_data, uint64_t p_thread_id) {
|
||||||
|
ERR_FAIL_COND(p_thread_id == Thread::UNASSIGNED_ID);
|
||||||
if (is_session_active()) {
|
if (is_session_active()) {
|
||||||
Array msg;
|
Array msg;
|
||||||
msg.push_back(p_message);
|
msg.push_back(p_message);
|
||||||
|
msg.push_back(p_thread_id);
|
||||||
msg.push_back(p_data);
|
msg.push_back(p_data);
|
||||||
peer->put_message(msg);
|
peer->put_message(msg);
|
||||||
}
|
}
|
||||||
@ -98,31 +100,31 @@ void ScriptEditorDebugger::debug_skip_breakpoints() {
|
|||||||
|
|
||||||
Array msg;
|
Array msg;
|
||||||
msg.push_back(skip_breakpoints_value);
|
msg.push_back(skip_breakpoints_value);
|
||||||
_put_msg("set_skip_breakpoints", msg);
|
_put_msg("set_skip_breakpoints", msg, debugging_thread_id != Thread::UNASSIGNED_ID ? debugging_thread_id : Thread::MAIN_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEditorDebugger::debug_next() {
|
void ScriptEditorDebugger::debug_next() {
|
||||||
ERR_FAIL_COND(!breaked);
|
ERR_FAIL_COND(!is_breaked());
|
||||||
|
|
||||||
_put_msg("next", Array());
|
_put_msg("next", Array(), debugging_thread_id);
|
||||||
_clear_execution();
|
_clear_execution();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEditorDebugger::debug_step() {
|
void ScriptEditorDebugger::debug_step() {
|
||||||
ERR_FAIL_COND(!breaked);
|
ERR_FAIL_COND(!is_breaked());
|
||||||
|
|
||||||
_put_msg("step", Array());
|
_put_msg("step", Array(), debugging_thread_id);
|
||||||
_clear_execution();
|
_clear_execution();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEditorDebugger::debug_break() {
|
void ScriptEditorDebugger::debug_break() {
|
||||||
ERR_FAIL_COND(breaked);
|
ERR_FAIL_COND(is_breaked());
|
||||||
|
|
||||||
_put_msg("break", Array());
|
_put_msg("break", Array());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEditorDebugger::debug_continue() {
|
void ScriptEditorDebugger::debug_continue() {
|
||||||
ERR_FAIL_COND(!breaked);
|
ERR_FAIL_COND(!is_breaked());
|
||||||
|
|
||||||
// Allow focus stealing only if we actually run this client for security.
|
// Allow focus stealing only if we actually run this client for security.
|
||||||
if (remote_pid && EditorNode::get_singleton()->has_child_process(remote_pid)) {
|
if (remote_pid && EditorNode::get_singleton()->has_child_process(remote_pid)) {
|
||||||
@ -130,7 +132,7 @@ void ScriptEditorDebugger::debug_continue() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_clear_execution();
|
_clear_execution();
|
||||||
_put_msg("continue", Array());
|
_put_msg("continue", Array(), debugging_thread_id);
|
||||||
_put_msg("servers:foreground", Array());
|
_put_msg("servers:foreground", Array());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -299,43 +301,89 @@ Size2 ScriptEditorDebugger::get_minimum_size() const {
|
|||||||
return ms;
|
return ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_data) {
|
void ScriptEditorDebugger::_thread_debug_enter(uint64_t p_thread_id) {
|
||||||
|
ERR_FAIL_COND(!threads_debugged.has(p_thread_id));
|
||||||
|
ThreadDebugged &td = threads_debugged[p_thread_id];
|
||||||
|
_set_reason_text(td.error, MESSAGE_ERROR);
|
||||||
|
emit_signal(SNAME("breaked"), true, td.can_debug, td.error, td.has_stackdump);
|
||||||
|
if (!td.error.is_empty()) {
|
||||||
|
tabs->set_current_tab(0);
|
||||||
|
}
|
||||||
|
inspector->clear_cache(); // Take a chance to force remote objects update.
|
||||||
|
_put_msg("get_stack_dump", Array(), p_thread_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptEditorDebugger::_select_thread(int p_index) {
|
||||||
|
debugging_thread_id = threads->get_item_metadata(threads->get_selected());
|
||||||
|
_thread_debug_enter(debugging_thread_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptEditorDebugger::_parse_message(const String &p_msg, uint64_t p_thread_id, const Array &p_data) {
|
||||||
emit_signal(SNAME("debug_data"), p_msg, p_data);
|
emit_signal(SNAME("debug_data"), p_msg, p_data);
|
||||||
if (p_msg == "debug_enter") {
|
if (p_msg == "debug_enter") {
|
||||||
_put_msg("get_stack_dump", Array());
|
ERR_FAIL_COND(p_data.size() != 4);
|
||||||
|
|
||||||
ERR_FAIL_COND(p_data.size() != 3);
|
ThreadDebugged td;
|
||||||
bool can_continue = p_data[0];
|
td.name = p_data[3];
|
||||||
String error = p_data[1];
|
td.error = p_data[1];
|
||||||
bool has_stackdump = p_data[2];
|
td.can_debug = p_data[0];
|
||||||
breaked = true;
|
td.has_stackdump = p_data[2];
|
||||||
can_request_idle_draw = true;
|
td.thread_id = p_thread_id;
|
||||||
can_debug = can_continue;
|
static uint32_t order_inc = 0;
|
||||||
|
td.debug_order = order_inc++;
|
||||||
|
|
||||||
|
threads_debugged.insert(p_thread_id, td);
|
||||||
|
|
||||||
|
if (threads_debugged.size() == 1) {
|
||||||
|
// First thread that requests debug
|
||||||
|
debugging_thread_id = p_thread_id;
|
||||||
|
_thread_debug_enter(p_thread_id);
|
||||||
|
can_request_idle_draw = true;
|
||||||
|
if (is_move_to_foreground()) {
|
||||||
|
DisplayServer::get_singleton()->window_move_to_foreground();
|
||||||
|
}
|
||||||
|
profiler->set_enabled(false, false);
|
||||||
|
visual_profiler->set_enabled(false);
|
||||||
|
}
|
||||||
_update_buttons_state();
|
_update_buttons_state();
|
||||||
_set_reason_text(error, MESSAGE_ERROR);
|
|
||||||
emit_signal(SNAME("breaked"), true, can_continue, error, has_stackdump);
|
|
||||||
if (is_move_to_foreground()) {
|
|
||||||
DisplayServer::get_singleton()->window_move_to_foreground();
|
|
||||||
}
|
|
||||||
if (!error.is_empty()) {
|
|
||||||
tabs->set_current_tab(0);
|
|
||||||
}
|
|
||||||
profiler->set_enabled(false, false);
|
|
||||||
visual_profiler->set_enabled(false);
|
|
||||||
inspector->clear_cache(); // Take a chance to force remote objects update.
|
|
||||||
|
|
||||||
} else if (p_msg == "debug_exit") {
|
} else if (p_msg == "debug_exit") {
|
||||||
breaked = false;
|
threads_debugged.erase(p_thread_id);
|
||||||
can_debug = false;
|
if (p_thread_id == debugging_thread_id) {
|
||||||
_clear_execution();
|
_clear_execution();
|
||||||
_update_buttons_state();
|
if (threads_debugged.size() == 0) {
|
||||||
_set_reason_text(TTR("Execution resumed."), MESSAGE_SUCCESS);
|
debugging_thread_id = Thread::UNASSIGNED_ID;
|
||||||
emit_signal(SNAME("breaked"), false, false, "", false);
|
} else {
|
||||||
|
// Find next thread to debug.
|
||||||
|
uint32_t min_order = 0xFFFFFFFF;
|
||||||
|
uint64_t next_thread = Thread::UNASSIGNED_ID;
|
||||||
|
for (KeyValue<uint64_t, ThreadDebugged> T : threads_debugged) {
|
||||||
|
if (T.value.debug_order < min_order) {
|
||||||
|
min_order = T.value.debug_order;
|
||||||
|
next_thread = T.key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
profiler->set_enabled(true, false);
|
debugging_thread_id = next_thread;
|
||||||
profiler->disable_seeking();
|
}
|
||||||
|
|
||||||
visual_profiler->set_enabled(true);
|
if (debugging_thread_id == Thread::UNASSIGNED_ID) {
|
||||||
|
// Nothing else to debug.
|
||||||
|
profiler->set_enabled(true, false);
|
||||||
|
profiler->disable_seeking();
|
||||||
|
|
||||||
|
visual_profiler->set_enabled(true);
|
||||||
|
|
||||||
|
_set_reason_text(TTR("Execution resumed."), MESSAGE_SUCCESS);
|
||||||
|
emit_signal(SNAME("breaked"), false, false, "", false);
|
||||||
|
|
||||||
|
_update_buttons_state();
|
||||||
|
} else {
|
||||||
|
_thread_debug_enter(debugging_thread_id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_update_buttons_state();
|
||||||
|
}
|
||||||
|
|
||||||
} else if (p_msg == "set_pid") {
|
} else if (p_msg == "set_pid") {
|
||||||
ERR_FAIL_COND(p_data.size() < 1);
|
ERR_FAIL_COND(p_data.size() < 1);
|
||||||
@ -379,7 +427,6 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
|
|||||||
|
|
||||||
vmem_total->set_tooltip_text(TTR("Bytes:") + " " + itos(total));
|
vmem_total->set_tooltip_text(TTR("Bytes:") + " " + itos(total));
|
||||||
vmem_total->set_text(String::humanize_size(total));
|
vmem_total->set_text(String::humanize_size(total));
|
||||||
|
|
||||||
} else if (p_msg == "servers:drawn") {
|
} else if (p_msg == "servers:drawn") {
|
||||||
can_request_idle_draw = true;
|
can_request_idle_draw = true;
|
||||||
} else if (p_msg == "stack_dump") {
|
} else if (p_msg == "stack_dump") {
|
||||||
@ -414,11 +461,9 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
|
|||||||
inspector->clear_stack_variables();
|
inspector->clear_stack_variables();
|
||||||
ERR_FAIL_COND(p_data.size() != 1);
|
ERR_FAIL_COND(p_data.size() != 1);
|
||||||
emit_signal(SNAME("stack_frame_vars"), p_data[0]);
|
emit_signal(SNAME("stack_frame_vars"), p_data[0]);
|
||||||
|
|
||||||
} else if (p_msg == "stack_frame_var") {
|
} else if (p_msg == "stack_frame_var") {
|
||||||
inspector->add_stack_variable(p_data);
|
inspector->add_stack_variable(p_data);
|
||||||
emit_signal(SNAME("stack_frame_var"), p_data);
|
emit_signal(SNAME("stack_frame_var"), p_data);
|
||||||
|
|
||||||
} else if (p_msg == "output") {
|
} else if (p_msg == "output") {
|
||||||
ERR_FAIL_COND(p_data.size() != 2);
|
ERR_FAIL_COND(p_data.size() != 2);
|
||||||
|
|
||||||
@ -458,7 +503,6 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
|
|||||||
frame_data.write[i] = p_data[i];
|
frame_data.write[i] = p_data[i];
|
||||||
}
|
}
|
||||||
performance_profiler->add_profile_frame(frame_data);
|
performance_profiler->add_profile_frame(frame_data);
|
||||||
|
|
||||||
} else if (p_msg == "visual:profile_frame") {
|
} else if (p_msg == "visual:profile_frame") {
|
||||||
ServersDebugger::VisualProfilerFrame frame;
|
ServersDebugger::VisualProfilerFrame frame;
|
||||||
frame.deserialize(p_data);
|
frame.deserialize(p_data);
|
||||||
@ -477,7 +521,6 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
visual_profiler->add_frame_metric(metric);
|
visual_profiler->add_frame_metric(metric);
|
||||||
|
|
||||||
} else if (p_msg == "error") {
|
} else if (p_msg == "error") {
|
||||||
DebuggerMarshalls::OutputError oe;
|
DebuggerMarshalls::OutputError oe;
|
||||||
ERR_FAIL_COND_MSG(oe.deserialize(p_data) == false, "Failed to deserialize error message");
|
ERR_FAIL_COND_MSG(oe.deserialize(p_data) == false, "Failed to deserialize error message");
|
||||||
@ -625,13 +668,11 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
|
|||||||
} else {
|
} else {
|
||||||
error_count++;
|
error_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (p_msg == "servers:function_signature") {
|
} else if (p_msg == "servers:function_signature") {
|
||||||
// Cache a profiler signature.
|
// Cache a profiler signature.
|
||||||
ServersDebugger::ScriptFunctionSignature sig;
|
ServersDebugger::ScriptFunctionSignature sig;
|
||||||
sig.deserialize(p_data);
|
sig.deserialize(p_data);
|
||||||
profiler_signature[sig.id] = sig.name;
|
profiler_signature[sig.id] = sig.name;
|
||||||
|
|
||||||
} else if (p_msg == "servers:profile_frame" || p_msg == "servers:profile_total") {
|
} else if (p_msg == "servers:profile_frame" || p_msg == "servers:profile_total") {
|
||||||
EditorProfiler::Metric metric;
|
EditorProfiler::Metric metric;
|
||||||
ServersDebugger::ServersProfilerFrame frame;
|
ServersDebugger::ServersProfilerFrame frame;
|
||||||
@ -744,11 +785,9 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
|
|||||||
} else {
|
} else {
|
||||||
profiler->add_frame_metric(metric, true);
|
profiler->add_frame_metric(metric, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (p_msg == "request_quit") {
|
} else if (p_msg == "request_quit") {
|
||||||
emit_signal(SNAME("stop_requested"));
|
emit_signal(SNAME("stop_requested"));
|
||||||
_stop_and_notify();
|
_stop_and_notify();
|
||||||
|
|
||||||
} else if (p_msg == "performance:profile_names") {
|
} else if (p_msg == "performance:profile_names") {
|
||||||
Vector<StringName> monitors;
|
Vector<StringName> monitors;
|
||||||
monitors.resize(p_data.size());
|
monitors.resize(p_data.size());
|
||||||
@ -757,13 +796,11 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
|
|||||||
monitors.set(i, p_data[i]);
|
monitors.set(i, p_data[i]);
|
||||||
}
|
}
|
||||||
performance_profiler->update_monitors(monitors);
|
performance_profiler->update_monitors(monitors);
|
||||||
|
|
||||||
} else if (p_msg == "filesystem:update_file") {
|
} else if (p_msg == "filesystem:update_file") {
|
||||||
ERR_FAIL_COND(p_data.size() < 1);
|
ERR_FAIL_COND(p_data.size() < 1);
|
||||||
if (EditorFileSystem::get_singleton()) {
|
if (EditorFileSystem::get_singleton()) {
|
||||||
EditorFileSystem::get_singleton()->update_file(p_data[0]);
|
EditorFileSystem::get_singleton()->update_file(p_data[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
int colon_index = p_msg.find_char(':');
|
int colon_index = p_msg.find_char(':');
|
||||||
ERR_FAIL_COND_MSG(colon_index < 1, "Invalid message received");
|
ERR_FAIL_COND_MSG(colon_index < 1, "Invalid message received");
|
||||||
@ -878,7 +915,7 @@ void ScriptEditorDebugger::_notification(int p_what) {
|
|||||||
msg.push_back(cam->get_far());
|
msg.push_back(cam->get_far());
|
||||||
_put_msg("scene:override_camera_3D:transform", msg);
|
_put_msg("scene:override_camera_3D:transform", msg);
|
||||||
}
|
}
|
||||||
if (breaked && can_request_idle_draw) {
|
if (is_breaked() && can_request_idle_draw) {
|
||||||
_put_msg("servers:draw", Array());
|
_put_msg("servers:draw", Array());
|
||||||
can_request_idle_draw = false;
|
can_request_idle_draw = false;
|
||||||
}
|
}
|
||||||
@ -888,11 +925,12 @@ void ScriptEditorDebugger::_notification(int p_what) {
|
|||||||
|
|
||||||
while (peer.is_valid() && peer->has_message()) {
|
while (peer.is_valid() && peer->has_message()) {
|
||||||
Array arr = peer->get_message();
|
Array arr = peer->get_message();
|
||||||
if (arr.size() != 2 || arr[0].get_type() != Variant::STRING || arr[1].get_type() != Variant::ARRAY) {
|
if (arr.size() != 3 || arr[0].get_type() != Variant::STRING || arr[1].get_type() != Variant::INT || arr[2].get_type() != Variant::ARRAY) {
|
||||||
_stop_and_notify();
|
_stop_and_notify();
|
||||||
ERR_FAIL_MSG("Invalid message format received from peer");
|
ERR_FAIL_MSG("Invalid message format received from peer");
|
||||||
}
|
}
|
||||||
_parse_message(arr[0], arr[1]);
|
|
||||||
|
_parse_message(arr[0], arr[1], arr[2]);
|
||||||
|
|
||||||
if (OS::get_singleton()->get_ticks_msec() > until) {
|
if (OS::get_singleton()->get_ticks_msec() > until) {
|
||||||
break;
|
break;
|
||||||
@ -959,8 +997,6 @@ void ScriptEditorDebugger::start(Ref<RemoteDebuggerPeer> p_peer) {
|
|||||||
performance_profiler->reset();
|
performance_profiler->reset();
|
||||||
|
|
||||||
set_process(true);
|
set_process(true);
|
||||||
breaked = false;
|
|
||||||
can_debug = true;
|
|
||||||
camera_override = CameraOverride::OVERRIDE_NONE;
|
camera_override = CameraOverride::OVERRIDE_NONE;
|
||||||
|
|
||||||
tabs->set_current_tab(0);
|
tabs->set_current_tab(0);
|
||||||
@ -973,13 +1009,35 @@ void ScriptEditorDebugger::_update_buttons_state() {
|
|||||||
const bool active = is_session_active();
|
const bool active = is_session_active();
|
||||||
const bool has_editor_tree = active && editor_remote_tree && editor_remote_tree->get_selected();
|
const bool has_editor_tree = active && editor_remote_tree && editor_remote_tree->get_selected();
|
||||||
vmem_refresh->set_disabled(!active);
|
vmem_refresh->set_disabled(!active);
|
||||||
step->set_disabled(!active || !breaked || !can_debug);
|
step->set_disabled(!active || !is_breaked() || !is_debuggable());
|
||||||
next->set_disabled(!active || !breaked || !can_debug);
|
next->set_disabled(!active || !is_breaked() || !is_debuggable());
|
||||||
copy->set_disabled(!active || !breaked);
|
copy->set_disabled(!active || !is_breaked());
|
||||||
docontinue->set_disabled(!active || !breaked);
|
docontinue->set_disabled(!active || !is_breaked());
|
||||||
dobreak->set_disabled(!active || breaked);
|
dobreak->set_disabled(!active || is_breaked());
|
||||||
le_clear->set_disabled(!active);
|
le_clear->set_disabled(!active);
|
||||||
le_set->set_disabled(!has_editor_tree);
|
le_set->set_disabled(!has_editor_tree);
|
||||||
|
|
||||||
|
thread_list_updating = true;
|
||||||
|
LocalVector<ThreadDebugged *> threadss;
|
||||||
|
for (KeyValue<uint64_t, ThreadDebugged> &I : threads_debugged) {
|
||||||
|
threadss.push_back(&I.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
threadss.sort_custom<ThreadSort>();
|
||||||
|
threads->clear();
|
||||||
|
int32_t selected_index = -1;
|
||||||
|
for (uint32_t i = 0; i < threadss.size(); i++) {
|
||||||
|
if (debugging_thread_id == threadss[i]->thread_id) {
|
||||||
|
selected_index = i;
|
||||||
|
}
|
||||||
|
threads->add_item(threadss[i]->name);
|
||||||
|
threads->set_item_metadata(threads->get_item_count() - 1, threadss[i]->thread_id);
|
||||||
|
}
|
||||||
|
if (selected_index != -1) {
|
||||||
|
threads->select(selected_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_list_updating = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEditorDebugger::_stop_and_notify() {
|
void ScriptEditorDebugger::_stop_and_notify() {
|
||||||
@ -990,8 +1048,8 @@ void ScriptEditorDebugger::_stop_and_notify() {
|
|||||||
|
|
||||||
void ScriptEditorDebugger::stop() {
|
void ScriptEditorDebugger::stop() {
|
||||||
set_process(false);
|
set_process(false);
|
||||||
breaked = false;
|
threads_debugged.clear();
|
||||||
can_debug = false;
|
debugging_thread_id = Thread::UNASSIGNED_ID;
|
||||||
remote_pid = 0;
|
remote_pid = 0;
|
||||||
_clear_execution();
|
_clear_execution();
|
||||||
|
|
||||||
@ -1043,7 +1101,7 @@ void ScriptEditorDebugger::_profiler_activate(bool p_enable, int p_type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEditorDebugger::_profiler_seeked() {
|
void ScriptEditorDebugger::_profiler_seeked() {
|
||||||
if (breaked) {
|
if (is_breaked()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
debug_break();
|
debug_break();
|
||||||
@ -1067,7 +1125,7 @@ void ScriptEditorDebugger::_export_csv() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String ScriptEditorDebugger::get_var_value(const String &p_var) const {
|
String ScriptEditorDebugger::get_var_value(const String &p_var) const {
|
||||||
if (!breaked) {
|
if (!is_breaked()) {
|
||||||
return String();
|
return String();
|
||||||
}
|
}
|
||||||
return inspector->get_stack_variable(p_var);
|
return inspector->get_stack_variable(p_var);
|
||||||
@ -1255,7 +1313,7 @@ bool ScriptEditorDebugger::request_stack_dump(const int &p_frame) {
|
|||||||
|
|
||||||
Array msg;
|
Array msg;
|
||||||
msg.push_back(p_frame);
|
msg.push_back(p_frame);
|
||||||
_put_msg("get_stack_frame_vars", msg);
|
_put_msg("get_stack_frame_vars", msg, debugging_thread_id);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1407,7 +1465,7 @@ void ScriptEditorDebugger::set_breakpoint(const String &p_path, int p_line, bool
|
|||||||
msg.push_back(p_path);
|
msg.push_back(p_path);
|
||||||
msg.push_back(p_line);
|
msg.push_back(p_line);
|
||||||
msg.push_back(p_enabled);
|
msg.push_back(p_enabled);
|
||||||
_put_msg("breakpoint", msg);
|
_put_msg("breakpoint", msg, debugging_thread_id != Thread::UNASSIGNED_ID ? debugging_thread_id : Thread::MAIN_ID);
|
||||||
|
|
||||||
TreeItem *path_item = breakpoints_tree->search_item_text(p_path);
|
TreeItem *path_item = breakpoints_tree->search_item_text(p_path);
|
||||||
if (path_item == nullptr) {
|
if (path_item == nullptr) {
|
||||||
@ -1450,7 +1508,7 @@ void ScriptEditorDebugger::set_breakpoint(const String &p_path, int p_line, bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEditorDebugger::reload_scripts() {
|
void ScriptEditorDebugger::reload_scripts() {
|
||||||
_put_msg("reload_scripts", Array());
|
_put_msg("reload_scripts", Array(), debugging_thread_id != Thread::UNASSIGNED_ID ? debugging_thread_id : Thread::MAIN_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ScriptEditorDebugger::is_skip_breakpoints() {
|
bool ScriptEditorDebugger::is_skip_breakpoints() {
|
||||||
@ -1804,15 +1862,26 @@ ScriptEditorDebugger::ScriptEditorDebugger() {
|
|||||||
sc->set_h_size_flags(SIZE_EXPAND_FILL);
|
sc->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||||
parent_sc->add_child(sc);
|
parent_sc->add_child(sc);
|
||||||
|
|
||||||
|
VBoxContainer *stack_vb = memnew(VBoxContainer);
|
||||||
|
stack_vb->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
sc->add_child(stack_vb);
|
||||||
|
HBoxContainer *thread_hb = memnew(HBoxContainer);
|
||||||
|
stack_vb->add_child(thread_hb);
|
||||||
|
thread_hb->add_child(memnew(Label(TTR("Thread:"))));
|
||||||
|
threads = memnew(OptionButton);
|
||||||
|
thread_hb->add_child(threads);
|
||||||
|
threads->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
threads->connect("item_selected", callable_mp(this, &ScriptEditorDebugger::_select_thread));
|
||||||
|
|
||||||
stack_dump = memnew(Tree);
|
stack_dump = memnew(Tree);
|
||||||
stack_dump->set_allow_reselect(true);
|
stack_dump->set_allow_reselect(true);
|
||||||
stack_dump->set_columns(1);
|
stack_dump->set_columns(1);
|
||||||
stack_dump->set_column_titles_visible(true);
|
stack_dump->set_column_titles_visible(true);
|
||||||
stack_dump->set_column_title(0, TTR("Stack Frames"));
|
stack_dump->set_column_title(0, TTR("Stack Frames"));
|
||||||
stack_dump->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
||||||
stack_dump->set_hide_root(true);
|
stack_dump->set_hide_root(true);
|
||||||
|
stack_dump->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||||
stack_dump->connect("cell_selected", callable_mp(this, &ScriptEditorDebugger::_stack_dump_frame_selected));
|
stack_dump->connect("cell_selected", callable_mp(this, &ScriptEditorDebugger::_stack_dump_frame_selected));
|
||||||
sc->add_child(stack_dump);
|
stack_vb->add_child(stack_dump);
|
||||||
|
|
||||||
VBoxContainer *inspector_vbox = memnew(VBoxContainer);
|
VBoxContainer *inspector_vbox = memnew(VBoxContainer);
|
||||||
inspector_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
|
inspector_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||||
|
@ -138,6 +138,7 @@ private:
|
|||||||
|
|
||||||
Tree *stack_dump = nullptr;
|
Tree *stack_dump = nullptr;
|
||||||
LineEdit *search = nullptr;
|
LineEdit *search = nullptr;
|
||||||
|
OptionButton *threads = nullptr;
|
||||||
EditorDebuggerInspector *inspector = nullptr;
|
EditorDebuggerInspector *inspector = nullptr;
|
||||||
SceneDebuggerTree *scene_tree = nullptr;
|
SceneDebuggerTree *scene_tree = nullptr;
|
||||||
|
|
||||||
@ -152,19 +153,39 @@ private:
|
|||||||
EditorPerformanceProfiler *performance_profiler = nullptr;
|
EditorPerformanceProfiler *performance_profiler = nullptr;
|
||||||
|
|
||||||
OS::ProcessID remote_pid = 0;
|
OS::ProcessID remote_pid = 0;
|
||||||
bool breaked = false;
|
|
||||||
bool can_debug = false;
|
|
||||||
bool move_to_foreground = true;
|
bool move_to_foreground = true;
|
||||||
bool can_request_idle_draw = false;
|
bool can_request_idle_draw = false;
|
||||||
|
|
||||||
bool live_debug;
|
bool live_debug;
|
||||||
|
|
||||||
|
uint64_t debugging_thread_id = Thread::UNASSIGNED_ID;
|
||||||
|
|
||||||
|
struct ThreadDebugged {
|
||||||
|
String name;
|
||||||
|
String error;
|
||||||
|
bool can_debug = false;
|
||||||
|
bool has_stackdump = false;
|
||||||
|
uint32_t debug_order = 0;
|
||||||
|
uint64_t thread_id = Thread::UNASSIGNED_ID; // for order
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ThreadSort {
|
||||||
|
bool operator()(const ThreadDebugged *a, const ThreadDebugged *b) const {
|
||||||
|
return a->debug_order < b->debug_order;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
HashMap<uint64_t, ThreadDebugged> threads_debugged;
|
||||||
|
bool thread_list_updating = false;
|
||||||
|
|
||||||
|
void _select_thread(int p_index);
|
||||||
|
|
||||||
EditorDebuggerNode::CameraOverride camera_override;
|
EditorDebuggerNode::CameraOverride camera_override;
|
||||||
|
|
||||||
void _stack_dump_frame_selected();
|
void _stack_dump_frame_selected();
|
||||||
|
|
||||||
void _file_selected(const String &p_file);
|
void _file_selected(const String &p_file);
|
||||||
void _parse_message(const String &p_msg, const Array &p_data);
|
void _parse_message(const String &p_msg, uint64_t p_thread_id, const Array &p_data);
|
||||||
void _set_reason_text(const String &p_reason, MessageType p_type);
|
void _set_reason_text(const String &p_reason, MessageType p_type);
|
||||||
void _update_buttons_state();
|
void _update_buttons_state();
|
||||||
void _remote_object_selected(ObjectID p_object);
|
void _remote_object_selected(ObjectID p_object);
|
||||||
@ -200,7 +221,7 @@ private:
|
|||||||
void _item_menu_id_pressed(int p_option);
|
void _item_menu_id_pressed(int p_option);
|
||||||
void _tab_changed(int p_tab);
|
void _tab_changed(int p_tab);
|
||||||
|
|
||||||
void _put_msg(String p_message, Array p_data);
|
void _put_msg(String p_message, Array p_data, uint64_t p_thread_id = Thread::MAIN_ID);
|
||||||
void _export_csv();
|
void _export_csv();
|
||||||
|
|
||||||
void _clear_execution();
|
void _clear_execution();
|
||||||
@ -213,6 +234,8 @@ private:
|
|||||||
|
|
||||||
String _format_frame_text(const ScriptLanguage::StackInfo *info);
|
String _format_frame_text(const ScriptLanguage::StackInfo *info);
|
||||||
|
|
||||||
|
void _thread_debug_enter(uint64_t p_thread_id);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void _notification(int p_what);
|
void _notification(int p_what);
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
@ -238,9 +261,9 @@ public:
|
|||||||
void debug_step();
|
void debug_step();
|
||||||
void debug_break();
|
void debug_break();
|
||||||
void debug_continue();
|
void debug_continue();
|
||||||
bool is_breaked() const { return breaked; }
|
bool is_breaked() const { return threads_debugged.size() > 0; }
|
||||||
bool is_debuggable() const { return can_debug; }
|
bool is_debuggable() const { return threads_debugged.size() > 0 && threads_debugged[debugging_thread_id].can_debug; }
|
||||||
bool is_session_active() { return peer.is_valid() && peer->is_peer_connected(); };
|
bool is_session_active() { return peer.is_valid() && peer->is_peer_connected(); }
|
||||||
int get_remote_pid() const { return remote_pid; }
|
int get_remote_pid() const { return remote_pid; }
|
||||||
|
|
||||||
bool is_move_to_foreground() const;
|
bool is_move_to_foreground() const;
|
||||||
|
@ -2094,10 +2094,7 @@ String GDScriptLanguage::get_extension() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GDScriptLanguage::finish() {
|
void GDScriptLanguage::finish() {
|
||||||
if (_call_stack) {
|
_call_stack.free();
|
||||||
memdelete_arr(_call_stack);
|
|
||||||
_call_stack = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear the cache before parsing the script_list
|
// Clear the cache before parsing the script_list
|
||||||
GDScriptCache::clear();
|
GDScriptCache::clear();
|
||||||
@ -2140,12 +2137,12 @@ void GDScriptLanguage::profiling_start() {
|
|||||||
|
|
||||||
SelfList<GDScriptFunction> *elem = function_list.first();
|
SelfList<GDScriptFunction> *elem = function_list.first();
|
||||||
while (elem) {
|
while (elem) {
|
||||||
elem->self()->profile.call_count = 0;
|
elem->self()->profile.call_count.set(0);
|
||||||
elem->self()->profile.self_time = 0;
|
elem->self()->profile.self_time.set(0);
|
||||||
elem->self()->profile.total_time = 0;
|
elem->self()->profile.total_time.set(0);
|
||||||
elem->self()->profile.frame_call_count = 0;
|
elem->self()->profile.frame_call_count.set(0);
|
||||||
elem->self()->profile.frame_self_time = 0;
|
elem->self()->profile.frame_self_time.set(0);
|
||||||
elem->self()->profile.frame_total_time = 0;
|
elem->self()->profile.frame_total_time.set(0);
|
||||||
elem->self()->profile.last_frame_call_count = 0;
|
elem->self()->profile.last_frame_call_count = 0;
|
||||||
elem->self()->profile.last_frame_self_time = 0;
|
elem->self()->profile.last_frame_self_time = 0;
|
||||||
elem->self()->profile.last_frame_total_time = 0;
|
elem->self()->profile.last_frame_total_time = 0;
|
||||||
@ -2175,9 +2172,9 @@ int GDScriptLanguage::profiling_get_accumulated_data(ProfilingInfo *p_info_arr,
|
|||||||
if (current >= p_info_max) {
|
if (current >= p_info_max) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
p_info_arr[current].call_count = elem->self()->profile.call_count;
|
p_info_arr[current].call_count = elem->self()->profile.call_count.get();
|
||||||
p_info_arr[current].self_time = elem->self()->profile.self_time;
|
p_info_arr[current].self_time = elem->self()->profile.self_time.get();
|
||||||
p_info_arr[current].total_time = elem->self()->profile.total_time;
|
p_info_arr[current].total_time = elem->self()->profile.total_time.get();
|
||||||
p_info_arr[current].signature = elem->self()->profile.signature;
|
p_info_arr[current].signature = elem->self()->profile.signature;
|
||||||
elem = elem->next();
|
elem = elem->next();
|
||||||
current++;
|
current++;
|
||||||
@ -2395,12 +2392,12 @@ void GDScriptLanguage::frame() {
|
|||||||
|
|
||||||
SelfList<GDScriptFunction> *elem = function_list.first();
|
SelfList<GDScriptFunction> *elem = function_list.first();
|
||||||
while (elem) {
|
while (elem) {
|
||||||
elem->self()->profile.last_frame_call_count = elem->self()->profile.frame_call_count;
|
elem->self()->profile.last_frame_call_count = elem->self()->profile.frame_call_count.get();
|
||||||
elem->self()->profile.last_frame_self_time = elem->self()->profile.frame_self_time;
|
elem->self()->profile.last_frame_self_time = elem->self()->profile.frame_self_time.get();
|
||||||
elem->self()->profile.last_frame_total_time = elem->self()->profile.frame_total_time;
|
elem->self()->profile.last_frame_total_time = elem->self()->profile.frame_total_time.get();
|
||||||
elem->self()->profile.frame_call_count = 0;
|
elem->self()->profile.frame_call_count.set(0);
|
||||||
elem->self()->profile.frame_self_time = 0;
|
elem->self()->profile.frame_self_time.set(0);
|
||||||
elem->self()->profile.frame_total_time = 0;
|
elem->self()->profile.frame_total_time.set(0);
|
||||||
elem = elem->next();
|
elem = elem->next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2607,6 +2604,8 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
|
|||||||
return c->identifier != nullptr ? String(c->identifier->name) : String();
|
return c->identifier != nullptr ? String(c->identifier->name) : String();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thread_local GDScriptLanguage::CallStack GDScriptLanguage::_call_stack;
|
||||||
|
|
||||||
GDScriptLanguage::GDScriptLanguage() {
|
GDScriptLanguage::GDScriptLanguage() {
|
||||||
calls = 0;
|
calls = 0;
|
||||||
ERR_FAIL_COND(singleton);
|
ERR_FAIL_COND(singleton);
|
||||||
@ -2626,18 +2625,14 @@ GDScriptLanguage::GDScriptLanguage() {
|
|||||||
profiling = false;
|
profiling = false;
|
||||||
script_frame_time = 0;
|
script_frame_time = 0;
|
||||||
|
|
||||||
_debug_call_stack_pos = 0;
|
|
||||||
int dmcs = GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "512," + itos(GDScriptFunction::MAX_CALL_DEPTH - 1) + ",1"), 1024);
|
int dmcs = GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "512," + itos(GDScriptFunction::MAX_CALL_DEPTH - 1) + ",1"), 1024);
|
||||||
|
|
||||||
if (EngineDebugger::is_active()) {
|
if (EngineDebugger::is_active()) {
|
||||||
//debugging enabled!
|
//debugging enabled!
|
||||||
|
|
||||||
_debug_max_call_stack = dmcs;
|
_debug_max_call_stack = dmcs;
|
||||||
_call_stack = memnew_arr(CallLevel, _debug_max_call_stack + 1);
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
_debug_max_call_stack = 0;
|
_debug_max_call_stack = 0;
|
||||||
_call_stack = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
|
@ -364,12 +364,26 @@ class GDScriptLanguage : public ScriptLanguage {
|
|||||||
int *line = nullptr;
|
int *line = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
int _debug_parse_err_line;
|
static thread_local int _debug_parse_err_line;
|
||||||
String _debug_parse_err_file;
|
static thread_local String _debug_parse_err_file;
|
||||||
String _debug_error;
|
static thread_local String _debug_error;
|
||||||
int _debug_call_stack_pos;
|
struct CallStack {
|
||||||
int _debug_max_call_stack;
|
CallLevel *levels = nullptr;
|
||||||
CallLevel *_call_stack = nullptr;
|
int stack_pos = 0;
|
||||||
|
|
||||||
|
void free() {
|
||||||
|
if (levels) {
|
||||||
|
memdelete(levels);
|
||||||
|
levels = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~CallStack() {
|
||||||
|
free();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static thread_local CallStack _call_stack;
|
||||||
|
int _debug_max_call_stack = 0;
|
||||||
|
|
||||||
void _add_global(const StringName &p_name, const Variant &p_value);
|
void _add_global(const StringName &p_name, const Variant &p_value);
|
||||||
|
|
||||||
@ -395,59 +409,51 @@ public:
|
|||||||
bool debug_break_parse(const String &p_file, int p_line, const String &p_error);
|
bool debug_break_parse(const String &p_file, int p_line, const String &p_error);
|
||||||
|
|
||||||
_FORCE_INLINE_ void enter_function(GDScriptInstance *p_instance, GDScriptFunction *p_function, Variant *p_stack, int *p_ip, int *p_line) {
|
_FORCE_INLINE_ void enter_function(GDScriptInstance *p_instance, GDScriptFunction *p_function, Variant *p_stack, int *p_ip, int *p_line) {
|
||||||
if (Thread::get_main_id() != Thread::get_caller_id()) {
|
if (unlikely(_call_stack.levels == nullptr)) {
|
||||||
return; //no support for other threads than main for now
|
_call_stack.levels = memnew_arr(CallLevel, _debug_max_call_stack + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) {
|
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);
|
EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_debug_call_stack_pos >= _debug_max_call_stack) {
|
if (_call_stack.stack_pos >= _debug_max_call_stack) {
|
||||||
//stack overflow
|
//stack overflow
|
||||||
_debug_error = vformat("Stack overflow (stack size: %s). Check for infinite recursion in your script.", _debug_max_call_stack);
|
_debug_error = vformat("Stack overflow (stack size: %s). Check for infinite recursion in your script.", _debug_max_call_stack);
|
||||||
EngineDebugger::get_script_debugger()->debug(this);
|
EngineDebugger::get_script_debugger()->debug(this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_call_stack[_debug_call_stack_pos].stack = p_stack;
|
_call_stack.levels[_call_stack.stack_pos].stack = p_stack;
|
||||||
_call_stack[_debug_call_stack_pos].instance = p_instance;
|
_call_stack.levels[_call_stack.stack_pos].instance = p_instance;
|
||||||
_call_stack[_debug_call_stack_pos].function = p_function;
|
_call_stack.levels[_call_stack.stack_pos].function = p_function;
|
||||||
_call_stack[_debug_call_stack_pos].ip = p_ip;
|
_call_stack.levels[_call_stack.stack_pos].ip = p_ip;
|
||||||
_call_stack[_debug_call_stack_pos].line = p_line;
|
_call_stack.levels[_call_stack.stack_pos].line = p_line;
|
||||||
_debug_call_stack_pos++;
|
_call_stack.stack_pos++;
|
||||||
}
|
}
|
||||||
|
|
||||||
_FORCE_INLINE_ void exit_function() {
|
_FORCE_INLINE_ void exit_function() {
|
||||||
if (Thread::get_main_id() != Thread::get_caller_id()) {
|
|
||||||
return; //no support for other threads than main for now
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EngineDebugger::get_script_debugger()->get_lines_left() > 0 && EngineDebugger::get_script_debugger()->get_depth() >= 0) {
|
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);
|
EngineDebugger::get_script_debugger()->set_depth(EngineDebugger::get_script_debugger()->get_depth() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_debug_call_stack_pos == 0) {
|
if (_call_stack.stack_pos == 0) {
|
||||||
_debug_error = "Stack Underflow (Engine Bug)";
|
_debug_error = "Stack Underflow (Engine Bug)";
|
||||||
EngineDebugger::get_script_debugger()->debug(this);
|
EngineDebugger::get_script_debugger()->debug(this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_debug_call_stack_pos--;
|
_call_stack.stack_pos--;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual Vector<StackInfo> debug_get_current_stack_info() override {
|
virtual Vector<StackInfo> debug_get_current_stack_info() override {
|
||||||
if (Thread::get_main_id() != Thread::get_caller_id()) {
|
|
||||||
return Vector<StackInfo>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector<StackInfo> csi;
|
Vector<StackInfo> csi;
|
||||||
csi.resize(_debug_call_stack_pos);
|
csi.resize(_call_stack.stack_pos);
|
||||||
for (int i = 0; i < _debug_call_stack_pos; i++) {
|
for (int i = 0; i < _call_stack.stack_pos; i++) {
|
||||||
csi.write[_debug_call_stack_pos - i - 1].line = _call_stack[i].line ? *_call_stack[i].line : 0;
|
csi.write[_call_stack.stack_pos - i - 1].line = _call_stack.levels[i].line ? *_call_stack.levels[i].line : 0;
|
||||||
if (_call_stack[i].function) {
|
if (_call_stack.levels[i].function) {
|
||||||
csi.write[_debug_call_stack_pos - i - 1].func = _call_stack[i].function->get_name();
|
csi.write[_call_stack.stack_pos - i - 1].func = _call_stack.levels[i].function->get_name();
|
||||||
csi.write[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_script_path();
|
csi.write[_call_stack.stack_pos - i - 1].file = _call_stack.levels[i].function->get_script()->get_script_path();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return csi;
|
return csi;
|
||||||
|
@ -233,6 +233,10 @@ Script *GDScriptLanguage::create_script() const {
|
|||||||
|
|
||||||
/* DEBUGGER FUNCTIONS */
|
/* DEBUGGER FUNCTIONS */
|
||||||
|
|
||||||
|
thread_local int GDScriptLanguage::_debug_parse_err_line = -1;
|
||||||
|
thread_local String GDScriptLanguage::_debug_parse_err_file;
|
||||||
|
thread_local String GDScriptLanguage::_debug_error;
|
||||||
|
|
||||||
bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) {
|
bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) {
|
||||||
// break because of parse error
|
// break because of parse error
|
||||||
|
|
||||||
@ -241,6 +245,9 @@ bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const
|
|||||||
_debug_parse_err_file = p_file;
|
_debug_parse_err_file = p_file;
|
||||||
_debug_error = p_error;
|
_debug_error = p_error;
|
||||||
EngineDebugger::get_script_debugger()->debug(this, false, true);
|
EngineDebugger::get_script_debugger()->debug(this, false, true);
|
||||||
|
// Because this is thread local, clear the memory afterwards.
|
||||||
|
_debug_parse_err_file = String();
|
||||||
|
_debug_error = String();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
@ -248,12 +255,15 @@ 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) {
|
bool GDScriptLanguage::debug_break(const String &p_error, bool p_allow_continue) {
|
||||||
if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) {
|
if (EngineDebugger::is_active()) {
|
||||||
_debug_parse_err_line = -1;
|
_debug_parse_err_line = -1;
|
||||||
_debug_parse_err_file = "";
|
_debug_parse_err_file = "";
|
||||||
_debug_error = p_error;
|
_debug_error = p_error;
|
||||||
bool is_error_breakpoint = p_error != "Breakpoint";
|
bool is_error_breakpoint = p_error != "Breakpoint";
|
||||||
EngineDebugger::get_script_debugger()->debug(this, p_allow_continue, is_error_breakpoint);
|
EngineDebugger::get_script_debugger()->debug(this, p_allow_continue, is_error_breakpoint);
|
||||||
|
// Because this is thread local, clear the memory afterwards.
|
||||||
|
_debug_parse_err_file = String();
|
||||||
|
_debug_error = String();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
@ -269,7 +279,7 @@ int GDScriptLanguage::debug_get_stack_level_count() const {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _debug_call_stack_pos;
|
return _call_stack.stack_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GDScriptLanguage::debug_get_stack_level_line(int p_level) const {
|
int GDScriptLanguage::debug_get_stack_level_line(int p_level) const {
|
||||||
@ -277,11 +287,11 @@ int GDScriptLanguage::debug_get_stack_level_line(int p_level) const {
|
|||||||
return _debug_parse_err_line;
|
return _debug_parse_err_line;
|
||||||
}
|
}
|
||||||
|
|
||||||
ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, -1);
|
ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, -1);
|
||||||
|
|
||||||
int l = _debug_call_stack_pos - p_level - 1;
|
int l = _call_stack.stack_pos - p_level - 1;
|
||||||
|
|
||||||
return *(_call_stack[l].line);
|
return *(_call_stack.levels[l].line);
|
||||||
}
|
}
|
||||||
|
|
||||||
String GDScriptLanguage::debug_get_stack_level_function(int p_level) const {
|
String GDScriptLanguage::debug_get_stack_level_function(int p_level) const {
|
||||||
@ -289,9 +299,9 @@ String GDScriptLanguage::debug_get_stack_level_function(int p_level) const {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, "");
|
ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, "");
|
||||||
int l = _debug_call_stack_pos - p_level - 1;
|
int l = _call_stack.stack_pos - p_level - 1;
|
||||||
return _call_stack[l].function->get_name();
|
return _call_stack.levels[l].function->get_name();
|
||||||
}
|
}
|
||||||
|
|
||||||
String GDScriptLanguage::debug_get_stack_level_source(int p_level) const {
|
String GDScriptLanguage::debug_get_stack_level_source(int p_level) const {
|
||||||
@ -299,9 +309,9 @@ String GDScriptLanguage::debug_get_stack_level_source(int p_level) const {
|
|||||||
return _debug_parse_err_file;
|
return _debug_parse_err_file;
|
||||||
}
|
}
|
||||||
|
|
||||||
ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, "");
|
ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, "");
|
||||||
int l = _debug_call_stack_pos - p_level - 1;
|
int l = _call_stack.stack_pos - p_level - 1;
|
||||||
return _call_stack[l].function->get_source();
|
return _call_stack.levels[l].function->get_source();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GDScriptLanguage::debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {
|
void GDScriptLanguage::debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {
|
||||||
@ -309,17 +319,17 @@ void GDScriptLanguage::debug_get_stack_level_locals(int p_level, List<String> *p
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ERR_FAIL_INDEX(p_level, _debug_call_stack_pos);
|
ERR_FAIL_INDEX(p_level, _call_stack.stack_pos);
|
||||||
int l = _debug_call_stack_pos - p_level - 1;
|
int l = _call_stack.stack_pos - p_level - 1;
|
||||||
|
|
||||||
GDScriptFunction *f = _call_stack[l].function;
|
GDScriptFunction *f = _call_stack.levels[l].function;
|
||||||
|
|
||||||
List<Pair<StringName, int>> locals;
|
List<Pair<StringName, int>> locals;
|
||||||
|
|
||||||
f->debug_get_stack_member_state(*_call_stack[l].line, &locals);
|
f->debug_get_stack_member_state(*_call_stack.levels[l].line, &locals);
|
||||||
for (const Pair<StringName, int> &E : locals) {
|
for (const Pair<StringName, int> &E : locals) {
|
||||||
p_locals->push_back(E.first);
|
p_locals->push_back(E.first);
|
||||||
p_values->push_back(_call_stack[l].stack[E.second]);
|
p_values->push_back(_call_stack.levels[l].stack[E.second]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,10 +338,10 @@ void GDScriptLanguage::debug_get_stack_level_members(int p_level, List<String> *
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ERR_FAIL_INDEX(p_level, _debug_call_stack_pos);
|
ERR_FAIL_INDEX(p_level, _call_stack.stack_pos);
|
||||||
int l = _debug_call_stack_pos - p_level - 1;
|
int l = _call_stack.stack_pos - p_level - 1;
|
||||||
|
|
||||||
GDScriptInstance *instance = _call_stack[l].instance;
|
GDScriptInstance *instance = _call_stack.levels[l].instance;
|
||||||
|
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
return;
|
return;
|
||||||
@ -353,10 +363,10 @@ ScriptInstance *GDScriptLanguage::debug_get_stack_level_instance(int p_level) {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
ERR_FAIL_INDEX_V(p_level, _debug_call_stack_pos, nullptr);
|
ERR_FAIL_INDEX_V(p_level, _call_stack.stack_pos, nullptr);
|
||||||
|
|
||||||
int l = _debug_call_stack_pos - p_level - 1;
|
int l = _call_stack.stack_pos - p_level - 1;
|
||||||
ScriptInstance *instance = _call_stack[l].instance;
|
ScriptInstance *instance = _call_stack.levels[l].instance;
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
@ -539,12 +539,12 @@ private:
|
|||||||
|
|
||||||
struct Profile {
|
struct Profile {
|
||||||
StringName signature;
|
StringName signature;
|
||||||
uint64_t call_count = 0;
|
SafeNumeric<uint64_t> call_count;
|
||||||
uint64_t self_time = 0;
|
SafeNumeric<uint64_t> self_time;
|
||||||
uint64_t total_time = 0;
|
SafeNumeric<uint64_t> total_time;
|
||||||
uint64_t frame_call_count = 0;
|
SafeNumeric<uint64_t> frame_call_count;
|
||||||
uint64_t frame_self_time = 0;
|
SafeNumeric<uint64_t> frame_self_time;
|
||||||
uint64_t frame_total_time = 0;
|
SafeNumeric<uint64_t> frame_total_time;
|
||||||
uint64_t last_frame_call_count = 0;
|
uint64_t last_frame_call_count = 0;
|
||||||
uint64_t last_frame_self_time = 0;
|
uint64_t last_frame_self_time = 0;
|
||||||
uint64_t last_frame_total_time = 0;
|
uint64_t last_frame_total_time = 0;
|
||||||
|
@ -663,8 +663,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
|||||||
if (GDScriptLanguage::get_singleton()->profiling) {
|
if (GDScriptLanguage::get_singleton()->profiling) {
|
||||||
function_start_time = OS::get_singleton()->get_ticks_usec();
|
function_start_time = OS::get_singleton()->get_ticks_usec();
|
||||||
function_call_time = 0;
|
function_call_time = 0;
|
||||||
profile.call_count++;
|
profile.call_count.increment();
|
||||||
profile.frame_call_count++;
|
profile.frame_call_count.increment();
|
||||||
}
|
}
|
||||||
bool exit_ok = false;
|
bool exit_ok = false;
|
||||||
bool awaited = false;
|
bool awaited = false;
|
||||||
@ -3550,7 +3550,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
|||||||
// line
|
// line
|
||||||
bool do_break = false;
|
bool do_break = false;
|
||||||
|
|
||||||
if (EngineDebugger::get_script_debugger()->get_lines_left() > 0) {
|
if (unlikely(EngineDebugger::get_script_debugger()->get_lines_left() > 0)) {
|
||||||
if (EngineDebugger::get_script_debugger()->get_depth() <= 0) {
|
if (EngineDebugger::get_script_debugger()->get_depth() <= 0) {
|
||||||
EngineDebugger::get_script_debugger()->set_lines_left(EngineDebugger::get_script_debugger()->get_lines_left() - 1);
|
EngineDebugger::get_script_debugger()->set_lines_left(EngineDebugger::get_script_debugger()->get_lines_left() - 1);
|
||||||
}
|
}
|
||||||
@ -3563,7 +3563,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
|||||||
do_break = true;
|
do_break = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (do_break) {
|
if (unlikely(do_break)) {
|
||||||
GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true);
|
GDScriptLanguage::get_singleton()->debug_break("Breakpoint", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3630,11 +3630,13 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
|
|||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
if (GDScriptLanguage::get_singleton()->profiling) {
|
if (GDScriptLanguage::get_singleton()->profiling) {
|
||||||
uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - function_start_time;
|
uint64_t time_taken = OS::get_singleton()->get_ticks_usec() - function_start_time;
|
||||||
profile.total_time += time_taken;
|
profile.total_time.add(time_taken);
|
||||||
profile.self_time += time_taken - function_call_time;
|
profile.self_time.add(time_taken - function_call_time);
|
||||||
profile.frame_total_time += time_taken;
|
profile.frame_total_time.add(time_taken);
|
||||||
profile.frame_self_time += time_taken - function_call_time;
|
profile.frame_self_time.add(time_taken - function_call_time);
|
||||||
GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time;
|
if (Thread::get_caller_id() == Thread::get_main_id()) {
|
||||||
|
GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is not the last time it was interrupted by `await` or if it's the first time executing.
|
// Check if this is not the last time it was interrupted by `await` or if it's the first time executing.
|
||||||
|
Loading…
Reference in New Issue
Block a user