From 742b27fdd6fc05642100e8ddbfe5110ccdde220a Mon Sep 17 00:00:00 2001 From: Stuart Carnie Date: Fri, 21 Mar 2025 17:47:33 +1100 Subject: [PATCH] Editor: Simplify `_parse_message`; separate into handler functions Move the individual message handlers to separate functions, to make it easier to follow and add additional message handlers. The function is also more efficient as it only performs a single hash lookup for the handler and then dispatches to the member function. --- editor/debugger/script_editor_debugger.cpp | 1090 +++++++++++--------- editor/debugger/script_editor_debugger.h | 36 + 2 files changed, 629 insertions(+), 497 deletions(-) diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index bda0c6ef50..1db15bcdce 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -350,536 +350,595 @@ void ScriptEditorDebugger::_select_thread(int p_index) { _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); - if (p_msg == "debug_enter") { - ERR_FAIL_COND(p_data.size() != 4); +void ScriptEditorDebugger::_msg_debug_enter(uint64_t p_thread_id, const Array &p_data) { + ERR_FAIL_COND(p_data.size() != 4); - const Thread::ID caller_id = p_data[3]; + const Thread::ID caller_id = p_data[3]; - ThreadDebugged td; - td.name = (caller_id == Thread::get_main_id()) ? TTR("Main Thread") : itos(caller_id); - td.error = p_data[1]; - td.can_debug = p_data[0]; - td.has_stackdump = p_data[2]; - td.thread_id = p_thread_id; - static uint32_t order_inc = 0; - td.debug_order = order_inc++; + ThreadDebugged td; + td.name = (caller_id == Thread::get_main_id()) ? TTR("Main Thread") : itos(caller_id); + td.error = p_data[1]; + td.can_debug = p_data[0]; + td.has_stackdump = p_data[2]; + td.thread_id = p_thread_id; + static uint32_t order_inc = 0; + td.debug_order = order_inc++; - threads_debugged.insert(p_thread_id, td); + 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(); - - } else if (p_msg == "debug_exit") { - threads_debugged.erase(p_thread_id); - if (p_thread_id == debugging_thread_id) { - _clear_execution(); - if (threads_debugged.size() == 0) { - debugging_thread_id = Thread::UNASSIGNED_ID; - } else { - // Find next thread to debug. - uint32_t min_order = 0xFFFFFFFF; - uint64_t next_thread = Thread::UNASSIGNED_ID; - for (KeyValue T : threads_debugged) { - if (T.value.debug_order < min_order) { - min_order = T.value.debug_order; - next_thread = T.key; - } - } - - debugging_thread_id = next_thread; - } - - 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") { - ERR_FAIL_COND(p_data.is_empty()); - remote_pid = p_data[0]; - } else if (p_msg == "scene:click_ctrl") { - ERR_FAIL_COND(p_data.size() < 2); - clicked_ctrl->set_text(p_data[0]); - clicked_ctrl_type->set_text(p_data[1]); - } else if (p_msg == "scene:scene_tree") { - scene_tree->nodes.clear(); - scene_tree->deserialize(p_data); - emit_signal(SNAME("remote_tree_updated")); - _update_buttons_state(); - } else if (p_msg == "scene:inspect_objects") { - ERR_FAIL_COND(p_data.is_empty()); - - TypedArray ids; - for (const Array arr : p_data) { - ERR_FAIL_COND(arr.is_empty()); - ERR_FAIL_COND(arr[0].get_type() != Variant::INT); - ids.append(arr[0]); - } - - if (EditorDebuggerNode::get_singleton()->match_remote_selection(ids)) { - EditorDebuggerRemoteObjects *objs = inspector->set_objects(p_data); - emit_signal(SNAME("remote_objects_updated"), objs); - } - } else if (p_msg == "servers:memory_usage") { - vmem_tree->clear(); - TreeItem *root = vmem_tree->create_item(); - ServersDebugger::ResourceUsage usage; - usage.deserialize(p_data); - - uint64_t total = 0; - - for (const ServersDebugger::ResourceInfo &E : usage.infos) { - TreeItem *it = vmem_tree->create_item(root); - String type = E.type; - int bytes = E.vram; - it->set_text(0, E.path); - it->set_text(1, type); - it->set_text(2, E.format); - it->set_text(3, String::humanize_size(bytes)); - total += bytes; - - if (has_theme_icon(type, EditorStringName(EditorIcons))) { - it->set_icon(0, get_editor_theme_icon(type)); - } - } - - vmem_total->set_tooltip_text(TTR("Bytes:") + " " + itos(total)); - vmem_total->set_text(String::humanize_size(total)); - } else if (p_msg == "servers:drawn") { + 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; - } else if (p_msg == "stack_dump") { - DebuggerMarshalls::ScriptStackDump stack; - stack.deserialize(p_data); - - stack_dump->clear(); - inspector->clear_stack_variables(); - TreeItem *r = stack_dump->create_item(); - - Array stack_dump_info; - - int i = 0; - for (List::Iterator itr = stack.frames.begin(); itr != stack.frames.end(); ++itr, ++i) { - TreeItem *s = stack_dump->create_item(r); - Dictionary d; - d["frame"] = i; - d["file"] = itr->file; - d["function"] = itr->func; - d["line"] = itr->line; - stack_dump_info.push_back(d); - s->set_metadata(0, d); - - String line = itos(i) + " - " + String(d["file"]) + ":" + itos(d["line"]) + " - at function: " + String(d["function"]); - s->set_text(0, line); - - if (i == 0) { - s->select(0); - } + if (is_move_to_foreground()) { + DisplayServer::get_singleton()->window_move_to_foreground(); } - emit_signal(SNAME("stack_dump"), stack_dump_info); - } else if (p_msg == "stack_frame_vars") { - inspector->clear_stack_variables(); - ERR_FAIL_COND(p_data.size() != 1); - emit_signal(SNAME("stack_frame_vars"), p_data[0]); - } else if (p_msg == "stack_frame_var") { - inspector->add_stack_variable(p_data); - emit_signal(SNAME("stack_frame_var"), p_data); - } else if (p_msg == "output") { - ERR_FAIL_COND(p_data.size() != 2); + profiler->set_enabled(false, false); + visual_profiler->set_enabled(false); + } + _update_buttons_state(); +} - ERR_FAIL_COND(p_data[0].get_type() != Variant::PACKED_STRING_ARRAY); - Vector output_strings = p_data[0]; - - ERR_FAIL_COND(p_data[1].get_type() != Variant::PACKED_INT32_ARRAY); - Vector output_types = p_data[1]; - - ERR_FAIL_COND(output_strings.size() != output_types.size()); - - for (int i = 0; i < output_strings.size(); i++) { - RemoteDebugger::MessageType type = (RemoteDebugger::MessageType)(int)(output_types[i]); - EditorLog::MessageType msg_type; - switch (type) { - case RemoteDebugger::MESSAGE_TYPE_LOG: { - msg_type = EditorLog::MSG_TYPE_STD; - } break; - case RemoteDebugger::MESSAGE_TYPE_LOG_RICH: { - msg_type = EditorLog::MSG_TYPE_STD_RICH; - } break; - case RemoteDebugger::MESSAGE_TYPE_ERROR: { - msg_type = EditorLog::MSG_TYPE_ERROR; - } break; - default: { - WARN_PRINT("Unhandled script debugger message type: " + itos(type)); - msg_type = EditorLog::MSG_TYPE_STD; - } break; - } - EditorNode::get_log()->add_message(output_strings[i], msg_type); - emit_signal(SceneStringName(output), output_strings[i], msg_type); - } - } else if (p_msg == "performance:profile_frame") { - Vector frame_data; - frame_data.resize(p_data.size()); - for (int i = 0; i < p_data.size(); i++) { - frame_data.write[i] = p_data[i]; - } - performance_profiler->add_profile_frame(frame_data); - } else if (p_msg == "visual:hardware_info") { - const String cpu_name = p_data[0]; - const String gpu_name = p_data[1]; - visual_profiler->set_hardware_info(cpu_name, gpu_name); - } else if (p_msg == "visual:profile_frame") { - ServersDebugger::VisualProfilerFrame frame; - frame.deserialize(p_data); - - EditorVisualProfiler::Metric metric; - metric.areas.resize(frame.areas.size()); - metric.frame_number = frame.frame_number; - metric.valid = true; - - { - EditorVisualProfiler::Metric::Area *areas_ptr = metric.areas.ptrw(); - for (int i = 0; i < frame.areas.size(); i++) { - areas_ptr[i].name = frame.areas[i].name; - areas_ptr[i].cpu_time = frame.areas[i].cpu_msec; - areas_ptr[i].gpu_time = frame.areas[i].gpu_msec; - } - } - visual_profiler->add_frame_metric(metric); - } else if (p_msg == "error") { - DebuggerMarshalls::OutputError oe; - ERR_FAIL_COND_MSG(oe.deserialize(p_data) == false, "Failed to deserialize error message"); - - // Format time. - Array time_vals; - time_vals.push_back(oe.hr); - time_vals.push_back(oe.min); - time_vals.push_back(oe.sec); - time_vals.push_back(oe.msec); - bool e; - String time = String("%d:%02d:%02d:%03d").sprintf(time_vals, &e); - - // Rest of the error data. - bool source_is_project_file = oe.source_file.begins_with("res://"); - - // Metadata to highlight error line in scripts. - Array source_meta; - source_meta.push_back(oe.source_file); - source_meta.push_back(oe.source_line); - - // Create error tree to display above error or warning details. - TreeItem *r = error_tree->get_root(); - if (!r) { - r = error_tree->create_item(); - } - - // Also provide the relevant details as tooltip to quickly check without - // uncollapsing the tree. - String tooltip = oe.warning ? TTR("Warning:") : TTR("Error:"); - - TreeItem *error = error_tree->create_item(r); - if (oe.warning) { - error->set_meta("_is_warning", true); +void ScriptEditorDebugger::_msg_debug_exit(uint64_t p_thread_id, const Array &p_data) { + threads_debugged.erase(p_thread_id); + if (p_thread_id == debugging_thread_id) { + _clear_execution(); + if (threads_debugged.size() == 0) { + debugging_thread_id = Thread::UNASSIGNED_ID; } else { - error->set_meta("_is_error", true); - } - error->set_collapsed(true); - - error->set_icon(0, get_editor_theme_icon(oe.warning ? SNAME("Warning") : SNAME("Error"))); - error->set_text(0, time); - error->set_text_alignment(0, HORIZONTAL_ALIGNMENT_LEFT); - - const Color color = get_theme_color(oe.warning ? SNAME("warning_color") : SNAME("error_color"), EditorStringName(Editor)); - error->set_custom_color(0, color); - error->set_custom_color(1, color); - - String error_title; - if (!oe.source_func.is_empty() && source_is_project_file) { - // If source function is inside the project file. - error_title += oe.source_func + ": "; - } else if (oe.callstack.size() > 0) { - // Otherwise, if available, use the script's stack in the error title. - error_title = _format_frame_text(&oe.callstack[0]) + ": "; - } else if (!oe.source_func.is_empty()) { - // Otherwise try to use the C++ source function. - error_title += oe.source_func + ": "; - } - // If we have a (custom) error message, use it as title, and add a C++ Error - // item with the original error condition. - error_title += oe.error_descr.is_empty() ? oe.error : oe.error_descr; - error->set_text(1, error_title); - tooltip += " " + error_title + "\n"; - - // Find the language of the error's source file. - String source_language_name = "C++"; // Default value is the old hard-coded one. - const String source_file_extension = oe.source_file.get_extension(); - for (int i = 0; i < ScriptServer::get_language_count(); ++i) { - ScriptLanguage *script_language = ScriptServer::get_language(i); - if (source_file_extension == script_language->get_extension()) { - source_language_name = script_language->get_name(); - break; - } - } - - if (!oe.error_descr.is_empty()) { - // Add item for C++ error condition. - TreeItem *cpp_cond = error_tree->create_item(error); - // TRANSLATORS: %s is the name of a language, e.g. C++. - cpp_cond->set_text(0, "<" + vformat(TTR("%s Error"), source_language_name) + ">"); - cpp_cond->set_text(1, oe.error); - cpp_cond->set_text_alignment(0, HORIZONTAL_ALIGNMENT_LEFT); - tooltip += vformat(TTR("%s Error:"), source_language_name) + " " + oe.error + "\n"; - if (source_is_project_file) { - cpp_cond->set_metadata(0, source_meta); - } - } - Vector v; - v.resize(100); - - // Source of the error. - String source_txt = (source_is_project_file ? oe.source_file.get_file() : oe.source_file) + ":" + itos(oe.source_line); - if (!oe.source_func.is_empty()) { - source_txt += " @ " + oe.source_func; - if (!oe.source_func.ends_with(")")) { - source_txt += "()"; - } - } - - TreeItem *cpp_source = error_tree->create_item(error); - // TRANSLATORS: %s is the name of a language, e.g. C++. - cpp_source->set_text(0, "<" + vformat(TTR("%s Source"), source_language_name) + ">"); - cpp_source->set_text(1, source_txt); - cpp_source->set_text_alignment(0, HORIZONTAL_ALIGNMENT_LEFT); - tooltip += vformat(TTR("%s Source:"), source_language_name) + " " + source_txt + "\n"; - - // Set metadata to highlight error line in scripts. - if (source_is_project_file) { - error->set_metadata(0, source_meta); - cpp_source->set_metadata(0, source_meta); - } - - // Format stack trace. - // stack_items_count is the number of elements to parse, with 3 items per frame - // of the stack trace (script, method, line). - const ScriptLanguage::StackInfo *infos = oe.callstack.ptr(); - for (unsigned int i = 0; i < (unsigned int)oe.callstack.size(); i++) { - TreeItem *stack_trace = error_tree->create_item(error); - - Array meta; - meta.push_back(infos[i].file); - meta.push_back(infos[i].line); - stack_trace->set_metadata(0, meta); - - if (i == 0) { - stack_trace->set_text(0, "<" + TTR("Stack Trace") + ">"); - stack_trace->set_text_alignment(0, HORIZONTAL_ALIGNMENT_LEFT); - if (!source_is_project_file) { - // Only override metadata if the source is not inside the project. - error->set_metadata(0, meta); + // Find next thread to debug. + uint32_t min_order = 0xFFFFFFFF; + uint64_t next_thread = Thread::UNASSIGNED_ID; + for (KeyValue T : threads_debugged) { + if (T.value.debug_order < min_order) { + min_order = T.value.debug_order; + next_thread = T.key; } - tooltip += TTR("Stack Trace:") + "\n"; } - String frame_txt = _format_frame_text(&infos[i]); - tooltip += frame_txt + "\n"; - stack_trace->set_text(1, frame_txt); + debugging_thread_id = next_thread; } - error->set_tooltip_text(0, tooltip); - error->set_tooltip_text(1, tooltip); + if (debugging_thread_id == Thread::UNASSIGNED_ID) { + // Nothing else to debug. + profiler->set_enabled(true, false); + profiler->disable_seeking(); - if (warning_count == 0 && error_count == 0) { - expand_all_button->set_disabled(false); - collapse_all_button->set_disabled(false); - clear_button->set_disabled(false); - } + visual_profiler->set_enabled(true); - if (oe.warning) { - warning_count++; + _set_reason_text(TTR("Execution resumed."), MESSAGE_SUCCESS); + emit_signal(SNAME("breaked"), false, false, "", false); + + _update_buttons_state(); } else { - error_count++; + _thread_debug_enter(debugging_thread_id); } - } else if (p_msg == "servers:function_signature") { - // Cache a profiler signature. - ServersDebugger::ScriptFunctionSignature sig; - sig.deserialize(p_data); - profiler_signature[sig.id] = sig.name; - } else if (p_msg == "servers:profile_frame" || p_msg == "servers:profile_total") { - EditorProfiler::Metric metric; - ServersDebugger::ServersProfilerFrame frame; - frame.deserialize(p_data); - metric.valid = true; - metric.frame_number = frame.frame_number; - metric.frame_time = frame.frame_time; - metric.process_time = frame.process_time; - metric.physics_time = frame.physics_time; - metric.physics_frame_time = frame.physics_frame_time; + } else { + _update_buttons_state(); + } +} - if (frame.servers.size()) { - EditorProfiler::Metric::Category frame_time; - frame_time.signature = "category_frame_time"; - frame_time.name = "Frame Time"; - frame_time.total_time = metric.frame_time; +void ScriptEditorDebugger::_msg_set_pid(uint64_t p_thread_id, const Array &p_data) { + ERR_FAIL_COND(p_data.is_empty()); + remote_pid = p_data[0]; +} +void ScriptEditorDebugger::_msg_scene_click_ctrl(uint64_t p_thread_id, const Array &p_data) { + ERR_FAIL_COND(p_data.size() < 2); + clicked_ctrl->set_text(p_data[0]); + clicked_ctrl_type->set_text(p_data[1]); +} + +void ScriptEditorDebugger::_msg_scene_scene_tree(uint64_t p_thread_id, const Array &p_data) { + scene_tree->nodes.clear(); + scene_tree->deserialize(p_data); + emit_signal(SNAME("remote_tree_updated")); + _update_buttons_state(); +} + +void ScriptEditorDebugger::_msg_scene_inspect_objects(uint64_t p_thread_id, const Array &p_data) { + ERR_FAIL_COND(p_data.is_empty()); + + TypedArray ids; + for (const Array arr : p_data) { + ERR_FAIL_COND(arr.is_empty()); + ERR_FAIL_COND(arr[0].get_type() != Variant::INT); + ids.append(arr[0]); + } + + if (EditorDebuggerNode::get_singleton()->match_remote_selection(ids)) { + EditorDebuggerRemoteObjects *objs = inspector->set_objects(p_data); + emit_signal(SNAME("remote_objects_updated"), objs); + } +} + +void ScriptEditorDebugger::_msg_servers_memory_usage(uint64_t p_thread_id, const Array &p_data) { + vmem_tree->clear(); + TreeItem *root = vmem_tree->create_item(); + ServersDebugger::ResourceUsage usage; + usage.deserialize(p_data); + + uint64_t total = 0; + + for (const ServersDebugger::ResourceInfo &E : usage.infos) { + TreeItem *it = vmem_tree->create_item(root); + String type = E.type; + int bytes = E.vram; + it->set_text(0, E.path); + it->set_text(1, type); + it->set_text(2, E.format); + it->set_text(3, String::humanize_size(bytes)); + total += bytes; + + if (has_theme_icon(type, EditorStringName(EditorIcons))) { + it->set_icon(0, get_editor_theme_icon(type)); + } + } + + vmem_total->set_tooltip_text(TTR("Bytes:") + " " + itos(total)); + vmem_total->set_text(String::humanize_size(total)); +} + +void ScriptEditorDebugger::_msg_servers_drawn(uint64_t p_thread_id, const Array &p_data) { + can_request_idle_draw = true; +} + +void ScriptEditorDebugger::_msg_stack_dump(uint64_t p_thread_id, const Array &p_data) { + DebuggerMarshalls::ScriptStackDump stack; + stack.deserialize(p_data); + + stack_dump->clear(); + inspector->clear_stack_variables(); + TreeItem *r = stack_dump->create_item(); + + Array stack_dump_info; + + int i = 0; + for (List::Iterator itr = stack.frames.begin(); itr != stack.frames.end(); ++itr, ++i) { + TreeItem *s = stack_dump->create_item(r); + Dictionary d; + d["frame"] = i; + d["file"] = itr->file; + d["function"] = itr->func; + d["line"] = itr->line; + stack_dump_info.push_back(d); + s->set_metadata(0, d); + + String line = itos(i) + " - " + String(d["file"]) + ":" + itos(d["line"]) + " - at function: " + String(d["function"]); + s->set_text(0, line); + + if (i == 0) { + s->select(0); + } + } + emit_signal(SNAME("stack_dump"), stack_dump_info); +} + +void ScriptEditorDebugger::_msg_stack_frame_vars(uint64_t p_thread_id, const Array &p_data) { + inspector->clear_stack_variables(); + ERR_FAIL_COND(p_data.size() != 1); + emit_signal(SNAME("stack_frame_vars"), p_data[0]); +} + +void ScriptEditorDebugger::_msg_stack_frame_var(uint64_t p_thread_id, const Array &p_data) { + inspector->add_stack_variable(p_data); + emit_signal(SNAME("stack_frame_var"), p_data); +} + +void ScriptEditorDebugger::_msg_output(uint64_t p_thread_id, const Array &p_data) { + ERR_FAIL_COND(p_data.size() != 2); + + ERR_FAIL_COND(p_data[0].get_type() != Variant::PACKED_STRING_ARRAY); + Vector output_strings = p_data[0]; + + ERR_FAIL_COND(p_data[1].get_type() != Variant::PACKED_INT32_ARRAY); + Vector output_types = p_data[1]; + + ERR_FAIL_COND(output_strings.size() != output_types.size()); + + for (int i = 0; i < output_strings.size(); i++) { + RemoteDebugger::MessageType type = (RemoteDebugger::MessageType)(int)(output_types[i]); + EditorLog::MessageType msg_type; + switch (type) { + case RemoteDebugger::MESSAGE_TYPE_LOG: { + msg_type = EditorLog::MSG_TYPE_STD; + } break; + case RemoteDebugger::MESSAGE_TYPE_LOG_RICH: { + msg_type = EditorLog::MSG_TYPE_STD_RICH; + } break; + case RemoteDebugger::MESSAGE_TYPE_ERROR: { + msg_type = EditorLog::MSG_TYPE_ERROR; + } break; + default: { + WARN_PRINT("Unhandled script debugger message type: " + itos(type)); + msg_type = EditorLog::MSG_TYPE_STD; + } break; + } + EditorNode::get_log()->add_message(output_strings[i], msg_type); + emit_signal(SceneStringName(output), output_strings[i], msg_type); + } +} + +void ScriptEditorDebugger::_msg_performance_profile_frame(uint64_t p_thread_id, const Array &p_data) { + Vector frame_data; + frame_data.resize(p_data.size()); + for (int i = 0; i < p_data.size(); i++) { + frame_data.write[i] = p_data[i]; + } + performance_profiler->add_profile_frame(frame_data); +} + +void ScriptEditorDebugger::_msg_visual_hardware_info(uint64_t p_thread_id, const Array &p_data) { + const String cpu_name = p_data[0]; + const String gpu_name = p_data[1]; + visual_profiler->set_hardware_info(cpu_name, gpu_name); +} + +void ScriptEditorDebugger::_msg_visual_profile_frame(uint64_t p_thread_id, const Array &p_data) { + ServersDebugger::VisualProfilerFrame frame; + frame.deserialize(p_data); + + EditorVisualProfiler::Metric metric; + metric.areas.resize(frame.areas.size()); + metric.frame_number = frame.frame_number; + metric.valid = true; + + { + EditorVisualProfiler::Metric::Area *areas_ptr = metric.areas.ptrw(); + for (int i = 0; i < frame.areas.size(); i++) { + areas_ptr[i].name = frame.areas[i].name; + areas_ptr[i].cpu_time = frame.areas[i].cpu_msec; + areas_ptr[i].gpu_time = frame.areas[i].gpu_msec; + } + } + visual_profiler->add_frame_metric(metric); +} + +void ScriptEditorDebugger::_msg_error(uint64_t p_thread_id, const Array &p_data) { + DebuggerMarshalls::OutputError oe; + ERR_FAIL_COND_MSG(oe.deserialize(p_data) == false, "Failed to deserialize error message"); + + // Format time. + Array time_vals; + time_vals.push_back(oe.hr); + time_vals.push_back(oe.min); + time_vals.push_back(oe.sec); + time_vals.push_back(oe.msec); + bool e; + String time = String("%d:%02d:%02d:%03d").sprintf(time_vals, &e); + + // Rest of the error data. + bool source_is_project_file = oe.source_file.begins_with("res://"); + + // Metadata to highlight error line in scripts. + Array source_meta; + source_meta.push_back(oe.source_file); + source_meta.push_back(oe.source_line); + + // Create error tree to display above error or warning details. + TreeItem *r = error_tree->get_root(); + if (!r) { + r = error_tree->create_item(); + } + + // Also provide the relevant details as tooltip to quickly check without + // uncollapsing the tree. + String tooltip = oe.warning ? TTR("Warning:") : TTR("Error:"); + + TreeItem *error = error_tree->create_item(r); + if (oe.warning) { + error->set_meta("_is_warning", true); + } else { + error->set_meta("_is_error", true); + } + error->set_collapsed(true); + + error->set_icon(0, get_editor_theme_icon(oe.warning ? SNAME("Warning") : SNAME("Error"))); + error->set_text(0, time); + error->set_text_alignment(0, HORIZONTAL_ALIGNMENT_LEFT); + + const Color color = get_theme_color(oe.warning ? SNAME("warning_color") : SNAME("error_color"), EditorStringName(Editor)); + error->set_custom_color(0, color); + error->set_custom_color(1, color); + + String error_title; + if (!oe.source_func.is_empty() && source_is_project_file) { + // If source function is inside the project file. + error_title += oe.source_func + ": "; + } else if (oe.callstack.size() > 0) { + // Otherwise, if available, use the script's stack in the error title. + error_title = _format_frame_text(&oe.callstack[0]) + ": "; + } else if (!oe.source_func.is_empty()) { + // Otherwise try to use the C++ source function. + error_title += oe.source_func + ": "; + } + // If we have a (custom) error message, use it as title, and add a C++ Error + // item with the original error condition. + error_title += oe.error_descr.is_empty() ? oe.error : oe.error_descr; + error->set_text(1, error_title); + tooltip += " " + error_title + "\n"; + + // Find the language of the error's source file. + String source_language_name = "C++"; // Default value is the old hard-coded one. + const String source_file_extension = oe.source_file.get_extension(); + for (int i = 0; i < ScriptServer::get_language_count(); ++i) { + ScriptLanguage *script_language = ScriptServer::get_language(i); + if (source_file_extension == script_language->get_extension()) { + source_language_name = script_language->get_name(); + break; + } + } + + if (!oe.error_descr.is_empty()) { + // Add item for C++ error condition. + TreeItem *cpp_cond = error_tree->create_item(error); + // TRANSLATORS: %s is the name of a language, e.g. C++. + cpp_cond->set_text(0, "<" + vformat(TTR("%s Error"), source_language_name) + ">"); + cpp_cond->set_text(1, oe.error); + cpp_cond->set_text_alignment(0, HORIZONTAL_ALIGNMENT_LEFT); + tooltip += vformat(TTR("%s Error:"), source_language_name) + " " + oe.error + "\n"; + if (source_is_project_file) { + cpp_cond->set_metadata(0, source_meta); + } + } + Vector v; + v.resize(100); + + // Source of the error. + String source_txt = (source_is_project_file ? oe.source_file.get_file() : oe.source_file) + ":" + itos(oe.source_line); + if (!oe.source_func.is_empty()) { + source_txt += " @ " + oe.source_func; + if (!oe.source_func.ends_with(")")) { + source_txt += "()"; + } + } + + TreeItem *cpp_source = error_tree->create_item(error); + // TRANSLATORS: %s is the name of a language, e.g. C++. + cpp_source->set_text(0, "<" + vformat(TTR("%s Source"), source_language_name) + ">"); + cpp_source->set_text(1, source_txt); + cpp_source->set_text_alignment(0, HORIZONTAL_ALIGNMENT_LEFT); + tooltip += vformat(TTR("%s Source:"), source_language_name) + " " + source_txt + "\n"; + + // Set metadata to highlight error line in scripts. + if (source_is_project_file) { + error->set_metadata(0, source_meta); + cpp_source->set_metadata(0, source_meta); + } + + // Format stack trace. + // stack_items_count is the number of elements to parse, with 3 items per frame + // of the stack trace (script, method, line). + const ScriptLanguage::StackInfo *infos = oe.callstack.ptr(); + for (unsigned int i = 0; i < (unsigned int)oe.callstack.size(); i++) { + TreeItem *stack_trace = error_tree->create_item(error); + + Array meta; + meta.push_back(infos[i].file); + meta.push_back(infos[i].line); + stack_trace->set_metadata(0, meta); + + if (i == 0) { + stack_trace->set_text(0, "<" + TTR("Stack Trace") + ">"); + stack_trace->set_text_alignment(0, HORIZONTAL_ALIGNMENT_LEFT); + if (!source_is_project_file) { + // Only override metadata if the source is not inside the project. + error->set_metadata(0, meta); + } + tooltip += TTR("Stack Trace:") + "\n"; + } + + String frame_txt = _format_frame_text(&infos[i]); + tooltip += frame_txt + "\n"; + stack_trace->set_text(1, frame_txt); + } + + error->set_tooltip_text(0, tooltip); + error->set_tooltip_text(1, tooltip); + + if (warning_count == 0 && error_count == 0) { + expand_all_button->set_disabled(false); + collapse_all_button->set_disabled(false); + clear_button->set_disabled(false); + } + + if (oe.warning) { + warning_count++; + } else { + error_count++; + } +} + +void ScriptEditorDebugger::_msg_servers_function_signature(uint64_t p_thread_id, const Array &p_data) { + // Cache a profiler signature. + ServersDebugger::ScriptFunctionSignature sig; + sig.deserialize(p_data); + profiler_signature[sig.id] = sig.name; +} + +void ScriptEditorDebugger::_msg_servers_profile_common(const Array &p_data, const bool p_final) { + EditorProfiler::Metric metric; + ServersDebugger::ServersProfilerFrame frame; + frame.deserialize(p_data); + metric.valid = true; + metric.frame_number = frame.frame_number; + metric.frame_time = frame.frame_time; + metric.process_time = frame.process_time; + metric.physics_time = frame.physics_time; + metric.physics_frame_time = frame.physics_frame_time; + + if (frame.servers.size()) { + EditorProfiler::Metric::Category frame_time; + frame_time.signature = "category_frame_time"; + frame_time.name = "Frame Time"; + frame_time.total_time = metric.frame_time; + + EditorProfiler::Metric::Category::Item item; + item.calls = 1; + item.line = 0; + + item.name = "Physics Time"; + item.total = metric.physics_time; + item.self = item.total; + item.signature = "physics_time"; + + frame_time.items.push_back(item); + + item.name = "Process Time"; + item.total = metric.process_time; + item.self = item.total; + item.signature = "process_time"; + + frame_time.items.push_back(item); + + item.name = "Physics Frame Time"; + item.total = metric.physics_frame_time; + item.self = item.total; + item.signature = "physics_frame_time"; + + frame_time.items.push_back(item); + + metric.categories.push_back(frame_time); + } + + for (const ServersDebugger::ServerInfo &srv : frame.servers) { + EditorProfiler::Metric::Category c; + const String name = srv.name; + c.name = EditorPropertyNameProcessor::get_singleton()->process_name(name, EditorPropertyNameProcessor::STYLE_CAPITALIZED); + c.items.resize(srv.functions.size()); + c.total_time = 0; + c.signature = "categ::" + name; + int j = 0; + for (List::ConstIterator itr = srv.functions.begin(); itr != srv.functions.end(); ++itr, ++j) { EditorProfiler::Metric::Category::Item item; item.calls = 1; item.line = 0; - - item.name = "Physics Time"; - item.total = metric.physics_time; - item.self = item.total; - item.signature = "physics_time"; - - frame_time.items.push_back(item); - - item.name = "Process Time"; - item.total = metric.process_time; - item.self = item.total; - item.signature = "process_time"; - - frame_time.items.push_back(item); - - item.name = "Physics Frame Time"; - item.total = metric.physics_frame_time; - item.self = item.total; - item.signature = "physics_frame_time"; - - frame_time.items.push_back(item); - - metric.categories.push_back(frame_time); + item.name = itr->name; + item.self = itr->time; + item.total = item.self; + item.signature = "categ::" + name + "::" + item.name; + item.name = EditorPropertyNameProcessor::get_singleton()->process_name(item.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED); + c.total_time += item.total; + c.items.write[j] = item; } + metric.categories.push_back(c); + } - for (const ServersDebugger::ServerInfo &srv : frame.servers) { - EditorProfiler::Metric::Category c; - const String name = srv.name; - c.name = EditorPropertyNameProcessor::get_singleton()->process_name(name, EditorPropertyNameProcessor::STYLE_CAPITALIZED); - c.items.resize(srv.functions.size()); - c.total_time = 0; - c.signature = "categ::" + name; - int j = 0; - for (List::ConstIterator itr = srv.functions.begin(); itr != srv.functions.end(); ++itr, ++j) { - EditorProfiler::Metric::Category::Item item; - item.calls = 1; - item.line = 0; - item.name = itr->name; - item.self = itr->time; - item.total = item.self; - item.signature = "categ::" + name + "::" + item.name; - item.name = EditorPropertyNameProcessor::get_singleton()->process_name(item.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED); - c.total_time += item.total; - c.items.write[j] = item; - } - metric.categories.push_back(c); - } + EditorProfiler::Metric::Category funcs; + funcs.total_time = frame.script_time; + funcs.items.resize(frame.script_functions.size()); + funcs.name = "Script Functions"; + funcs.signature = "script_functions"; + for (int i = 0; i < frame.script_functions.size(); i++) { + int signature = frame.script_functions[i].sig_id; + int calls = frame.script_functions[i].call_count; + float total = frame.script_functions[i].total_time; + float self = frame.script_functions[i].self_time; + float internal = frame.script_functions[i].internal_time; - EditorProfiler::Metric::Category funcs; - funcs.total_time = frame.script_time; - funcs.items.resize(frame.script_functions.size()); - funcs.name = "Script Functions"; - funcs.signature = "script_functions"; - for (int i = 0; i < frame.script_functions.size(); i++) { - int signature = frame.script_functions[i].sig_id; - int calls = frame.script_functions[i].call_count; - float total = frame.script_functions[i].total_time; - float self = frame.script_functions[i].self_time; - float internal = frame.script_functions[i].internal_time; + EditorProfiler::Metric::Category::Item item; + if (profiler_signature.has(signature)) { + item.signature = profiler_signature[signature]; - EditorProfiler::Metric::Category::Item item; - if (profiler_signature.has(signature)) { - item.signature = profiler_signature[signature]; - - String name = profiler_signature[signature]; - Vector strings = name.split("::"); - if (strings.size() == 3) { - item.name = strings[2]; - item.script = strings[0]; - item.line = strings[1].to_int(); - } else if (strings.size() == 4) { //Built-in scripts have an :: in their name - item.name = strings[3]; - item.script = strings[0] + "::" + strings[1]; - item.line = strings[2].to_int(); - } - - } else { - item.name = "SigErr " + itos(signature); + String name = profiler_signature[signature]; + Vector strings = name.split("::"); + if (strings.size() == 3) { + item.name = strings[2]; + item.script = strings[0]; + item.line = strings[1].to_int(); + } else if (strings.size() == 4) { //Built-in scripts have an :: in their name + item.name = strings[3]; + item.script = strings[0] + "::" + strings[1]; + item.line = strings[2].to_int(); } - item.calls = calls; - item.self = self; - item.total = total; - item.internal = internal; - funcs.items.write[i] = item; - } - - metric.categories.push_back(funcs); - - if (p_msg == "servers:profile_frame") { - profiler->add_frame_metric(metric, false); } else { - profiler->add_frame_metric(metric, true); + item.name = "SigErr " + itos(signature); } - } else if (p_msg == "request_quit") { - emit_signal(SNAME("stop_requested")); - _stop_and_notify(); - } else if (p_msg == "remote_nodes_clicked") { - ERR_FAIL_COND(p_data.is_empty()); - EditorDebuggerRemoteObjects *objs = inspector->set_objects(p_data); - if (objs) { - EditorDebuggerNode::get_singleton()->stop_waiting_inspection(); - emit_signal(SNAME("remote_objects_updated"), objs); - emit_signal(SNAME("remote_tree_select_requested"), objs->remote_object_ids.duplicate()); - } - } else if (p_msg == "remote_nothing_clicked") { + item.calls = calls; + item.self = self; + item.total = total; + item.internal = internal; + funcs.items.write[i] = item; + } + + metric.categories.push_back(funcs); + + profiler->add_frame_metric(metric, p_final); +} + +void ScriptEditorDebugger::_msg_servers_profile_frame(uint64_t p_thread_id, const Array &p_data) { + _msg_servers_profile_common(p_data, false); +} +void ScriptEditorDebugger::_msg_servers_profile_total(uint64_t p_thread_id, const Array &p_data) { + _msg_servers_profile_common(p_data, true); +} + +void ScriptEditorDebugger::_msg_request_quit(uint64_t p_thread_id, const Array &p_data) { + emit_signal(SNAME("stop_requested")); + _stop_and_notify(); +} + +void ScriptEditorDebugger::_msg_remote_nodes_clicked(uint64_t p_thread_id, const Array &p_data) { + ERR_FAIL_COND(p_data.is_empty()); + EditorDebuggerRemoteObjects *objs = inspector->set_objects(p_data); + if (objs) { EditorDebuggerNode::get_singleton()->stop_waiting_inspection(); - emit_signal(SNAME("remote_tree_clear_selection_requested")); - } else if (p_msg == "remote_selection_invalidated") { - ERR_FAIL_COND(p_data.is_empty()); - inspector->invalidate_selection_from_cache(p_data[0]); - } else if (p_msg == "show_selection_limit_warning") { - EditorToaster::get_singleton()->popup_str(vformat(TTR("Some remote nodes were not selected, as the configured maximum selection is %d. This can be changed at \"debugger/max_node_selection\" in the Editor Settings."), EDITOR_GET("debugger/max_node_selection")), EditorToaster::SEVERITY_WARNING); - } else if (p_msg == "performance:profile_names") { - Vector monitors; - monitors.resize(p_data.size()); - for (int i = 0; i < p_data.size(); i++) { - ERR_FAIL_COND(p_data[i].get_type() != Variant::STRING_NAME); - monitors.set(i, p_data[i]); - } - performance_profiler->update_monitors(monitors); - } else if (p_msg == "filesystem:update_file") { - ERR_FAIL_COND(p_data.is_empty()); - if (EditorFileSystem::get_singleton()) { - EditorFileSystem::get_singleton()->update_file(p_data[0]); - } - } else if (p_msg == "evaluation_return") { - expression_evaluator->add_value(p_data); - } else if (p_msg == "window:title") { - ERR_FAIL_COND(p_data.size() != 1); - emit_signal(SNAME("remote_window_title_changed"), p_data[0]); + emit_signal(SNAME("remote_objects_updated"), objs); + emit_signal(SNAME("remote_tree_select_requested"), objs->remote_object_ids.duplicate()); + } +} + +void ScriptEditorDebugger::_msg_remote_nothing_clicked(uint64_t p_thread_id, const Array &p_data) { + EditorDebuggerNode::get_singleton()->stop_waiting_inspection(); + + emit_signal(SNAME("remote_tree_clear_selection_requested")); +} + +void ScriptEditorDebugger::_msg_remote_selection_invalidated(uint64_t p_thread_id, const Array &p_data) { + ERR_FAIL_COND(p_data.is_empty()); + inspector->invalidate_selection_from_cache(p_data[0]); +} + +void ScriptEditorDebugger::_msg_show_selection_limit_warning(uint64_t p_thread_id, const Array &p_data) { + EditorToaster::get_singleton()->popup_str(vformat(TTR("Some remote nodes were not selected, as the configured maximum selection is %d. This can be changed at \"debugger/max_node_selection\" in the Editor Settings."), EDITOR_GET("debugger/max_node_selection")), EditorToaster::SEVERITY_WARNING); +} + +void ScriptEditorDebugger::_msg_performance_profile_names(uint64_t p_thread_id, const Array &p_data) { + Vector monitors; + monitors.resize(p_data.size()); + for (int i = 0; i < p_data.size(); i++) { + ERR_FAIL_COND(p_data[i].get_type() != Variant::STRING_NAME); + monitors.set(i, p_data[i]); + } + performance_profiler->update_monitors(monitors); +} + +void ScriptEditorDebugger::_msg_filesystem_update_file(uint64_t p_thread_id, const Array &p_data) { + ERR_FAIL_COND(p_data.is_empty()); + if (EditorFileSystem::get_singleton()) { + EditorFileSystem::get_singleton()->update_file(p_data[0]); + } +} + +void ScriptEditorDebugger::_msg_evaluation_return(uint64_t p_thread_id, const Array &p_data) { + expression_evaluator->add_value(p_data); +} + +void ScriptEditorDebugger::_msg_window_title(uint64_t p_thread_id, const Array &p_data) { + ERR_FAIL_COND(p_data.size() != 1); + emit_signal(SNAME("remote_window_title_changed"), p_data[0]); +} + +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); + + ParseMessageFunc *fn_ptr = parse_message_handlers.getptr(p_msg); + if (fn_ptr) { + (this->**fn_ptr)(p_thread_id, p_data); } else { int colon_index = p_msg.find_char(':'); ERR_FAIL_COND_MSG(colon_index < 1, "Invalid message received"); @@ -891,6 +950,39 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, uint64_t p_thread } } +HashMap ScriptEditorDebugger::parse_message_handlers; + +void ScriptEditorDebugger::_init_parse_message_handlers() { + parse_message_handlers["debug_enter"] = &ScriptEditorDebugger::_msg_debug_enter; + parse_message_handlers["debug_exit"] = &ScriptEditorDebugger::_msg_debug_exit; + parse_message_handlers["set_pid"] = &ScriptEditorDebugger::_msg_set_pid; + parse_message_handlers["scene:click_ctrl"] = &ScriptEditorDebugger::_msg_scene_click_ctrl; + parse_message_handlers["scene:scene_tree"] = &ScriptEditorDebugger::_msg_scene_scene_tree; + parse_message_handlers["scene:inspect_objects"] = &ScriptEditorDebugger::_msg_scene_inspect_objects; + parse_message_handlers["servers:memory_usage"] = &ScriptEditorDebugger::_msg_servers_memory_usage; + parse_message_handlers["servers:drawn"] = &ScriptEditorDebugger::_msg_servers_drawn; + parse_message_handlers["stack_dump"] = &ScriptEditorDebugger::_msg_stack_dump; + parse_message_handlers["stack_frame_vars"] = &ScriptEditorDebugger::_msg_stack_frame_vars; + parse_message_handlers["stack_frame_var"] = &ScriptEditorDebugger::_msg_stack_frame_var; + parse_message_handlers["output"] = &ScriptEditorDebugger::_msg_output; + parse_message_handlers["performance:profile_frame"] = &ScriptEditorDebugger::_msg_performance_profile_frame; + parse_message_handlers["visual:hardware_info"] = &ScriptEditorDebugger::_msg_visual_hardware_info; + parse_message_handlers["visual:profile_frame"] = &ScriptEditorDebugger::_msg_visual_profile_frame; + parse_message_handlers["error"] = &ScriptEditorDebugger::_msg_error; + parse_message_handlers["servers:function_signature"] = &ScriptEditorDebugger::_msg_servers_function_signature; + parse_message_handlers["servers:profile_frame"] = &ScriptEditorDebugger::_msg_servers_profile_frame; + parse_message_handlers["servers:profile_total"] = &ScriptEditorDebugger::_msg_servers_profile_total; + parse_message_handlers["request_quit"] = &ScriptEditorDebugger::_msg_request_quit; + parse_message_handlers["remote_nodes_clicked"] = &ScriptEditorDebugger::_msg_remote_nodes_clicked; + parse_message_handlers["remote_nothing_clicked"] = &ScriptEditorDebugger::_msg_remote_nothing_clicked; + parse_message_handlers["remote_selection_invalidated"] = &ScriptEditorDebugger::_msg_remote_selection_invalidated; + parse_message_handlers["show_selection_limit_warning"] = &ScriptEditorDebugger::_msg_show_selection_limit_warning; + parse_message_handlers["performance:profile_names"] = &ScriptEditorDebugger::_msg_performance_profile_names; + parse_message_handlers["filesystem:update_file"] = &ScriptEditorDebugger::_msg_filesystem_update_file; + parse_message_handlers["evaluation_return"] = &ScriptEditorDebugger::_msg_evaluation_return; + parse_message_handlers["window:title"] = &ScriptEditorDebugger::_msg_window_title; +} + void ScriptEditorDebugger::_set_reason_text(const String &p_reason, MessageType p_type) { switch (p_type) { case MESSAGE_ERROR: @@ -1910,6 +2002,10 @@ void ScriptEditorDebugger::toggle_profiler(const String &p_profiler, bool p_enab } ScriptEditorDebugger::ScriptEditorDebugger() { + if (unlikely(parse_message_handlers.is_empty())) { + _init_parse_message_handlers(); + } + tabs = memnew(TabContainer); add_child(tabs); tabs->connect("tab_changed", callable_mp(this, &ScriptEditorDebugger::_tab_changed)); diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h index 60525d2a71..852deaf00a 100644 --- a/editor/debugger/script_editor_debugger.h +++ b/editor/debugger/script_editor_debugger.h @@ -190,6 +190,42 @@ private: void _stack_dump_frame_selected(); void _file_selected(const String &p_file); + + /// Message handler function for _parse_message. + typedef void (ScriptEditorDebugger::*ParseMessageFunc)(uint64_t p_thread_id, const Array &p_data); + static HashMap parse_message_handlers; + static void _init_parse_message_handlers(); + + void _msg_debug_enter(uint64_t p_thread_id, const Array &p_data); + void _msg_debug_exit(uint64_t p_thread_id, const Array &p_data); + void _msg_set_pid(uint64_t p_thread_id, const Array &p_data); + void _msg_scene_click_ctrl(uint64_t p_thread_id, const Array &p_data); + void _msg_scene_scene_tree(uint64_t p_thread_id, const Array &p_data); + void _msg_scene_inspect_objects(uint64_t p_thread_id, const Array &p_data); + void _msg_servers_memory_usage(uint64_t p_thread_id, const Array &p_data); + void _msg_servers_drawn(uint64_t p_thread_id, const Array &p_data); + void _msg_stack_dump(uint64_t p_thread_id, const Array &p_data); + void _msg_stack_frame_vars(uint64_t p_thread_id, const Array &p_data); + void _msg_stack_frame_var(uint64_t p_thread_id, const Array &p_data); + void _msg_output(uint64_t p_thread_id, const Array &p_data); + void _msg_performance_profile_frame(uint64_t p_thread_id, const Array &p_data); + void _msg_visual_hardware_info(uint64_t p_thread_id, const Array &p_data); + void _msg_visual_profile_frame(uint64_t p_thread_id, const Array &p_data); + void _msg_error(uint64_t p_thread_id, const Array &p_data); + void _msg_servers_function_signature(uint64_t p_thread_id, const Array &p_data); + void _msg_servers_profile_common(const Array &p_data, const bool p_final); + void _msg_servers_profile_frame(uint64_t p_thread_id, const Array &p_data); + void _msg_servers_profile_total(uint64_t p_thread_id, const Array &p_data); + void _msg_request_quit(uint64_t p_thread_id, const Array &p_data); + void _msg_remote_nodes_clicked(uint64_t p_thread_id, const Array &p_data); + void _msg_remote_nothing_clicked(uint64_t p_thread_id, const Array &p_data); + void _msg_remote_selection_invalidated(uint64_t p_thread_id, const Array &p_data); + void _msg_show_selection_limit_warning(uint64_t p_thread_id, const Array &p_data); + void _msg_performance_profile_names(uint64_t p_thread_id, const Array &p_data); + void _msg_filesystem_update_file(uint64_t p_thread_id, const Array &p_data); + void _msg_evaluation_return(uint64_t p_thread_id, const Array &p_data); + void _msg_window_title(uint64_t p_thread_id, 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 _update_buttons_state();