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 | |