| 1 | /**************************************************************************/ |
| 2 | /* remote_debugger.cpp */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #include "remote_debugger.h" |
| 32 | |
| 33 | #include "core/config/project_settings.h" |
| 34 | #include "core/debugger/debugger_marshalls.h" |
| 35 | #include "core/debugger/engine_debugger.h" |
| 36 | #include "core/debugger/engine_profiler.h" |
| 37 | #include "core/debugger/script_debugger.h" |
| 38 | #include "core/input/input.h" |
| 39 | #include "core/object/script_language.h" |
| 40 | #include "core/os/os.h" |
| 41 | |
| 42 | class RemoteDebugger::PerformanceProfiler : public EngineProfiler { |
| 43 | Object *performance = nullptr; |
| 44 | int last_perf_time = 0; |
| 45 | uint64_t last_monitor_modification_time = 0; |
| 46 | |
| 47 | public: |
| 48 | void toggle(bool p_enable, const Array &p_opts) {} |
| 49 | void add(const Array &p_data) {} |
| 50 | void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) { |
| 51 | if (!performance) { |
| 52 | return; |
| 53 | } |
| 54 | |
| 55 | uint64_t pt = OS::get_singleton()->get_ticks_msec(); |
| 56 | if (pt - last_perf_time < 1000) { |
| 57 | return; |
| 58 | } |
| 59 | last_perf_time = pt; |
| 60 | |
| 61 | Array custom_monitor_names = performance->call("get_custom_monitor_names" ); |
| 62 | |
| 63 | uint64_t monitor_modification_time = performance->call("get_monitor_modification_time" ); |
| 64 | if (monitor_modification_time > last_monitor_modification_time) { |
| 65 | last_monitor_modification_time = monitor_modification_time; |
| 66 | EngineDebugger::get_singleton()->send_message("performance:profile_names" , custom_monitor_names); |
| 67 | } |
| 68 | |
| 69 | int max = performance->get("MONITOR_MAX" ); |
| 70 | Array arr; |
| 71 | arr.resize(max + custom_monitor_names.size()); |
| 72 | for (int i = 0; i < max; i++) { |
| 73 | arr[i] = performance->call("get_monitor" , i); |
| 74 | } |
| 75 | |
| 76 | for (int i = 0; i < custom_monitor_names.size(); i++) { |
| 77 | Variant monitor_value = performance->call("get_custom_monitor" , custom_monitor_names[i]); |
| 78 | if (!monitor_value.is_num()) { |
| 79 | ERR_PRINT("Value of custom monitor '" + String(custom_monitor_names[i]) + "' is not a number" ); |
| 80 | arr[i + max] = Variant(); |
| 81 | } else { |
| 82 | arr[i + max] = monitor_value; |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | EngineDebugger::get_singleton()->send_message("performance:profile_frame" , arr); |
| 87 | } |
| 88 | |
| 89 | explicit PerformanceProfiler(Object *p_performance) { |
| 90 | performance = p_performance; |
| 91 | } |
| 92 | }; |
| 93 | |
| 94 | Error RemoteDebugger::_put_msg(String p_message, Array p_data) { |
| 95 | Array msg; |
| 96 | msg.push_back(p_message); |
| 97 | msg.push_back(Thread::get_caller_id()); |
| 98 | msg.push_back(p_data); |
| 99 | Error err = peer->put_message(msg); |
| 100 | if (err != OK) { |
| 101 | n_messages_dropped++; |
| 102 | } |
| 103 | return err; |
| 104 | } |
| 105 | |
| 106 | void RemoteDebugger::_err_handler(void *p_this, const char *p_func, const char *p_file, int p_line, const char *p_err, const char *p_descr, bool p_editor_notify, ErrorHandlerType p_type) { |
| 107 | if (p_type == ERR_HANDLER_SCRIPT) { |
| 108 | return; //ignore script errors, those go through debugger |
| 109 | } |
| 110 | |
| 111 | RemoteDebugger *rd = static_cast<RemoteDebugger *>(p_this); |
| 112 | if (rd->flushing && Thread::get_caller_id() == rd->flush_thread) { // Can't handle recursive errors during flush. |
| 113 | return; |
| 114 | } |
| 115 | |
| 116 | Vector<ScriptLanguage::StackInfo> si; |
| 117 | |
| 118 | for (int i = 0; i < ScriptServer::get_language_count(); i++) { |
| 119 | si = ScriptServer::get_language(i)->debug_get_current_stack_info(); |
| 120 | if (si.size()) { |
| 121 | break; |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | // send_error will lock internally. |
| 126 | rd->script_debugger->send_error(String::utf8(p_func), String::utf8(p_file), p_line, String::utf8(p_err), String::utf8(p_descr), p_editor_notify, p_type, si); |
| 127 | } |
| 128 | |
| 129 | void RemoteDebugger::_print_handler(void *p_this, const String &p_string, bool p_error, bool p_rich) { |
| 130 | RemoteDebugger *rd = static_cast<RemoteDebugger *>(p_this); |
| 131 | |
| 132 | if (rd->flushing && Thread::get_caller_id() == rd->flush_thread) { // Can't handle recursive prints during flush. |
| 133 | return; |
| 134 | } |
| 135 | |
| 136 | String s = p_string; |
| 137 | int allowed_chars = MIN(MAX(rd->max_chars_per_second - rd->char_count, 0), s.length()); |
| 138 | |
| 139 | if (allowed_chars == 0 && s.length() > 0) { |
| 140 | return; |
| 141 | } |
| 142 | |
| 143 | if (allowed_chars < s.length()) { |
| 144 | s = s.substr(0, allowed_chars); |
| 145 | } |
| 146 | |
| 147 | MutexLock lock(rd->mutex); |
| 148 | |
| 149 | rd->char_count += allowed_chars; |
| 150 | bool overflowed = rd->char_count >= rd->max_chars_per_second; |
| 151 | if (rd->is_peer_connected()) { |
| 152 | if (overflowed) { |
| 153 | s += "[...]" ; |
| 154 | } |
| 155 | |
| 156 | OutputString output_string; |
| 157 | output_string.message = s; |
| 158 | if (p_error) { |
| 159 | output_string.type = MESSAGE_TYPE_ERROR; |
| 160 | } else if (p_rich) { |
| 161 | output_string.type = MESSAGE_TYPE_LOG_RICH; |
| 162 | } else { |
| 163 | output_string.type = MESSAGE_TYPE_LOG; |
| 164 | } |
| 165 | rd->output_strings.push_back(output_string); |
| 166 | |
| 167 | if (overflowed) { |
| 168 | output_string.message = "[output overflow, print less text!]" ; |
| 169 | output_string.type = MESSAGE_TYPE_ERROR; |
| 170 | rd->output_strings.push_back(output_string); |
| 171 | } |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | RemoteDebugger::ErrorMessage RemoteDebugger::_create_overflow_error(const String &p_what, const String &p_descr) { |
| 176 | ErrorMessage oe; |
| 177 | oe.error = p_what; |
| 178 | oe.error_descr = p_descr; |
| 179 | oe.warning = false; |
| 180 | uint64_t time = OS::get_singleton()->get_ticks_msec(); |
| 181 | oe.hr = time / 3600000; |
| 182 | oe.min = (time / 60000) % 60; |
| 183 | oe.sec = (time / 1000) % 60; |
| 184 | oe.msec = time % 1000; |
| 185 | return oe; |
| 186 | } |
| 187 | |
| 188 | void RemoteDebugger::flush_output() { |
| 189 | MutexLock lock(mutex); |
| 190 | flush_thread = Thread::get_caller_id(); |
| 191 | flushing = true; |
| 192 | if (!is_peer_connected()) { |
| 193 | return; |
| 194 | } |
| 195 | |
| 196 | if (n_messages_dropped > 0) { |
| 197 | ErrorMessage err_msg = _create_overflow_error("TOO_MANY_MESSAGES" , "Too many messages! " + String::num_int64(n_messages_dropped) + " messages were dropped. Profiling might misbheave, try raising 'network/limits/debugger/max_queued_messages' in project setting." ); |
| 198 | if (_put_msg("error" , err_msg.serialize()) == OK) { |
| 199 | n_messages_dropped = 0; |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | if (output_strings.size()) { |
| 204 | // Join output strings so we generate less messages. |
| 205 | Vector<String> joined_log_strings; |
| 206 | Vector<String> strings; |
| 207 | Vector<int> types; |
| 208 | for (int i = 0; i < output_strings.size(); i++) { |
| 209 | const OutputString &output_string = output_strings[i]; |
| 210 | if (output_string.type == MESSAGE_TYPE_ERROR) { |
| 211 | if (!joined_log_strings.is_empty()) { |
| 212 | strings.push_back(String("\n" ).join(joined_log_strings)); |
| 213 | types.push_back(MESSAGE_TYPE_LOG); |
| 214 | joined_log_strings.clear(); |
| 215 | } |
| 216 | strings.push_back(output_string.message); |
| 217 | types.push_back(MESSAGE_TYPE_ERROR); |
| 218 | } else if (output_string.type == MESSAGE_TYPE_LOG_RICH) { |
| 219 | if (!joined_log_strings.is_empty()) { |
| 220 | strings.push_back(String("\n" ).join(joined_log_strings)); |
| 221 | types.push_back(MESSAGE_TYPE_LOG_RICH); |
| 222 | joined_log_strings.clear(); |
| 223 | } |
| 224 | strings.push_back(output_string.message); |
| 225 | types.push_back(MESSAGE_TYPE_LOG_RICH); |
| 226 | } else { |
| 227 | joined_log_strings.push_back(output_string.message); |
| 228 | } |
| 229 | } |
| 230 | |
| 231 | if (!joined_log_strings.is_empty()) { |
| 232 | strings.push_back(String("\n" ).join(joined_log_strings)); |
| 233 | types.push_back(MESSAGE_TYPE_LOG); |
| 234 | } |
| 235 | |
| 236 | Array arr; |
| 237 | arr.push_back(strings); |
| 238 | arr.push_back(types); |
| 239 | _put_msg("output" , arr); |
| 240 | output_strings.clear(); |
| 241 | } |
| 242 | |
| 243 | while (errors.size()) { |
| 244 | ErrorMessage oe = errors.front()->get(); |
| 245 | _put_msg("error" , oe.serialize()); |
| 246 | errors.pop_front(); |
| 247 | } |
| 248 | |
| 249 | // Update limits |
| 250 | uint64_t ticks = OS::get_singleton()->get_ticks_usec() / 1000; |
| 251 | |
| 252 | if (ticks - last_reset > 1000) { |
| 253 | last_reset = ticks; |
| 254 | char_count = 0; |
| 255 | err_count = 0; |
| 256 | n_errors_dropped = 0; |
| 257 | warn_count = 0; |
| 258 | n_warnings_dropped = 0; |
| 259 | } |
| 260 | flushing = false; |
| 261 | } |
| 262 | |
| 263 | void RemoteDebugger::send_message(const String &p_message, const Array &p_args) { |
| 264 | MutexLock lock(mutex); |
| 265 | if (is_peer_connected()) { |
| 266 | _put_msg(p_message, p_args); |
| 267 | } |
| 268 | } |
| 269 | |
| 270 | void RemoteDebugger::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, bool p_editor_notify, ErrorHandlerType p_type) { |
| 271 | ErrorMessage oe; |
| 272 | oe.error = p_err; |
| 273 | oe.error_descr = p_descr; |
| 274 | oe.source_file = p_file; |
| 275 | oe.source_line = p_line; |
| 276 | oe.source_func = p_func; |
| 277 | oe.warning = p_type == ERR_HANDLER_WARNING; |
| 278 | uint64_t time = OS::get_singleton()->get_ticks_msec(); |
| 279 | oe.hr = time / 3600000; |
| 280 | oe.min = (time / 60000) % 60; |
| 281 | oe.sec = (time / 1000) % 60; |
| 282 | oe.msec = time % 1000; |
| 283 | oe.callstack.append_array(script_debugger->get_error_stack_info()); |
| 284 | |
| 285 | if (flushing && Thread::get_caller_id() == flush_thread) { // Can't handle recursive errors during flush. |
| 286 | return; |
| 287 | } |
| 288 | |
| 289 | MutexLock lock(mutex); |
| 290 | |
| 291 | if (oe.warning) { |
| 292 | warn_count++; |
| 293 | } else { |
| 294 | err_count++; |
| 295 | } |
| 296 | |
| 297 | if (is_peer_connected()) { |
| 298 | if (oe.warning) { |
| 299 | if (warn_count > max_warnings_per_second) { |
| 300 | n_warnings_dropped++; |
| 301 | if (n_warnings_dropped == 1) { |
| 302 | // Only print one message about dropping per second |
| 303 | ErrorMessage overflow = _create_overflow_error("TOO_MANY_WARNINGS" , "Too many warnings! Ignoring warnings for up to 1 second." ); |
| 304 | errors.push_back(overflow); |
| 305 | } |
| 306 | } else { |
| 307 | errors.push_back(oe); |
| 308 | } |
| 309 | } else { |
| 310 | if (err_count > max_errors_per_second) { |
| 311 | n_errors_dropped++; |
| 312 | if (n_errors_dropped == 1) { |
| 313 | // Only print one message about dropping per second |
| 314 | ErrorMessage overflow = _create_overflow_error("TOO_MANY_ERRORS" , "Too many errors! Ignoring errors for up to 1 second." ); |
| 315 | errors.push_back(overflow); |
| 316 | } |
| 317 | } else { |
| 318 | errors.push_back(oe); |
| 319 | } |
| 320 | } |
| 321 | } |
| 322 | } |
| 323 | |
| 324 | void RemoteDebugger::_send_stack_vars(List<String> &p_names, List<Variant> &p_vals, int p_type) { |
| 325 | DebuggerMarshalls::ScriptStackVariable stvar; |
| 326 | List<String>::Element *E = p_names.front(); |
| 327 | List<Variant>::Element *F = p_vals.front(); |
| 328 | while (E) { |
| 329 | stvar.name = E->get(); |
| 330 | stvar.value = F->get(); |
| 331 | stvar.type = p_type; |
| 332 | send_message("stack_frame_var" , stvar.serialize()); |
| 333 | E = E->next(); |
| 334 | F = F->next(); |
| 335 | } |
| 336 | } |
| 337 | |
| 338 | Error RemoteDebugger::_try_capture(const String &p_msg, const Array &p_data, bool &r_captured) { |
| 339 | const int idx = p_msg.find(":" ); |
| 340 | r_captured = false; |
| 341 | if (idx < 0) { // No prefix, unknown message. |
| 342 | return OK; |
| 343 | } |
| 344 | const String cap = p_msg.substr(0, idx); |
| 345 | if (!has_capture(cap)) { |
| 346 | return ERR_UNAVAILABLE; // Unknown message... |
| 347 | } |
| 348 | const String msg = p_msg.substr(idx + 1); |
| 349 | return capture_parse(cap, msg, p_data, r_captured); |
| 350 | } |
| 351 | |
| 352 | void RemoteDebugger::_poll_messages() { |
| 353 | MutexLock mutex_lock(mutex); |
| 354 | |
| 355 | peer->poll(); |
| 356 | while (peer->has_message()) { |
| 357 | Array cmd = peer->get_message(); |
| 358 | ERR_CONTINUE(cmd.size() != 3); |
| 359 | ERR_CONTINUE(cmd[0].get_type() != Variant::STRING); |
| 360 | ERR_CONTINUE(cmd[1].get_type() != Variant::INT); |
| 361 | ERR_CONTINUE(cmd[2].get_type() != Variant::ARRAY); |
| 362 | |
| 363 | Thread::ID thread = cmd[1]; |
| 364 | |
| 365 | if (!messages.has(thread)) { |
| 366 | continue; // This thread is not around to receive the messages |
| 367 | } |
| 368 | |
| 369 | Message msg; |
| 370 | msg.message = cmd[0]; |
| 371 | msg.data = cmd[2]; |
| 372 | messages[thread].push_back(msg); |
| 373 | } |
| 374 | } |
| 375 | |
| 376 | bool RemoteDebugger::_has_messages() { |
| 377 | MutexLock mutex_lock(mutex); |
| 378 | return messages.has(Thread::get_caller_id()) && !messages[Thread::get_caller_id()].is_empty(); |
| 379 | } |
| 380 | |
| 381 | Array RemoteDebugger::_get_message() { |
| 382 | MutexLock mutex_lock(mutex); |
| 383 | ERR_FAIL_COND_V(!messages.has(Thread::get_caller_id()), Array()); |
| 384 | List<Message> &message_list = messages[Thread::get_caller_id()]; |
| 385 | ERR_FAIL_COND_V(message_list.is_empty(), Array()); |
| 386 | |
| 387 | Array msg; |
| 388 | msg.resize(2); |
| 389 | msg[0] = message_list.front()->get().message; |
| 390 | msg[1] = message_list.front()->get().data; |
| 391 | message_list.pop_front(); |
| 392 | return msg; |
| 393 | } |
| 394 | |
| 395 | void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { |
| 396 | //this function is called when there is a debugger break (bug on script) |
| 397 | //or when execution is paused from editor |
| 398 | |
| 399 | { |
| 400 | MutexLock lock(mutex); |
| 401 | // Tests that require mutex. |
| 402 | if (script_debugger->is_skipping_breakpoints() && !p_is_error_breakpoint) { |
| 403 | return; |
| 404 | } |
| 405 | |
| 406 | ERR_FAIL_COND_MSG(!is_peer_connected(), "Script Debugger failed to connect, but being used anyway." ); |
| 407 | |
| 408 | if (!peer->can_block()) { |
| 409 | return; // Peer does not support blocking IO. We could at least send the error though. |
| 410 | } |
| 411 | } |
| 412 | |
| 413 | ScriptLanguage *script_lang = script_debugger->get_break_language(); |
| 414 | const String error_str = script_lang ? script_lang->debug_get_error() : "" ; |
| 415 | Array msg; |
| 416 | msg.push_back(p_can_continue); |
| 417 | msg.push_back(error_str); |
| 418 | ERR_FAIL_NULL(script_lang); |
| 419 | msg.push_back(script_lang->debug_get_stack_level_count() > 0); |
| 420 | msg.push_back(Thread::get_caller_id() == Thread::get_main_id() ? String(RTR("Main Thread" )) : itos(Thread::get_caller_id())); |
| 421 | if (allow_focus_steal_fn) { |
| 422 | allow_focus_steal_fn(); |
| 423 | } |
| 424 | send_message("debug_enter" , msg); |
| 425 | |
| 426 | Input::MouseMode mouse_mode = Input::MOUSE_MODE_VISIBLE; |
| 427 | |
| 428 | if (Thread::get_caller_id() == Thread::get_main_id()) { |
| 429 | mouse_mode = Input::get_singleton()->get_mouse_mode(); |
| 430 | if (mouse_mode != Input::MOUSE_MODE_VISIBLE) { |
| 431 | Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); |
| 432 | } |
| 433 | } else { |
| 434 | MutexLock mutex_lock(mutex); |
| 435 | messages.insert(Thread::get_caller_id(), List<Message>()); |
| 436 | } |
| 437 | |
| 438 | mutex.lock(); |
| 439 | while (is_peer_connected()) { |
| 440 | mutex.unlock(); |
| 441 | flush_output(); |
| 442 | |
| 443 | _poll_messages(); |
| 444 | |
| 445 | if (_has_messages()) { |
| 446 | Array cmd = _get_message(); |
| 447 | |
| 448 | ERR_CONTINUE(cmd.size() != 2); |
| 449 | ERR_CONTINUE(cmd[0].get_type() != Variant::STRING); |
| 450 | ERR_CONTINUE(cmd[1].get_type() != Variant::ARRAY); |
| 451 | |
| 452 | String command = cmd[0]; |
| 453 | Array data = cmd[1]; |
| 454 | |
| 455 | if (command == "step" ) { |
| 456 | script_debugger->set_depth(-1); |
| 457 | script_debugger->set_lines_left(1); |
| 458 | break; |
| 459 | |
| 460 | } else if (command == "next" ) { |
| 461 | script_debugger->set_depth(0); |
| 462 | script_debugger->set_lines_left(1); |
| 463 | break; |
| 464 | |
| 465 | } else if (command == "continue" ) { |
| 466 | script_debugger->set_depth(-1); |
| 467 | script_debugger->set_lines_left(-1); |
| 468 | break; |
| 469 | |
| 470 | } else if (command == "break" ) { |
| 471 | ERR_PRINT("Got break when already broke!" ); |
| 472 | break; |
| 473 | |
| 474 | } else if (command == "get_stack_dump" ) { |
| 475 | DebuggerMarshalls::ScriptStackDump dump; |
| 476 | int slc = script_lang->debug_get_stack_level_count(); |
| 477 | for (int i = 0; i < slc; i++) { |
| 478 | ScriptLanguage::StackInfo frame; |
| 479 | frame.file = script_lang->debug_get_stack_level_source(i); |
| 480 | frame.line = script_lang->debug_get_stack_level_line(i); |
| 481 | frame.func = script_lang->debug_get_stack_level_function(i); |
| 482 | dump.frames.push_back(frame); |
| 483 | } |
| 484 | send_message("stack_dump" , dump.serialize()); |
| 485 | |
| 486 | } else if (command == "get_stack_frame_vars" ) { |
| 487 | ERR_FAIL_COND(data.size() != 1); |
| 488 | ERR_FAIL_NULL(script_lang); |
| 489 | int lv = data[0]; |
| 490 | |
| 491 | List<String> members; |
| 492 | List<Variant> member_vals; |
| 493 | if (ScriptInstance *inst = script_lang->debug_get_stack_level_instance(lv)) { |
| 494 | members.push_back("self" ); |
| 495 | member_vals.push_back(inst->get_owner()); |
| 496 | } |
| 497 | script_lang->debug_get_stack_level_members(lv, &members, &member_vals); |
| 498 | ERR_FAIL_COND(members.size() != member_vals.size()); |
| 499 | |
| 500 | List<String> locals; |
| 501 | List<Variant> local_vals; |
| 502 | script_lang->debug_get_stack_level_locals(lv, &locals, &local_vals); |
| 503 | ERR_FAIL_COND(locals.size() != local_vals.size()); |
| 504 | |
| 505 | List<String> globals; |
| 506 | List<Variant> globals_vals; |
| 507 | script_lang->debug_get_globals(&globals, &globals_vals); |
| 508 | ERR_FAIL_COND(globals.size() != globals_vals.size()); |
| 509 | |
| 510 | Array var_size; |
| 511 | var_size.push_back(local_vals.size() + member_vals.size() + globals_vals.size()); |
| 512 | send_message("stack_frame_vars" , var_size); |
| 513 | _send_stack_vars(locals, local_vals, 0); |
| 514 | _send_stack_vars(members, member_vals, 1); |
| 515 | _send_stack_vars(globals, globals_vals, 2); |
| 516 | |
| 517 | } else if (command == "reload_scripts" ) { |
| 518 | reload_all_scripts = true; |
| 519 | |
| 520 | } else if (command == "breakpoint" ) { |
| 521 | ERR_FAIL_COND(data.size() < 3); |
| 522 | bool set = data[2]; |
| 523 | if (set) { |
| 524 | script_debugger->insert_breakpoint(data[1], data[0]); |
| 525 | } else { |
| 526 | script_debugger->remove_breakpoint(data[1], data[0]); |
| 527 | } |
| 528 | |
| 529 | } else if (command == "set_skip_breakpoints" ) { |
| 530 | ERR_FAIL_COND(data.size() < 1); |
| 531 | script_debugger->set_skip_breakpoints(data[0]); |
| 532 | } else { |
| 533 | bool captured = false; |
| 534 | ERR_CONTINUE(_try_capture(command, data, captured) != OK); |
| 535 | if (!captured) { |
| 536 | WARN_PRINT("Unknown message received from debugger: " + command); |
| 537 | } |
| 538 | } |
| 539 | } else { |
| 540 | OS::get_singleton()->delay_usec(10000); |
| 541 | if (Thread::get_caller_id() == Thread::get_main_id()) { |
| 542 | // If this is a busy loop on the main thread, events still need to be processed. |
| 543 | OS::get_singleton()->process_and_drop_events(); |
| 544 | } |
| 545 | } |
| 546 | } |
| 547 | |
| 548 | send_message("debug_exit" , Array()); |
| 549 | |
| 550 | if (Thread::get_caller_id() == Thread::get_main_id()) { |
| 551 | if (mouse_mode != Input::MOUSE_MODE_VISIBLE) { |
| 552 | Input::get_singleton()->set_mouse_mode(mouse_mode); |
| 553 | } |
| 554 | } else { |
| 555 | MutexLock mutex_lock(mutex); |
| 556 | messages.erase(Thread::get_caller_id()); |
| 557 | } |
| 558 | } |
| 559 | |
| 560 | void RemoteDebugger::poll_events(bool p_is_idle) { |
| 561 | if (peer.is_null()) { |
| 562 | return; |
| 563 | } |
| 564 | |
| 565 | flush_output(); |
| 566 | |
| 567 | _poll_messages(); |
| 568 | |
| 569 | while (_has_messages()) { |
| 570 | Array arr = _get_message(); |
| 571 | |
| 572 | ERR_CONTINUE(arr.size() != 2); |
| 573 | ERR_CONTINUE(arr[0].get_type() != Variant::STRING); |
| 574 | ERR_CONTINUE(arr[1].get_type() != Variant::ARRAY); |
| 575 | |
| 576 | const String cmd = arr[0]; |
| 577 | const int idx = cmd.find(":" ); |
| 578 | bool parsed = false; |
| 579 | if (idx < 0) { // Not prefix, use scripts capture. |
| 580 | capture_parse("core" , cmd, arr[1], parsed); |
| 581 | continue; |
| 582 | } |
| 583 | |
| 584 | const String cap = cmd.substr(0, idx); |
| 585 | if (!has_capture(cap)) { |
| 586 | continue; // Unknown message... |
| 587 | } |
| 588 | |
| 589 | const String msg = cmd.substr(idx + 1); |
| 590 | capture_parse(cap, msg, arr[1], parsed); |
| 591 | } |
| 592 | |
| 593 | // Reload scripts during idle poll only. |
| 594 | if (p_is_idle && reload_all_scripts) { |
| 595 | for (int i = 0; i < ScriptServer::get_language_count(); i++) { |
| 596 | ScriptServer::get_language(i)->reload_all_scripts(); |
| 597 | } |
| 598 | reload_all_scripts = false; |
| 599 | } |
| 600 | } |
| 601 | |
| 602 | Error RemoteDebugger::_core_capture(const String &p_cmd, const Array &p_data, bool &r_captured) { |
| 603 | r_captured = true; |
| 604 | if (p_cmd == "reload_scripts" ) { |
| 605 | reload_all_scripts = true; |
| 606 | |
| 607 | } else if (p_cmd == "breakpoint" ) { |
| 608 | ERR_FAIL_COND_V(p_data.size() < 3, ERR_INVALID_DATA); |
| 609 | bool set = p_data[2]; |
| 610 | if (set) { |
| 611 | script_debugger->insert_breakpoint(p_data[1], p_data[0]); |
| 612 | } else { |
| 613 | script_debugger->remove_breakpoint(p_data[1], p_data[0]); |
| 614 | } |
| 615 | |
| 616 | } else if (p_cmd == "set_skip_breakpoints" ) { |
| 617 | ERR_FAIL_COND_V(p_data.size() < 1, ERR_INVALID_DATA); |
| 618 | script_debugger->set_skip_breakpoints(p_data[0]); |
| 619 | } else if (p_cmd == "break" ) { |
| 620 | script_debugger->debug(script_debugger->get_break_language()); |
| 621 | } else { |
| 622 | r_captured = false; |
| 623 | } |
| 624 | return OK; |
| 625 | } |
| 626 | |
| 627 | Error RemoteDebugger::_profiler_capture(const String &p_cmd, const Array &p_data, bool &r_captured) { |
| 628 | r_captured = false; |
| 629 | ERR_FAIL_COND_V(p_data.size() < 1, ERR_INVALID_DATA); |
| 630 | ERR_FAIL_COND_V(p_data[0].get_type() != Variant::BOOL, ERR_INVALID_DATA); |
| 631 | ERR_FAIL_COND_V(!has_profiler(p_cmd), ERR_UNAVAILABLE); |
| 632 | Array opts; |
| 633 | if (p_data.size() > 1) { // Optional profiler parameters. |
| 634 | ERR_FAIL_COND_V(p_data[1].get_type() != Variant::ARRAY, ERR_INVALID_DATA); |
| 635 | opts = p_data[1]; |
| 636 | } |
| 637 | r_captured = true; |
| 638 | profiler_enable(p_cmd, p_data[0], opts); |
| 639 | return OK; |
| 640 | } |
| 641 | |
| 642 | RemoteDebugger::RemoteDebugger(Ref<RemoteDebuggerPeer> p_peer) { |
| 643 | peer = p_peer; |
| 644 | max_chars_per_second = GLOBAL_GET("network/limits/debugger/max_chars_per_second" ); |
| 645 | max_errors_per_second = GLOBAL_GET("network/limits/debugger/max_errors_per_second" ); |
| 646 | max_warnings_per_second = GLOBAL_GET("network/limits/debugger/max_warnings_per_second" ); |
| 647 | |
| 648 | // Performance Profiler |
| 649 | Object *perf = Engine::get_singleton()->get_singleton_object("Performance" ); |
| 650 | if (perf) { |
| 651 | performance_profiler = Ref<PerformanceProfiler>(memnew(PerformanceProfiler(perf))); |
| 652 | performance_profiler->bind("performance" ); |
| 653 | profiler_enable("performance" , true); |
| 654 | } |
| 655 | |
| 656 | // Core and profiler captures. |
| 657 | Capture core_cap(this, |
| 658 | [](void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) { |
| 659 | return static_cast<RemoteDebugger *>(p_user)->_core_capture(p_cmd, p_data, r_captured); |
| 660 | }); |
| 661 | register_message_capture("core" , core_cap); |
| 662 | Capture profiler_cap(this, |
| 663 | [](void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) { |
| 664 | return static_cast<RemoteDebugger *>(p_user)->_profiler_capture(p_cmd, p_data, r_captured); |
| 665 | }); |
| 666 | register_message_capture("profiler" , profiler_cap); |
| 667 | |
| 668 | // Error handlers |
| 669 | phl.printfunc = _print_handler; |
| 670 | phl.userdata = this; |
| 671 | add_print_handler(&phl); |
| 672 | |
| 673 | eh.errfunc = _err_handler; |
| 674 | eh.userdata = this; |
| 675 | add_error_handler(&eh); |
| 676 | |
| 677 | messages.insert(Thread::get_main_id(), List<Message>()); |
| 678 | } |
| 679 | |
| 680 | RemoteDebugger::~RemoteDebugger() { |
| 681 | remove_print_handler(&phl); |
| 682 | remove_error_handler(&eh); |
| 683 | } |
| 684 | |