diff options
41 files changed, 4485 insertions, 3290 deletions
diff --git a/core/script_debugger_remote.cpp b/core/script_debugger_remote.cpp new file mode 100644 index 0000000000..fdb6135edd --- /dev/null +++ b/core/script_debugger_remote.cpp @@ -0,0 +1,1145 @@ +/*************************************************************************/ +/* script_debugger_remote.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "script_debugger_remote.h" + +#include "core/engine.h" +#include "core/io/ip.h" +#include "core/io/marshalls.h" +#include "core/os/input.h" +#include "core/os/os.h" +#include "core/project_settings.h" +#include "servers/visual_server.h" + +#define CHECK_SIZE(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() < (uint32_t)(expected), false, String("Malformed ") + what + " message from script debugger, message too short. Exptected size: " + itos(expected) + ", actual size: " + itos(arr.size())) +#define CHECK_END(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() > (uint32_t)expected, false, String("Malformed ") + what + " message from script debugger, message too short. Exptected size: " + itos(expected) + ", actual size: " + itos(arr.size())) + +Array ScriptDebuggerRemote::ScriptStackDump::serialize() { + Array arr; + arr.push_back(frames.size() * 3); + for (int i = 0; i < frames.size(); i++) { + arr.push_back(frames[i].file); + arr.push_back(frames[i].line); + arr.push_back(frames[i].func); + } + return arr; +} + +bool ScriptDebuggerRemote::ScriptStackDump::deserialize(const Array &p_arr) { + CHECK_SIZE(p_arr, 1, "ScriptStackDump"); + uint32_t size = p_arr[0]; + CHECK_SIZE(p_arr, size, "ScriptStackDump"); + int idx = 1; + for (uint32_t i = 0; i < size / 3; i++) { + ScriptLanguage::StackInfo sf; + sf.file = p_arr[idx]; + sf.line = p_arr[idx + 1]; + sf.func = p_arr[idx + 2]; + frames.push_back(sf); + idx += 3; + } + CHECK_END(p_arr, idx, "ScriptStackDump"); + return true; +} + +Array ScriptDebuggerRemote::ScriptStackVariable::serialize(int max_size) { + Array arr; + arr.push_back(name); + arr.push_back(type); + + Variant var = value; + if (value.get_type() == Variant::OBJECT && value.get_validated_object() == nullptr) { + var = Variant(); + } + + int len = 0; + Error err = encode_variant(var, NULL, len, true); + if (err != OK) + ERR_PRINT("Failed to encode variant."); + + if (len > max_size) { + arr.push_back(Variant()); + } else { + arr.push_back(var); + } + return arr; +} + +bool ScriptDebuggerRemote::ScriptStackVariable::deserialize(const Array &p_arr) { + CHECK_SIZE(p_arr, 3, "ScriptStackVariable"); + name = p_arr[0]; + type = p_arr[1]; + value = p_arr[2]; + CHECK_END(p_arr, 3, "ScriptStackVariable"); + return true; +} + +Array ScriptDebuggerRemote::OutputError::serialize() { + Array arr; + arr.push_back(hr); + arr.push_back(min); + arr.push_back(sec); + arr.push_back(msec); + arr.push_back(source_file); + arr.push_back(source_func); + arr.push_back(source_line); + arr.push_back(error); + arr.push_back(error_descr); + arr.push_back(warning); + unsigned int size = callstack.size(); + const ScriptLanguage::StackInfo *r = callstack.ptr(); + arr.push_back(size * 3); + for (int i = 0; i < callstack.size(); i++) { + arr.push_back(r[i].file); + arr.push_back(r[i].func); + arr.push_back(r[i].line); + } + return arr; +} + +bool ScriptDebuggerRemote::OutputError::deserialize(const Array &p_arr) { + CHECK_SIZE(p_arr, 11, "OutputError"); + hr = p_arr[0]; + min = p_arr[1]; + sec = p_arr[2]; + msec = p_arr[3]; + source_file = p_arr[4]; + source_func = p_arr[5]; + source_line = p_arr[6]; + error = p_arr[7]; + error_descr = p_arr[8]; + warning = p_arr[9]; + unsigned int stack_size = p_arr[10]; + CHECK_SIZE(p_arr, stack_size, "OutputError"); + int idx = 11; + callstack.resize(stack_size / 3); + ScriptLanguage::StackInfo *w = callstack.ptrw(); + for (unsigned int i = 0; i < stack_size / 3; i++) { + w[i].file = p_arr[idx]; + w[i].func = p_arr[idx + 1]; + w[i].line = p_arr[idx + 2]; + idx += 3; + } + CHECK_END(p_arr, idx, "OutputError"); + return true; +} + +Array ScriptDebuggerRemote::ResourceUsage::serialize() { + infos.sort(); + + Array arr; + arr.push_back(infos.size() * 4); + for (List<ResourceInfo>::Element *E = infos.front(); E; E = E->next()) { + arr.push_back(E->get().path); + arr.push_back(E->get().format); + arr.push_back(E->get().type); + arr.push_back(E->get().vram); + } + return arr; +} + +bool ScriptDebuggerRemote::ResourceUsage::deserialize(const Array &p_arr) { + CHECK_SIZE(p_arr, 1, "ResourceUsage"); + uint32_t size = p_arr[0]; + CHECK_SIZE(p_arr, size, "ResourceUsage"); + int idx = 1; + for (uint32_t i = 0; i < size / 4; i++) { + ResourceInfo info; + info.path = p_arr[idx]; + info.format = p_arr[idx + 1]; + info.type = p_arr[idx + 2]; + info.vram = p_arr[idx + 3]; + infos.push_back(info); + } + CHECK_END(p_arr, idx, "ResourceUsage"); + return true; +} + +Array ScriptDebuggerRemote::ProfilerSignature::serialize() { + Array arr; + arr.push_back(name); + arr.push_back(id); + return arr; +} + +bool ScriptDebuggerRemote::ProfilerSignature::deserialize(const Array &p_arr) { + CHECK_SIZE(p_arr, 2, "ProfilerSignature"); + name = p_arr[0]; + id = p_arr[1]; + CHECK_END(p_arr, 2, "ProfilerSignature"); + return true; +} + +Array ScriptDebuggerRemote::ProfilerFrame::serialize() { + Array arr; + arr.push_back(frame_number); + arr.push_back(frame_time); + arr.push_back(idle_time); + arr.push_back(physics_time); + arr.push_back(physics_frame_time); + arr.push_back(USEC_TO_SEC(script_time)); + + arr.push_back(frames_data.size()); + arr.push_back(frame_functions.size() * 4); + + // Servers profiling info. + for (int i = 0; i < frames_data.size(); i++) { + arr.push_back(frames_data[i].name); // Type (physics/process/audio/...) + arr.push_back(frames_data[i].data.size()); + for (int j = 0; j < frames_data[i].data.size() / 2; j++) { + arr.push_back(frames_data[i].data[2 * j]); // NAME + arr.push_back(frames_data[i].data[2 * j + 1]); // TIME + } + } + for (int i = 0; i < frame_functions.size(); i++) { + arr.push_back(frame_functions[i].sig_id); + arr.push_back(frame_functions[i].call_count); + arr.push_back(frame_functions[i].self_time); + arr.push_back(frame_functions[i].total_time); + } + return arr; +} + +bool ScriptDebuggerRemote::ProfilerFrame::deserialize(const Array &p_arr) { + CHECK_SIZE(p_arr, 8, "ProfilerFrame"); + frame_number = p_arr[0]; + frame_time = p_arr[1]; + idle_time = p_arr[2]; + physics_time = p_arr[3]; + physics_frame_time = p_arr[4]; + script_time = p_arr[5]; + uint32_t frame_data_size = p_arr[6]; + int frame_func_size = p_arr[7]; + int idx = 8; + while (frame_data_size) { + CHECK_SIZE(p_arr, idx + 2, "ProfilerFrame"); + frame_data_size--; + FrameData fd; + fd.name = p_arr[idx]; + int sub_data_size = p_arr[idx + 1]; + idx += 2; + CHECK_SIZE(p_arr, idx + sub_data_size, "ProfilerFrame"); + for (int j = 0; j < sub_data_size / 2; j++) { + fd.data.push_back(p_arr[idx]); // NAME + fd.data.push_back(p_arr[idx + 1]); // TIME + idx += 2; + } + frames_data.push_back(fd); + } + CHECK_SIZE(p_arr, idx + frame_func_size, "ProfilerFrame"); + for (int i = 0; i < frame_func_size / 4; i++) { + FrameFunction ff; + ff.sig_id = p_arr[idx]; + ff.call_count = p_arr[idx + 1]; + ff.self_time = p_arr[idx + 2]; + ff.total_time = p_arr[idx + 3]; + frame_functions.push_back(ff); + idx += 4; + } + CHECK_END(p_arr, idx, "ProfilerFrame"); + return true; +} + +Array ScriptDebuggerRemote::NetworkProfilerFrame::serialize() { + Array arr; + arr.push_back(infos.size() * 6); + for (int i = 0; i < infos.size(); ++i) { + arr.push_back(uint64_t(infos[i].node)); + arr.push_back(infos[i].node_path); + arr.push_back(infos[i].incoming_rpc); + arr.push_back(infos[i].incoming_rset); + arr.push_back(infos[i].outgoing_rpc); + arr.push_back(infos[i].outgoing_rset); + } + return arr; +} + +bool ScriptDebuggerRemote::NetworkProfilerFrame::deserialize(const Array &p_arr) { + CHECK_SIZE(p_arr, 1, "NetworkProfilerFrame"); + uint32_t size = p_arr[0]; + CHECK_SIZE(p_arr, size, "NetworkProfilerFrame"); + infos.resize(size); + int idx = 1; + for (uint32_t i = 0; i < size / 6; ++i) { + infos.write[i].node = uint64_t(p_arr[idx]); + infos.write[i].node_path = p_arr[idx + 1]; + infos.write[i].incoming_rpc = p_arr[idx + 2]; + infos.write[i].incoming_rset = p_arr[idx + 3]; + infos.write[i].outgoing_rpc = p_arr[idx + 4]; + infos.write[i].outgoing_rset = p_arr[idx + 5]; + } + CHECK_END(p_arr, idx, "NetworkProfilerFrame"); + return true; +} + +void ScriptDebuggerRemote::_put_msg(String p_message, Array p_data) { + Array msg; + msg.push_back(p_message); + msg.push_back(p_data); + packet_peer_stream->put_var(msg); +} + +bool ScriptDebuggerRemote::is_peer_connected() { + return tcp_client->is_connected_to_host() && tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED; +} + +void ScriptDebuggerRemote::_send_video_memory() { + + ResourceUsage usage; + if (resource_usage_func) + resource_usage_func(&usage); + + _put_msg("message:video_mem", usage.serialize()); +} + +Error ScriptDebuggerRemote::connect_to_host(const String &p_host, uint16_t p_port) { + + IP_Address ip; + if (p_host.is_valid_ip_address()) + ip = p_host; + else + ip = IP::get_singleton()->resolve_hostname(p_host); + + int port = p_port; + + const int tries = 6; + int waits[tries] = { 1, 10, 100, 1000, 1000, 1000 }; + + tcp_client->connect_to_host(ip, port); + + for (int i = 0; i < tries; i++) { + + if (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED) { + print_verbose("Remote Debugger: Connected!"); + break; + } else { + + const int ms = waits[i]; + OS::get_singleton()->delay_usec(ms * 1000); + print_verbose("Remote Debugger: Connection failed with status: '" + String::num(tcp_client->get_status()) + "', retrying in " + String::num(ms) + " msec."); + }; + }; + + if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) { + + ERR_PRINT("Remote Debugger: Unable to connect. Status: " + String::num(tcp_client->get_status()) + "."); + return FAILED; + }; + + packet_peer_stream->set_stream_peer(tcp_client); + Array msg; + msg.push_back(OS::get_singleton()->get_process_id()); + send_message("set_pid", msg); + + return OK; +} + +void ScriptDebuggerRemote::_parse_message(const String p_command, const Array &p_data, ScriptLanguage *p_script) { + + if (p_command == "request_video_mem") { + _send_video_memory(); + + } else if (p_command == "start_profiling") { + ERR_FAIL_COND(p_data.size() < 1); + + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->profiling_start(); + } + + max_frame_functions = p_data[0]; + profiler_function_signature_map.clear(); + profiling = true; + frame_time = 0; + idle_time = 0; + physics_time = 0; + physics_frame_time = 0; + print_line("PROFILING ALRIGHT!"); + + } else if (p_command == "stop_profiling") { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->profiling_stop(); + } + profiling = false; + _send_profiling_data(false); + print_line("PROFILING END!"); + + } else if (p_command == "start_visual_profiling") { + + visual_profiling = true; + VS::get_singleton()->set_frame_profiling_enabled(true); + } else if (p_command == "stop_visual_profiling") { + + visual_profiling = false; + VS::get_singleton()->set_frame_profiling_enabled(false); + + } else if (p_command == "start_network_profiling") { + + network_profiling = true; + multiplayer->profiling_start(); + + } else if (p_command == "stop_network_profiling") { + + network_profiling = false; + multiplayer->profiling_end(); + + } else if (p_command == "reload_scripts") { + reload_all_scripts = true; + + } else if (p_command == "breakpoint") { + ERR_FAIL_COND(p_data.size() < 3); + bool set = p_data[2]; + if (set) + insert_breakpoint(p_data[1], p_data[0]); + else + remove_breakpoint(p_data[1], p_data[0]); + + } else if (p_command == "set_skip_breakpoints") { + ERR_FAIL_COND(p_data.size() < 1); + skip_breakpoints = p_data[0]; + + } else if (p_command == "get_stack_dump") { + ERR_FAIL_COND(!p_script); + ScriptStackDump dump; + int slc = p_script->debug_get_stack_level_count(); + for (int i = 0; i < slc; i++) { + ScriptLanguage::StackInfo frame; + frame.file = p_script->debug_get_stack_level_source(i); + frame.line = p_script->debug_get_stack_level_line(i); + frame.func = p_script->debug_get_stack_level_function(i); + dump.frames.push_back(frame); + } + _put_msg("stack_dump", dump.serialize()); + + } else if (p_command == "get_stack_frame_vars") { + ERR_FAIL_COND(p_data.size() != 1); + ERR_FAIL_COND(!p_script); + int lv = p_data[0]; + + List<String> members; + List<Variant> member_vals; + if (ScriptInstance *inst = p_script->debug_get_stack_level_instance(lv)) { + members.push_back("self"); + member_vals.push_back(inst->get_owner()); + } + p_script->debug_get_stack_level_members(lv, &members, &member_vals); + ERR_FAIL_COND(members.size() != member_vals.size()); + + List<String> locals; + List<Variant> local_vals; + p_script->debug_get_stack_level_locals(lv, &locals, &local_vals); + ERR_FAIL_COND(locals.size() != local_vals.size()); + + List<String> globals; + List<Variant> globals_vals; + p_script->debug_get_globals(&globals, &globals_vals); + ERR_FAIL_COND(globals.size() != globals_vals.size()); + + _put_msg("stack_frame_vars", Array()); + + ScriptStackVariable stvar; + { //locals + List<String>::Element *E = locals.front(); + List<Variant>::Element *F = local_vals.front(); + while (E) { + stvar.name = E->get(); + stvar.value = F->get(); + stvar.type = 0; + _put_msg("stack_frame_var", stvar.serialize()); + + E = E->next(); + F = F->next(); + } + } + + { //members + List<String>::Element *E = members.front(); + List<Variant>::Element *F = member_vals.front(); + while (E) { + stvar.name = E->get(); + stvar.value = F->get(); + stvar.type = 1; + _put_msg("stack_frame_var", stvar.serialize()); + + E = E->next(); + F = F->next(); + } + } + + { //globals + List<String>::Element *E = globals.front(); + List<Variant>::Element *F = globals_vals.front(); + while (E) { + stvar.name = E->get(); + stvar.value = F->get(); + stvar.type = 2; + _put_msg("stack_frame_var", stvar.serialize()); + + E = E->next(); + F = F->next(); + } + } + + } else { + if (scene_tree_parse_func) { + scene_tree_parse_func(p_command, p_data); + } + // Unknown message... + } +} + +void ScriptDebuggerRemote::debug(ScriptLanguage *p_script, bool p_can_continue, bool p_is_error_breakpoint) { + + //this function is called when there is a debugger break (bug on script) + //or when execution is paused from editor + + if (skip_breakpoints && !p_is_error_breakpoint) + return; + + ERR_FAIL_COND_MSG(!is_peer_connected(), "Script Debugger failed to connect, but being used anyway."); + + Array msg; + msg.push_back(p_can_continue); + msg.push_back(p_script->debug_get_error()); + _put_msg("debug_enter", msg); + + skip_profile_frame = true; // to avoid super long frame time for the frame + + Input::MouseMode mouse_mode = Input::get_singleton()->get_mouse_mode(); + if (mouse_mode != Input::MOUSE_MODE_VISIBLE) + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); + + uint64_t loop_begin_usec = 0; + uint64_t loop_time_sec = 0; + while (true) { + loop_begin_usec = OS::get_singleton()->get_ticks_usec(); + + _get_output(); + + if (packet_peer_stream->get_available_packet_count() > 0) { + + Variant var; + Error err = packet_peer_stream->get_var(var); + + ERR_CONTINUE(err != OK); + ERR_CONTINUE(var.get_type() != Variant::ARRAY); + + Array cmd = var; + + ERR_CONTINUE(cmd.size() != 2); + ERR_CONTINUE(cmd[0].get_type() != Variant::STRING); + ERR_CONTINUE(cmd[1].get_type() != Variant::ARRAY); + + String command = cmd[0]; + Array data = cmd[1]; + if (command == "step") { + + set_depth(-1); + set_lines_left(1); + break; + } else if (command == "next") { + + set_depth(0); + set_lines_left(1); + break; + + } else if (command == "continue") { + set_depth(-1); + set_lines_left(-1); + OS::get_singleton()->move_window_to_foreground(); + break; + } else if (command == "break") { + ERR_PRINT("Got break when already broke!"); + break; + } + + _parse_message(command, data, p_script); + } else { + OS::get_singleton()->delay_usec(10000); + OS::get_singleton()->process_and_drop_events(); + } + + // This is for the camera override to stay live even when the game is paused from the editor + loop_time_sec = (OS::get_singleton()->get_ticks_usec() - loop_begin_usec) / 1000000.0f; + VisualServer::get_singleton()->sync(); + if (VisualServer::get_singleton()->has_changed()) { + VisualServer::get_singleton()->draw(true, loop_time_sec * Engine::get_singleton()->get_time_scale()); + } + } + + _put_msg("debug_exit", Array()); + + if (mouse_mode != Input::MOUSE_MODE_VISIBLE) + Input::get_singleton()->set_mouse_mode(mouse_mode); +} + +void ScriptDebuggerRemote::_get_output() { + + mutex->lock(); + if (output_strings.size()) { + + locking = true; + + while (output_strings.size()) { + + Array arr; + arr.push_back(output_strings.front()->get()); + _put_msg("output", arr); + output_strings.pop_front(); + } + locking = false; + } + + if (n_messages_dropped > 0) { + Message msg; + msg.message = "Too many messages! " + String::num_int64(n_messages_dropped) + " messages were dropped."; + messages.push_back(msg); + n_messages_dropped = 0; + } + + while (messages.size()) { + locking = true; + Message msg = messages.front()->get(); + _put_msg("message:" + msg.message, msg.data); + messages.pop_front(); + locking = false; + } + + if (n_errors_dropped == 1) { + // Only print one message about dropping per second + OutputError oe; + oe.error = "TOO_MANY_ERRORS"; + oe.error_descr = "Too many errors! Ignoring errors for up to 1 second."; + oe.warning = false; + uint64_t time = OS::get_singleton()->get_ticks_msec(); + oe.hr = time / 3600000; + oe.min = (time / 60000) % 60; + oe.sec = (time / 1000) % 60; + oe.msec = time % 1000; + errors.push_back(oe); + } + + if (n_warnings_dropped == 1) { + // Only print one message about dropping per second + OutputError oe; + oe.error = "TOO_MANY_WARNINGS"; + oe.error_descr = "Too many warnings! Ignoring warnings for up to 1 second."; + oe.warning = true; + uint64_t time = OS::get_singleton()->get_ticks_msec(); + oe.hr = time / 3600000; + oe.min = (time / 60000) % 60; + oe.sec = (time / 1000) % 60; + oe.msec = time % 1000; + errors.push_back(oe); + } + + while (errors.size()) { + locking = true; + OutputError oe = errors.front()->get(); + _put_msg("error", oe.serialize()); + errors.pop_front(); + locking = false; + } + mutex->unlock(); +} + +void ScriptDebuggerRemote::line_poll() { + + //the purpose of this is just processing events every now and then when the script might get too busy + //otherwise bugs like infinite loops can't be caught + if (poll_every % 2048 == 0) + _poll_events(); + poll_every++; +} + +void ScriptDebuggerRemote::_err_handler(void *ud, const char *p_func, const char *p_file, int p_line, const char *p_err, const char *p_descr, ErrorHandlerType p_type) { + + if (p_type == ERR_HANDLER_SCRIPT) + return; //ignore script errors, those go through debugger + + Vector<ScriptLanguage::StackInfo> si; + + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + si = ScriptServer::get_language(i)->debug_get_current_stack_info(); + if (si.size()) + break; + } + + ScriptDebuggerRemote *sdr = (ScriptDebuggerRemote *)ud; + sdr->send_error(p_func, p_file, p_line, p_err, p_descr, p_type, si); +} + +void ScriptDebuggerRemote::_poll_events() { + + //this si called from ::idle_poll, happens only when running the game, + //does not get called while on debug break + + while (packet_peer_stream->get_available_packet_count() > 0) { + + _get_output(); + + //send over output_strings + + Variant var; + Error err = packet_peer_stream->get_var(var); + + ERR_CONTINUE(err != OK); + ERR_CONTINUE(var.get_type() != Variant::ARRAY); + + Array cmd = var; + + ERR_CONTINUE(cmd.size() < 2); + ERR_CONTINUE(cmd[0].get_type() != Variant::STRING); + ERR_CONTINUE(cmd[1].get_type() != Variant::ARRAY); + + String command = cmd[0]; + Array data = cmd[1]; + + if (command == "break") { + + if (get_break_language()) + debug(get_break_language()); + } else { + _parse_message(command, data); + } + } +} + +void ScriptDebuggerRemote::_send_profiling_data(bool p_for_frame) { + + int ofs = 0; + + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + if (p_for_frame) + ofs += ScriptServer::get_language(i)->profiling_get_frame_data(&profile_info.write[ofs], profile_info.size() - ofs); + else + ofs += ScriptServer::get_language(i)->profiling_get_accumulated_data(&profile_info.write[ofs], profile_info.size() - ofs); + } + + for (int i = 0; i < ofs; i++) { + profile_info_ptrs.write[i] = &profile_info.write[i]; + } + + SortArray<ScriptLanguage::ProfilingInfo *, ProfileInfoSort> sa; + sa.sort(profile_info_ptrs.ptrw(), ofs); + + int to_send = MIN(ofs, max_frame_functions); + + //check signatures first + uint64_t total_script_time = 0; + + for (int i = 0; i < to_send; i++) { + + if (!profiler_function_signature_map.has(profile_info_ptrs[i]->signature)) { + + int idx = profiler_function_signature_map.size(); + ProfilerSignature sig; + sig.name = profile_info_ptrs[i]->signature; + sig.id = idx; + _put_msg("profile_sig", sig.serialize()); + profiler_function_signature_map[profile_info_ptrs[i]->signature] = idx; + } + + total_script_time += profile_info_ptrs[i]->self_time; + } + + //send frames then + ProfilerFrame metric; + metric.frame_number = Engine::get_singleton()->get_frames_drawn(); + metric.frame_time = frame_time; + metric.idle_time = idle_time; + metric.physics_time = physics_time; + metric.physics_frame_time = physics_frame_time; + metric.script_time = total_script_time; + + // Add script functions information. + metric.frame_functions.resize(to_send); + FrameFunction *w = metric.frame_functions.ptrw(); + for (int i = 0; i < to_send; i++) { + + if (profiler_function_signature_map.has(profile_info_ptrs[i]->signature)) { + w[i].sig_id = profiler_function_signature_map[profile_info_ptrs[i]->signature]; + } + + w[i].call_count = profile_info_ptrs[i]->call_count; + w[i].total_time = profile_info_ptrs[i]->total_time / 1000000.0; + w[i].self_time = profile_info_ptrs[i]->self_time / 1000000.0; + } + if (p_for_frame) { + // Add profile frame data information. + metric.frames_data.append_array(profile_frame_data); + _put_msg("profile_frame", metric.serialize()); + profile_frame_data.clear(); + } else { + _put_msg("profile_total", metric.serialize()); + } +} + +void ScriptDebuggerRemote::idle_poll() { + + // this function is called every frame, except when there is a debugger break (::debug() in this class) + // execution stops and remains in the ::debug function + + _get_output(); + + if (requested_quit) { + + _put_msg("kill_me", Array()); + requested_quit = false; + } + + if (performance) { + + uint64_t pt = OS::get_singleton()->get_ticks_msec(); + if (pt - last_perf_time > 1000) { + + last_perf_time = pt; + int max = performance->get("MONITOR_MAX"); + Array arr; + arr.resize(max); + for (int i = 0; i < max; i++) { + arr[i] = performance->call("get_monitor", i); + } + _put_msg("performance", arr); + } + } + + if (visual_profiling) { + Vector<VS::FrameProfileArea> profile_areas = VS::get_singleton()->get_frame_profile(); + if (profile_areas.size()) { + Vector<String> area_names; + Vector<real_t> area_times; + area_names.resize(profile_areas.size()); + area_times.resize(profile_areas.size() * 2); + { + String *area_namesw = area_names.ptrw(); + real_t *area_timesw = area_times.ptrw(); + + for (int i = 0; i < profile_areas.size(); i++) { + area_namesw[i] = profile_areas[i].name; + area_timesw[i * 2 + 0] = profile_areas[i].cpu_msec; + area_timesw[i * 2 + 1] = profile_areas[i].gpu_msec; + } + } + Array msg; + msg.push_back(VS::get_singleton()->get_frame_profile_frame()); + msg.push_back(area_names); + msg.push_back(area_times); + _put_msg("visual_profile", msg); + } + } + + if (profiling) { + + if (skip_profile_frame) { + skip_profile_frame = false; + } else { + //send profiling info normally + _send_profiling_data(true); + } + } + + if (network_profiling) { + uint64_t pt = OS::get_singleton()->get_ticks_msec(); + if (pt - last_net_bandwidth_time > 200) { + last_net_bandwidth_time = pt; + _send_network_bandwidth_usage(); + } + if (pt - last_net_prof_time > 100) { + last_net_prof_time = pt; + _send_network_profiling_data(); + } + } + + if (reload_all_scripts) { + + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->reload_all_scripts(); + } + reload_all_scripts = false; + } + + _poll_events(); +} + +void ScriptDebuggerRemote::_send_network_profiling_data() { + ERR_FAIL_COND(multiplayer.is_null()); + + int n_nodes = multiplayer->get_profiling_frame(&network_profile_info.write[0]); + + NetworkProfilerFrame frame; + for (int i = 0; i < n_nodes; i++) { + frame.infos.push_back(network_profile_info[i]); + } + _put_msg("network_profile", frame.serialize()); +} + +void ScriptDebuggerRemote::_send_network_bandwidth_usage() { + ERR_FAIL_COND(multiplayer.is_null()); + + int incoming_bandwidth = multiplayer->get_incoming_bandwidth_usage(); + int outgoing_bandwidth = multiplayer->get_outgoing_bandwidth_usage(); + + Array arr; + arr.push_back(incoming_bandwidth); + arr.push_back(outgoing_bandwidth); + _put_msg("network_bandwidth", arr); +} + +void ScriptDebuggerRemote::send_message(const String &p_message, const Array &p_args) { + + mutex->lock(); + if (!locking && is_peer_connected()) { + + if (messages.size() >= max_messages_per_frame) { + n_messages_dropped++; + } else { + Message msg; + msg.message = p_message; + msg.data = p_args; + messages.push_back(msg); + } + } + mutex->unlock(); +} + +void ScriptDebuggerRemote::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type, const Vector<ScriptLanguage::StackInfo> &p_stack_info) { + + OutputError oe; + oe.error = p_err; + oe.error_descr = p_descr; + oe.source_file = p_file; + oe.source_line = p_line; + oe.source_func = p_func; + oe.warning = p_type == ERR_HANDLER_WARNING; + uint64_t time = OS::get_singleton()->get_ticks_msec(); + oe.hr = time / 3600000; + oe.min = (time / 60000) % 60; + oe.sec = (time / 1000) % 60; + oe.msec = time % 1000; + Array cstack; + + uint64_t ticks = OS::get_singleton()->get_ticks_usec() / 1000; + msec_count += ticks - last_msec; + last_msec = ticks; + + if (msec_count > 1000) { + msec_count = 0; + + err_count = 0; + n_errors_dropped = 0; + warn_count = 0; + n_warnings_dropped = 0; + } + + cstack.resize(p_stack_info.size() * 3); + for (int i = 0; i < p_stack_info.size(); i++) { + cstack[i * 3 + 0] = p_stack_info[i].file; + cstack[i * 3 + 1] = p_stack_info[i].func; + cstack[i * 3 + 2] = p_stack_info[i].line; + } + + //oe.callstack = cstack; + if (oe.warning) { + warn_count++; + } else { + err_count++; + } + + mutex->lock(); + + if (!locking && is_peer_connected()) { + + if (oe.warning) { + if (warn_count > max_warnings_per_second) { + n_warnings_dropped++; + } else { + errors.push_back(oe); + } + } else { + if (err_count > max_errors_per_second) { + n_errors_dropped++; + } else { + errors.push_back(oe); + } + } + } + + mutex->unlock(); +} + +void ScriptDebuggerRemote::_print_handler(void *p_this, const String &p_string, bool p_error) { + + ScriptDebuggerRemote *sdr = (ScriptDebuggerRemote *)p_this; + + uint64_t ticks = OS::get_singleton()->get_ticks_usec() / 1000; + sdr->msec_count += ticks - sdr->last_msec; + sdr->last_msec = ticks; + + if (sdr->msec_count > 1000) { + sdr->char_count = 0; + sdr->msec_count = 0; + } + + String s = p_string; + int allowed_chars = MIN(MAX(sdr->max_cps - sdr->char_count, 0), s.length()); + + if (allowed_chars == 0) + return; + + if (allowed_chars < s.length()) { + s = s.substr(0, allowed_chars); + } + + sdr->char_count += allowed_chars; + bool overflowed = sdr->char_count >= sdr->max_cps; + + sdr->mutex->lock(); + if (!sdr->locking && sdr->is_peer_connected()) { + + if (overflowed) + s += "[...]"; + + sdr->output_strings.push_back(s); + + if (overflowed) { + sdr->output_strings.push_back("[output overflow, print less text!]"); + } + } + sdr->mutex->unlock(); +} + +void ScriptDebuggerRemote::request_quit() { + + requested_quit = true; +} + +void ScriptDebuggerRemote::set_multiplayer(Ref<MultiplayerAPI> p_multiplayer) { + multiplayer = p_multiplayer; +} + +bool ScriptDebuggerRemote::is_profiling() const { + + return profiling; +} +void ScriptDebuggerRemote::add_profiling_frame_data(const StringName &p_name, const Array &p_data) { + + int idx = -1; + for (int i = 0; i < profile_frame_data.size(); i++) { + if (profile_frame_data[i].name == p_name) { + idx = i; + break; + } + } + + FrameData fd; + fd.name = p_name; + fd.data = p_data; + + if (idx == -1) { + profile_frame_data.push_back(fd); + } else { + profile_frame_data.write[idx] = fd; + } +} + +void ScriptDebuggerRemote::profiling_start() { + //ignores this, uses it via connection +} + +void ScriptDebuggerRemote::profiling_end() { + //ignores this, uses it via connection +} + +void ScriptDebuggerRemote::profiling_set_frame_times(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) { + + frame_time = p_frame_time; + idle_time = p_idle_time; + physics_time = p_physics_time; + physics_frame_time = p_physics_frame_time; +} + +void ScriptDebuggerRemote::set_skip_breakpoints(bool p_skip_breakpoints) { + skip_breakpoints = p_skip_breakpoints; +} + +ScriptDebuggerRemote::ResourceUsageFunc ScriptDebuggerRemote::resource_usage_func = NULL; +ScriptDebuggerRemote::ParseMessageFunc ScriptDebuggerRemote::scene_tree_parse_func = NULL; + +ScriptDebuggerRemote::ScriptDebuggerRemote() : + profiling(false), + visual_profiling(false), + network_profiling(false), + max_frame_functions(16), + skip_profile_frame(false), + reload_all_scripts(false), + tcp_client(Ref<StreamPeerTCP>(memnew(StreamPeerTCP))), + packet_peer_stream(Ref<PacketPeerStream>(memnew(PacketPeerStream))), + last_perf_time(0), + last_net_prof_time(0), + last_net_bandwidth_time(0), + performance(Engine::get_singleton()->get_singleton_object("Performance")), + requested_quit(false), + mutex(Mutex::create()), + max_messages_per_frame(GLOBAL_GET("network/limits/debugger_stdout/max_messages_per_frame")), + n_messages_dropped(0), + max_errors_per_second(GLOBAL_GET("network/limits/debugger_stdout/max_errors_per_second")), + max_warnings_per_second(GLOBAL_GET("network/limits/debugger_stdout/max_warnings_per_second")), + n_errors_dropped(0), + max_cps(GLOBAL_GET("network/limits/debugger_stdout/max_chars_per_second")), + char_count(0), + err_count(0), + warn_count(0), + last_msec(0), + msec_count(0), + locking(false), + poll_every(0) { + + packet_peer_stream->set_stream_peer(tcp_client); + packet_peer_stream->set_output_buffer_max_size((1024 * 1024 * 8) - 4); // 8 MiB should be way more than enough, minus 4 bytes for separator. + + phl.printfunc = _print_handler; + phl.userdata = this; + add_print_handler(&phl); + + eh.errfunc = _err_handler; + eh.userdata = this; + add_error_handler(&eh); + + profile_info.resize(GLOBAL_GET("debug/settings/profiler/max_functions")); + network_profile_info.resize(GLOBAL_GET("debug/settings/profiler/max_functions")); + profile_info_ptrs.resize(profile_info.size()); +} + +ScriptDebuggerRemote::~ScriptDebuggerRemote() { + + remove_print_handler(&phl); + remove_error_handler(&eh); + memdelete(mutex); +} diff --git a/scene/debugger/script_debugger_remote.h b/core/script_debugger_remote.h index ae44bf9ca2..682da1d276 100644 --- a/scene/debugger/script_debugger_remote.h +++ b/core/script_debugger_remote.h @@ -37,16 +37,170 @@ #include "core/os/os.h" #include "core/script_language.h" -class SceneTree; - class ScriptDebuggerRemote : public ScriptDebugger { - struct Message { +public: + class ResourceInfo { + public: + String path; + String format; + String type; + RID id; + int vram; + bool operator<(const ResourceInfo &p_img) const { return vram == p_img.vram ? id < p_img.id : vram > p_img.vram; } + ResourceInfo() { + vram = 0; + } + }; + + class ResourceUsage { + public: + List<ResourceInfo> infos; + + Array serialize(); + bool deserialize(const Array &p_arr); + }; + + class FrameInfo { + public: + StringName name; + float self_time; + float total_time; + + FrameInfo() { + self_time = 0; + total_time = 0; + } + }; + + class FrameFunction { + public: + int sig_id; + int call_count; + StringName name; + float self_time; + float total_time; + + FrameFunction() { + sig_id = -1; + call_count = 0; + self_time = 0; + total_time = 0; + } + }; + class ScriptStackVariable { + public: + String name; + Variant value; + int type; + ScriptStackVariable() { + type = -1; + } + + Array serialize(int max_size = 1 << 20); // 1 MiB default. + bool deserialize(const Array &p_arr); + }; + + class ScriptStackDump { + public: + List<ScriptLanguage::StackInfo> frames; + ScriptStackDump() {} + + Array serialize(); + bool deserialize(const Array &p_arr); + }; + + class Message { + + public: String message; Array data; + + Message() {} + }; + + class OutputError { + public: + int hr; + int min; + int sec; + int msec; + String source_file; + String source_func; + int source_line; + String error; + String error_descr; + bool warning; + Vector<ScriptLanguage::StackInfo> callstack; + + OutputError() { + hr = -1; + min = -1; + sec = -1; + msec = -1; + source_line = -1; + warning = false; + } + + Array serialize(); + bool deserialize(const Array &p_arr); + }; + + struct FrameData { + + StringName name; + Array data; + }; + + class ProfilerSignature { + public: + StringName name; + int id; + + Array serialize(); + bool deserialize(const Array &p_arr); + + ProfilerSignature() { + id = -1; + }; + }; + + class ProfilerFrame { + public: + int frame_number; + float frame_time; + float idle_time; + float physics_time; + float physics_frame_time; + float script_time; + + Vector<FrameData> frames_data; + Vector<FrameFunction> frame_functions; + + ProfilerFrame() { + frame_number = 0; + frame_time = 0; + idle_time = 0; + physics_time = 0; + physics_frame_time = 0; + } + + Array serialize(); + bool deserialize(const Array &p_arr); }; + class NetworkProfilerFrame { + public: + Vector<MultiplayerAPI::ProfilingInfo> infos; + + Array serialize(); + bool deserialize(const Array &p_arr); + + NetworkProfilerFrame(){}; + }; + +protected: struct ProfileInfoSort { bool operator()(ScriptLanguage::ProfilingInfo *A, ScriptLanguage::ProfilingInfo *B) const { @@ -78,21 +232,6 @@ class ScriptDebuggerRemote : public ScriptDebugger { bool requested_quit; Mutex *mutex; - struct OutputError { - - int hr; - int min; - int sec; - int msec; - String source_file; - String source_func; - int source_line; - String error; - String error_descr; - bool warning; - Array callstack; - }; - List<String> output_strings; List<Message> messages; int max_messages_per_frame; @@ -119,9 +258,7 @@ class ScriptDebuggerRemote : public ScriptDebugger { void _poll_events(); uint32_t poll_every; - SceneTree *scene_tree; - - bool _parse_live_edit(const Array &p_command); + void _parse_message(const String p_command, const Array &p_data, ScriptLanguage *p_script = NULL); void _set_object_property(ObjectID p_id, const String &p_property, const Variant &p_value); @@ -133,40 +270,24 @@ class ScriptDebuggerRemote : public ScriptDebugger { ErrorHandlerList eh; static void _err_handler(void *, const char *, const char *, int p_line, const char *, const char *, ErrorHandlerType p_type); + void _put_msg(String p_message, Array p_data); void _send_profiling_data(bool p_for_frame); void _send_network_profiling_data(); void _send_network_bandwidth_usage(); - struct FrameData { - - StringName name; - Array data; - }; - Vector<FrameData> profile_frame_data; - void _put_variable(const String &p_name, const Variant &p_variable); - - void _save_node(ObjectID id, const String &p_path); - bool skip_breakpoints; public: - struct ResourceUsage { - - String path; - String format; - String type; - RID id; - int vram; - bool operator<(const ResourceUsage &p_img) const { return vram == p_img.vram ? id < p_img.id : vram > p_img.vram; } - }; - - typedef void (*ResourceUsageFunc)(List<ResourceUsage> *); + typedef void (*ResourceUsageFunc)(ResourceUsage *); + typedef Error (*ParseMessageFunc)(const String &p_name, const Array &p_msg); // Returns true if something was found (stopping propagation). static ResourceUsageFunc resource_usage_func; + static ParseMessageFunc scene_tree_parse_func; // Could be made into list, extensible... Error connect_to_host(const String &p_host, uint16_t p_port); + bool is_peer_connected(); virtual void debug(ScriptLanguage *p_script, bool p_can_continue = true, bool p_is_error_breakpoint = false); virtual void idle_poll(); virtual void line_poll(); @@ -188,8 +309,6 @@ public: virtual void set_skip_breakpoints(bool p_skip_breakpoints); - void set_scene_tree(SceneTree *p_scene_tree) { scene_tree = p_scene_tree; }; - ScriptDebuggerRemote(); ~ScriptDebuggerRemote(); }; diff --git a/editor/SCsub b/editor/SCsub index 2b560f68e8..4431166ee6 100644 --- a/editor/SCsub +++ b/editor/SCsub @@ -82,6 +82,7 @@ if env['tools']: SConscript('collada/SCsub') SConscript('doc/SCsub') + SConscript('debugger/SCsub') SConscript('fileserver/SCsub') SConscript('icons/SCsub') SConscript('import/SCsub') diff --git a/editor/debugger/SCsub b/editor/debugger/SCsub new file mode 100644 index 0000000000..2b1e889fb0 --- /dev/null +++ b/editor/debugger/SCsub @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +Import('env') + +env.add_source_files(env.editor_sources, "*.cpp") diff --git a/editor/debugger/editor_debugger_inspector.cpp b/editor/debugger/editor_debugger_inspector.cpp new file mode 100644 index 0000000000..35d7dda3f4 --- /dev/null +++ b/editor/debugger/editor_debugger_inspector.cpp @@ -0,0 +1,277 @@ +/*************************************************************************/ +/* editor_debugger_inspector.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "editor_debugger_inspector.h" + +#include "core/io/marshalls.h" +#include "core/script_debugger_remote.h" +#include "editor/editor_node.h" +#include "scene/debugger/scene_debugger.h" + +bool EditorDebuggerRemoteObject::_set(const StringName &p_name, const Variant &p_value) { + + if (!editable || !prop_values.has(p_name) || String(p_name).begins_with("Constants/")) + return false; + + prop_values[p_name] = p_value; + emit_signal("value_edited", remote_object_id, p_name, p_value); + return true; +} + +bool EditorDebuggerRemoteObject::_get(const StringName &p_name, Variant &r_ret) const { + + if (!prop_values.has(p_name)) + return false; + + r_ret = prop_values[p_name]; + return true; +} + +void EditorDebuggerRemoteObject::_get_property_list(List<PropertyInfo> *p_list) const { + + p_list->clear(); //sorry, no want category + for (const List<PropertyInfo>::Element *E = prop_list.front(); E; E = E->next()) { + p_list->push_back(E->get()); + } +} + +String EditorDebuggerRemoteObject::get_title() { + if (remote_object_id.is_valid()) + return TTR("Remote ") + String(type_name) + ": " + itos(remote_object_id); + else + return "<null>"; +} + +Variant EditorDebuggerRemoteObject::get_variant(const StringName &p_name) { + Variant var; + _get(p_name, var); + return var; +} + +void EditorDebuggerRemoteObject::_bind_methods() { + + ClassDB::bind_method(D_METHOD("get_title"), &EditorDebuggerRemoteObject::get_title); + ClassDB::bind_method(D_METHOD("get_variant"), &EditorDebuggerRemoteObject::get_variant); + ClassDB::bind_method(D_METHOD("clear"), &EditorDebuggerRemoteObject::clear); + ClassDB::bind_method(D_METHOD("get_remote_object_id"), &EditorDebuggerRemoteObject::get_remote_object_id); + + ADD_SIGNAL(MethodInfo("value_edited", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "property"), PropertyInfo("value"))); +} + +EditorDebuggerInspector::EditorDebuggerInspector() { + variables = memnew(EditorDebuggerRemoteObject); + variables->editable = false; +} + +EditorDebuggerInspector::~EditorDebuggerInspector() { + memdelete(variables); +} + +void EditorDebuggerInspector::_bind_methods() { + ClassDB::bind_method(D_METHOD("_object_edited", "name", "value"), &EditorDebuggerInspector::_object_edited); + ClassDB::bind_method(D_METHOD("_object_selected", "id"), &EditorDebuggerInspector::_object_selected); + ADD_SIGNAL(MethodInfo("object_selected", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("object_edited", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property"), PropertyInfo("value"))); + ADD_SIGNAL(MethodInfo("object_property_updated", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property"))); +} + +void EditorDebuggerInspector::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_POSTINITIALIZE: + connect_compat("object_id_selected", this, "_object_selected"); + break; + case NOTIFICATION_ENTER_TREE: + edit(variables); + break; + default: + break; + } +} + +void EditorDebuggerInspector::_object_edited(ObjectID p_id, const String &p_prop, const Variant &p_value) { + + emit_signal("object_edited", p_id, p_prop, p_value); +} + +void EditorDebuggerInspector::_object_selected(ObjectID p_object) { + + emit_signal("object_selected", p_object); +} + +ObjectID EditorDebuggerInspector::add_object(const Array &p_arr) { + EditorDebuggerRemoteObject *debugObj = NULL; + + SceneDebuggerObject obj; + obj.deserialize(p_arr); + ERR_FAIL_COND_V(obj.id.is_null(), ObjectID()); + + if (remote_objects.has(obj.id)) { + debugObj = remote_objects[obj.id]; + } else { + debugObj = memnew(EditorDebuggerRemoteObject); + debugObj->remote_object_id = obj.id; + debugObj->type_name = obj.class_name; + remote_objects[obj.id] = debugObj; + debugObj->connect_compat("value_edited", this, "_object_edited"); + } + + int old_prop_size = debugObj->prop_list.size(); + + debugObj->prop_list.clear(); + int new_props_added = 0; + Set<String> changed; + for (int i = 0; i < obj.properties.size(); i++) { + + PropertyInfo &pinfo = obj.properties[i].first; + Variant &var = obj.properties[i].second; + + if (pinfo.type == Variant::OBJECT) { + if (var.get_type() == Variant::STRING) { + String path = var; + if (path.find("::") != -1) { + // built-in resource + String base_path = path.get_slice("::", 0); + if (ResourceLoader::get_resource_type(base_path) == "PackedScene") { + if (!EditorNode::get_singleton()->is_scene_open(base_path)) { + EditorNode::get_singleton()->load_scene(base_path); + } + } else { + EditorNode::get_singleton()->load_resource(base_path); + } + } + var = ResourceLoader::load(path); + + if (pinfo.hint_string == "Script") { + if (debugObj->get_script() != var) { + debugObj->set_script(REF()); + Ref<Script> script(var); + if (!script.is_null()) { + ScriptInstance *script_instance = script->placeholder_instance_create(debugObj); + debugObj->set_script_and_instance(var, script_instance); + } + } + } + } + } + + //always add the property, since props may have been added or removed + debugObj->prop_list.push_back(pinfo); + + if (!debugObj->prop_values.has(pinfo.name)) { + new_props_added++; + debugObj->prop_values[pinfo.name] = var; + } else { + + if (bool(Variant::evaluate(Variant::OP_NOT_EQUAL, debugObj->prop_values[pinfo.name], var))) { + debugObj->prop_values[pinfo.name] = var; + changed.insert(pinfo.name); + } + } + } + + if (old_prop_size == debugObj->prop_list.size() && new_props_added == 0) { + //only some may have changed, if so, then update those, if exist + for (Set<String>::Element *E = changed.front(); E; E = E->next()) { + emit_signal("object_property_updated", debugObj->remote_object_id, E->get()); + } + } else { + //full update, because props were added or removed + debugObj->update(); + } + return obj.id; +} + +void EditorDebuggerInspector::clear_cache() { + for (Map<ObjectID, EditorDebuggerRemoteObject *>::Element *E = remote_objects.front(); E; E = E->next()) { + EditorNode *editor = EditorNode::get_singleton(); + if (editor->get_editor_history()->get_current() == E->value()->get_instance_id()) { + editor->push_item(NULL); + } + memdelete(E->value()); + } + remote_objects.clear(); +} + +Object *EditorDebuggerInspector::get_object(ObjectID p_id) { + if (remote_objects.has(p_id)) + return remote_objects[p_id]; + return NULL; +} + +void EditorDebuggerInspector::add_stack_variable(const Array &p_array) { + + ScriptDebuggerRemote::ScriptStackVariable var; + var.deserialize(p_array); + String n = var.name; + Variant v = var.value; + + PropertyHint h = PROPERTY_HINT_NONE; + String hs = String(); + + if (v.get_type() == Variant::OBJECT) { + v = Object::cast_to<EncodedObjectAsID>(v)->get_object_id(); + h = PROPERTY_HINT_OBJECT_ID; + hs = "Object"; + } + String type; + switch (var.type) { + case 0: + type = "Locals/"; + break; + case 1: + type = "Members/"; + break; + case 2: + type = "Globals/"; + break; + default: + type = "Unknown/"; + } + + PropertyInfo pinfo; + pinfo.name = type + n; + pinfo.type = v.get_type(); + pinfo.hint = h; + pinfo.hint_string = hs; + + variables->prop_list.push_back(pinfo); + variables->prop_values[type + n] = v; + variables->update(); + edit(variables); +} + +void EditorDebuggerInspector::clear_stack_variables() { + variables->clear(); + variables->update(); +} + +String EditorDebuggerInspector::get_stack_variable(const String &p_var) { + return variables->get_variant(p_var); +} diff --git a/editor/debugger/editor_debugger_inspector.h b/editor/debugger/editor_debugger_inspector.h new file mode 100644 index 0000000000..e1dfbefcf3 --- /dev/null +++ b/editor/debugger/editor_debugger_inspector.h @@ -0,0 +1,98 @@ +/*************************************************************************/ +/* editor_debugger_inspector.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef EDITOR_DEBUGGER_INSPECTOR_H +#define EDITOR_DEBUGGER_INSPECTOR_H +#include "editor/editor_inspector.h" + +class EditorDebuggerRemoteObject : public Object { + + GDCLASS(EditorDebuggerRemoteObject, Object); + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + static void _bind_methods(); + +public: + bool editable = false; + ObjectID remote_object_id; + String type_name; + List<PropertyInfo> prop_list; + Map<StringName, Variant> prop_values; + + ObjectID get_remote_object_id() { return remote_object_id; }; + String get_title(); + + Variant get_variant(const StringName &p_name); + + void clear() { + prop_list.clear(); + prop_values.clear(); + } + + void update() { _change_notify(); } + + EditorDebuggerRemoteObject(){}; +}; + +class EditorDebuggerInspector : public EditorInspector { + + GDCLASS(EditorDebuggerInspector, EditorInspector); + +private: + ObjectID inspected_object_id; + Map<ObjectID, EditorDebuggerRemoteObject *> remote_objects; + EditorDebuggerRemoteObject *variables; + + void _object_selected(ObjectID p_object); + void _object_edited(ObjectID p_id, const String &p_prop, const Variant &p_value); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + EditorDebuggerInspector(); + ~EditorDebuggerInspector(); + + // Remote Object cache + ObjectID add_object(const Array &p_arr); + Object *get_object(ObjectID p_id); + void clear_cache(); + + // Stack Dump variables + String get_stack_variable(const String &p_var); + void add_stack_variable(const Array &p_arr); + void clear_stack_variables(); +}; + +#endif // EDITOR_DEBUGGER_INSPECTOR_H diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp new file mode 100644 index 0000000000..cf600312fe --- /dev/null +++ b/editor/debugger/editor_debugger_node.cpp @@ -0,0 +1,564 @@ +/*************************************************************************/ +/* editor_debugger_node.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "editor_debugger_node.h" + +#include "editor/debugger/editor_debugger_tree.h" +#include "editor/editor_log.h" +#include "editor/editor_node.h" +#include "editor/plugins/script_editor_plugin.h" + +template <typename Func> +void _for_all(EditorDebuggerNode *p_node, const Func &p_func) { + for (int i = 0; i < p_node->get_tab_count(); i++) { + ScriptEditorDebugger *dbg = Object::cast_to<ScriptEditorDebugger>(p_node->get_tab_control(i)); + ERR_FAIL_COND(!dbg); + p_func(dbg); + } +} + +EditorDebuggerNode *EditorDebuggerNode::singleton = NULL; + +EditorDebuggerNode::EditorDebuggerNode() { + if (!singleton) + singleton = this; + server.instance(); + EditorNode *editor = EditorNode::get_singleton(); + set_tab_align(TabAlign::ALIGN_LEFT); + add_style_override("panel", editor->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles")); + add_style_override("tab_fg", editor->get_gui_base()->get_stylebox("DebuggerTabFG", "EditorStyles")); + add_style_override("tab_bg", editor->get_gui_base()->get_stylebox("DebuggerTabBG", "EditorStyles")); + + auto_switch_remote_scene_tree = EDITOR_DEF("debugger/auto_switch_to_remote_scene_tree", false); + _add_debugger("Debugger"); + + // Remote scene tree + remote_scene_tree = memnew(EditorDebuggerTree); + remote_scene_tree->connect_compat("object_selected", this, "_remote_object_requested"); + remote_scene_tree->connect_compat("save_node", this, "_save_node_requested"); + EditorNode::get_singleton()->get_scene_tree_dock()->add_remote_tree_editor(remote_scene_tree); + EditorNode::get_singleton()->get_scene_tree_dock()->connect_compat("remote_tree_selected", this, "request_remote_tree"); + + remote_scene_tree_timeout = EDITOR_DEF("debugger/remote_scene_tree_refresh_interval", 1.0); + inspect_edited_object_timeout = EDITOR_DEF("debugger/remote_inspect_refresh_interval", 0.2); + + editor->get_undo_redo()->set_method_notify_callback(_method_changeds, this); + editor->get_undo_redo()->set_property_notify_callback(_property_changeds, this); + editor->get_pause_button()->connect_compat("pressed", this, "_paused"); +} + +ScriptEditorDebugger *EditorDebuggerNode::_add_debugger(String p_name) { + ScriptEditorDebugger *node = memnew(ScriptEditorDebugger(EditorNode::get_singleton())); + node->set_name(p_name); + int id = get_tab_count(); + node->connect_compat("stop_requested", this, "_debugger_wants_stop", varray(id)); + node->connect_compat("stopped", this, "_debugger_stopped", varray(id)); + node->connect_compat("stack_frame_selected", this, "_stack_frame_selected", varray(id)); + node->connect_compat("error_selected", this, "_error_selected", varray(id)); + node->connect_compat("clear_execution", this, "_clear_execution"); + node->connect_compat("breaked", this, "_breaked", varray(id)); + node->connect_compat("remote_tree_updated", this, "_remote_tree_updated", varray(id)); + node->connect_compat("remote_object_updated", this, "_remote_object_updated", varray(id)); + node->connect_compat("remote_object_property_updated", this, "_remote_object_property_updated", varray(id)); + node->connect_compat("remote_object_requested", this, "_remote_object_requested", varray(id)); + add_child(node); + return node; +} + +void EditorDebuggerNode::_stack_frame_selected(int p_debugger) { + const ScriptEditorDebugger *dbg = get_debugger(p_debugger); + ERR_FAIL_COND(!dbg); + if (dbg != get_current_debugger()) + return; + _text_editor_stack_goto(dbg); +} + +void EditorDebuggerNode::_error_selected(const String &p_file, int p_line, int p_debugger) { + Ref<Script> s = ResourceLoader::load(p_file); + emit_signal("goto_script_line", s, p_line - 1); +} + +void EditorDebuggerNode::_text_editor_stack_goto(const ScriptEditorDebugger *p_debugger) { + const String file = p_debugger->get_stack_script_file(); + if (file.empty()) + return; + stack_script = ResourceLoader::load(file); + const int line = p_debugger->get_stack_script_line() - 1; + emit_signal("goto_script_line", stack_script, line); + emit_signal("set_execution", stack_script, line); + stack_script.unref(); // Why?!? +} + +void EditorDebuggerNode::_bind_methods() { + ClassDB::bind_method("_menu_option", &EditorDebuggerNode::_menu_option); + ClassDB::bind_method("_debugger_stopped", &EditorDebuggerNode::_debugger_stopped); + ClassDB::bind_method("_debugger_wants_stop", &EditorDebuggerNode::_debugger_wants_stop); + ClassDB::bind_method("_debugger_changed", &EditorDebuggerNode::_debugger_changed); + ClassDB::bind_method("_stack_frame_selected", &EditorDebuggerNode::_stack_frame_selected); + ClassDB::bind_method("_error_selected", &EditorDebuggerNode::_error_selected); + ClassDB::bind_method("_clear_execution", &EditorDebuggerNode::_clear_execution); + ClassDB::bind_method("_breaked", &EditorDebuggerNode::_breaked); + ClassDB::bind_method("start", &EditorDebuggerNode::start); + ClassDB::bind_method("stop", &EditorDebuggerNode::stop); + ClassDB::bind_method("_paused", &EditorDebuggerNode::_paused); + ClassDB::bind_method("request_remote_tree", &EditorDebuggerNode::request_remote_tree); + ClassDB::bind_method("_remote_tree_updated", &EditorDebuggerNode::_remote_tree_updated); + ClassDB::bind_method("_remote_object_updated", &EditorDebuggerNode::_remote_object_updated); + ClassDB::bind_method("_remote_object_property_updated", &EditorDebuggerNode::_remote_object_property_updated); + ClassDB::bind_method("_remote_object_requested", &EditorDebuggerNode::_remote_object_requested); + ClassDB::bind_method("_save_node_requested", &EditorDebuggerNode::_save_node_requested); + + // LiveDebug. + ClassDB::bind_method("live_debug_create_node", &EditorDebuggerNode::live_debug_create_node); + ClassDB::bind_method("live_debug_instance_node", &EditorDebuggerNode::live_debug_instance_node); + ClassDB::bind_method("live_debug_remove_node", &EditorDebuggerNode::live_debug_remove_node); + ClassDB::bind_method("live_debug_remove_and_keep_node", &EditorDebuggerNode::live_debug_remove_and_keep_node); + ClassDB::bind_method("live_debug_restore_node", &EditorDebuggerNode::live_debug_restore_node); + ClassDB::bind_method("live_debug_duplicate_node", &EditorDebuggerNode::live_debug_duplicate_node); + ClassDB::bind_method("live_debug_reparent_node", &EditorDebuggerNode::live_debug_reparent_node); + + ADD_SIGNAL(MethodInfo("goto_script_line")); + ADD_SIGNAL(MethodInfo("set_execution", PropertyInfo("script"), PropertyInfo(Variant::INT, "line"))); + ADD_SIGNAL(MethodInfo("clear_execution", PropertyInfo("script"))); + ADD_SIGNAL(MethodInfo("breaked", PropertyInfo(Variant::BOOL, "reallydid"), PropertyInfo(Variant::BOOL, "can_debug"))); +} + +EditorDebuggerRemoteObject *EditorDebuggerNode::get_inspected_remote_object() { + return Object::cast_to<EditorDebuggerRemoteObject>(ObjectDB::get_instance(EditorNode::get_singleton()->get_editor_history()->get_current())); +} + +ScriptEditorDebugger *EditorDebuggerNode::get_debugger(int p_id) const { + return Object::cast_to<ScriptEditorDebugger>(get_tab_control(p_id)); +} + +ScriptEditorDebugger *EditorDebuggerNode::get_current_debugger() const { + return Object::cast_to<ScriptEditorDebugger>(get_tab_control(get_current_tab())); +} + +ScriptEditorDebugger *EditorDebuggerNode::get_default_debugger() const { + return Object::cast_to<ScriptEditorDebugger>(get_tab_control(0)); +} + +Error EditorDebuggerNode::start() { + stop(); + if (EDITOR_GET("run/output/always_open_output_on_play")) { + EditorNode::get_singleton()->make_bottom_panel_item_visible(EditorNode::get_log()); + } else { + EditorNode::get_singleton()->make_bottom_panel_item_visible(this); + } + + int remote_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port"); + const Error err = server->listen(remote_port); + if (err != OK) { + EditorNode::get_log()->add_message(String("Error listening on port ") + itos(remote_port), EditorLog::MSG_TYPE_ERROR); + return err; + } + set_process(true); + EditorNode::get_log()->add_message("--- Debugging process started ---", EditorLog::MSG_TYPE_EDITOR); + return OK; +} + +void EditorDebuggerNode::stop() { + if (server->is_listening()) { + server->stop(); + EditorNode::get_log()->add_message("--- Debugging process stopped ---", EditorLog::MSG_TYPE_EDITOR); + } + // Also close all debugging sessions. + _for_all(this, [&](ScriptEditorDebugger *dbg) { + if (dbg->is_session_active()) + dbg->stop(); + }); + _break_state_changed(); + if (hide_on_stop) { + if (is_visible_in_tree()) + EditorNode::get_singleton()->hide_bottom_panel(); + } + breakpoints.clear(); + set_process(false); +} + +void EditorDebuggerNode::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_POSTINITIALIZE: { + connect_compat("tab_changed", this, "_debugger_changed"); + } break; + case NOTIFICATION_ENTER_TREE: { + EditorNode::get_singleton()->connect_compat("play_pressed", this, "start"); + EditorNode::get_singleton()->connect_compat("stop_pressed", this, "stop"); + } break; + case NOTIFICATION_EXIT_TREE: { + EditorNode::get_singleton()->disconnect_compat("play_pressed", this, "start"); + EditorNode::get_singleton()->disconnect_compat("stop_pressed", this, "stop"); + } break; + default: + break; + } + + if (p_what != NOTIFICATION_PROCESS || !server->is_listening()) + return; + + // Errors and warnings + int error_count = 0; + int warning_count = 0; + _for_all(this, [&](ScriptEditorDebugger *dbg) { + error_count += dbg->get_error_count(); + warning_count += dbg->get_warning_count(); + }); + + if (error_count != last_error_count || warning_count != last_warning_count) { + + _for_all(this, [&](ScriptEditorDebugger *dbg) { + dbg->update_tabs(); + }); + + if (error_count == 0 && warning_count == 0) { + debugger_button->set_text(TTR("Debugger")); + debugger_button->set_icon(Ref<Texture2D>()); + } else { + debugger_button->set_text(TTR("Debugger") + " (" + itos(error_count + warning_count) + ")"); + if (error_count == 0) { + debugger_button->set_icon(get_icon("Warning", "EditorIcons")); + } else { + debugger_button->set_icon(get_icon("Error", "EditorIcons")); + } + } + last_error_count = error_count; + last_warning_count = warning_count; + } + + // Remote scene tree update + remote_scene_tree_timeout -= get_process_delta_time(); + if (remote_scene_tree_timeout < 0) { + remote_scene_tree_timeout = EditorSettings::get_singleton()->get("debugger/remote_scene_tree_refresh_interval"); + if (remote_scene_tree->is_visible_in_tree()) { + get_current_debugger()->request_remote_tree(); + } + } + + // Remote inspector update + inspect_edited_object_timeout -= get_process_delta_time(); + if (inspect_edited_object_timeout < 0) { + inspect_edited_object_timeout = EditorSettings::get_singleton()->get("debugger/remote_inspect_refresh_interval"); + if (EditorDebuggerRemoteObject *obj = get_inspected_remote_object()) { + get_current_debugger()->request_remote_object(obj->remote_object_id); + } + } + + // Take connections. + if (server->is_connection_available()) { + ScriptEditorDebugger *debugger = NULL; + _for_all(this, [&](ScriptEditorDebugger *dbg) { + if (debugger || dbg->is_session_active()) + return; + debugger = dbg; + }); + if (debugger == NULL) { + if (get_tab_count() <= 4) { // Max 4 debugging sessions active. + debugger = _add_debugger("Session " + itos(get_tab_count())); + } else { + // We already have too many sessions, disconnecting new clients to prevent it from hanging. + // (Not keeping a reference to the connection will disconnect it) + server->take_connection(); + return; // Can't add, stop here. + } + } + + EditorNode::get_singleton()->get_pause_button()->set_disabled(false); + // Switch to remote tree view if so desired. + auto_switch_remote_scene_tree = (bool)EditorSettings::get_singleton()->get("debugger/auto_switch_to_remote_scene_tree"); + if (auto_switch_remote_scene_tree) { + EditorNode::get_singleton()->get_scene_tree_dock()->show_remote_tree(); + } + // Good to go. + EditorNode::get_singleton()->get_scene_tree_dock()->show_tab_buttons(); + debugger->set_editor_remote_tree(remote_scene_tree); + debugger->start(server->take_connection()); + // Send breakpoints. + for (Map<Breakpoint, bool>::Element *E = breakpoints.front(); E; E = E->next()) { + const Breakpoint &bp = E->key(); + debugger->set_breakpoint(bp.source, bp.line, E->get()); + } // Will arrive too late, how does the regular run work? + + debugger->update_live_edit_root(); + } +} + +void EditorDebuggerNode::_debugger_stopped(int p_id) { + ScriptEditorDebugger *dbg = get_debugger(p_id); + ERR_FAIL_COND(!dbg); + + bool found = false; + _for_all(this, [&](ScriptEditorDebugger *p_debugger) { + if (p_debugger->is_session_active()) + found = true; + }); + if (!found) { + EditorNode::get_singleton()->get_pause_button()->set_pressed(false); + EditorNode::get_singleton()->get_pause_button()->set_disabled(true); + EditorNode::get_singleton()->get_scene_tree_dock()->hide_remote_tree(); + EditorNode::get_singleton()->get_scene_tree_dock()->hide_tab_buttons(); + EditorNode::get_singleton()->notify_all_debug_sessions_exited(); + } +} + +void EditorDebuggerNode::_debugger_wants_stop(int p_id) { + // Ask editor to kill PID. + int pid = get_debugger(p_id)->get_remote_pid(); + if (pid) + EditorNode::get_singleton()->call_deferred("stop_child_process", pid); +} + +void EditorDebuggerNode::_debugger_changed(int p_tab) { + if (get_inspected_remote_object()) { + // Clear inspected object, you can only inspect objects in selected debugger. + // Hopefully, in the future, we will have one inspector per debugger. + EditorNode::get_singleton()->push_item(NULL); + } + if (remote_scene_tree->is_visible_in_tree()) { + get_current_debugger()->request_remote_tree(); + } + if (get_current_debugger()->is_breaked()) { + _text_editor_stack_goto(get_current_debugger()); + } +} + +void EditorDebuggerNode::set_script_debug_button(MenuButton *p_button) { + script_menu = p_button; + script_menu->set_text(TTR("Debug")); + script_menu->set_switch_on_hover(true); + PopupMenu *p = script_menu->get_popup(); + p->set_hide_on_window_lose_focus(true); + p->add_shortcut(ED_GET_SHORTCUT("debugger/step_into"), DEBUG_STEP); + p->add_shortcut(ED_GET_SHORTCUT("debugger/step_over"), DEBUG_NEXT); + p->add_separator(); + p->add_shortcut(ED_GET_SHORTCUT("debugger/break"), DEBUG_BREAK); + p->add_shortcut(ED_GET_SHORTCUT("debugger/continue"), DEBUG_CONTINUE); + p->add_separator(); + p->add_check_shortcut(ED_GET_SHORTCUT("debugger/keep_debugger_open"), DEBUG_SHOW_KEEP_OPEN); + p->add_check_shortcut(ED_GET_SHORTCUT("debugger/debug_with_external_editor"), DEBUG_WITH_EXTERNAL_EDITOR); + p->connect_compat("id_pressed", this, "_menu_option"); + + _break_state_changed(); + script_menu->show(); +} + +void EditorDebuggerNode::_break_state_changed() { + const bool breaked = get_current_debugger()->is_breaked(); + const bool can_debug = get_current_debugger()->is_debuggable(); + if (breaked) // Show debugger. + EditorNode::get_singleton()->make_bottom_panel_item_visible(this); + + // Update script menu. + if (!script_menu) + return; + PopupMenu *p = script_menu->get_popup(); + p->set_item_disabled(p->get_item_index(DEBUG_NEXT), !(breaked && can_debug)); + p->set_item_disabled(p->get_item_index(DEBUG_STEP), !(breaked && can_debug)); + p->set_item_disabled(p->get_item_index(DEBUG_BREAK), breaked); + p->set_item_disabled(p->get_item_index(DEBUG_CONTINUE), !breaked); +} + +void EditorDebuggerNode::_menu_option(int p_id) { + switch (p_id) { + case DEBUG_NEXT: { + debug_next(); + } break; + case DEBUG_STEP: { + debug_step(); + } break; + case DEBUG_BREAK: { + debug_break(); + } break; + case DEBUG_CONTINUE: { + debug_continue(); + } break; + + case DEBUG_SHOW_KEEP_OPEN: { + bool visible = script_menu->get_popup()->is_item_checked(script_menu->get_popup()->get_item_index(DEBUG_SHOW_KEEP_OPEN)); + hide_on_stop = visible; + script_menu->get_popup()->set_item_checked(script_menu->get_popup()->get_item_index(DEBUG_SHOW_KEEP_OPEN), !visible); + } break; + case DEBUG_WITH_EXTERNAL_EDITOR: { + bool checked = !script_menu->get_popup()->is_item_checked(script_menu->get_popup()->get_item_index(DEBUG_WITH_EXTERNAL_EDITOR)); + debug_with_external_editor = checked; + script_menu->get_popup()->set_item_checked(script_menu->get_popup()->get_item_index(DEBUG_WITH_EXTERNAL_EDITOR), checked); + } break; + } +} + +void EditorDebuggerNode::_paused() { + const bool paused = EditorNode::get_singleton()->get_pause_button()->is_pressed(); + _for_all(this, [&](ScriptEditorDebugger *dbg) { + if (paused && !dbg->is_breaked()) { + dbg->debug_break(); + } else if (!paused && dbg->is_breaked()) { + dbg->debug_continue(); + } + }); +} + +void EditorDebuggerNode::_breaked(bool p_breaked, bool p_can_debug, int p_debugger) { + if (get_current_debugger() != get_debugger(p_debugger)) { + if (!p_breaked) + return; + set_current_tab(p_debugger); + } + _break_state_changed(); + EditorNode::get_singleton()->get_pause_button()->set_pressed(p_breaked); + emit_signal("breaked", p_breaked, p_can_debug); +} + +bool EditorDebuggerNode::is_skip_breakpoints() const { + return get_default_debugger()->is_skip_breakpoints(); +} + +void EditorDebuggerNode::set_breakpoint(const String &p_path, int p_line, bool p_enabled) { + breakpoints[Breakpoint(p_path, p_line)] = p_enabled; + _for_all(this, [&](ScriptEditorDebugger *dbg) { + dbg->set_breakpoint(p_path, p_line, p_enabled); + }); +} + +void EditorDebuggerNode::reload_scripts() { + _for_all(this, [&](ScriptEditorDebugger *dbg) { + dbg->reload_scripts(); + }); +} + +// LiveEdit/Inspector +void EditorDebuggerNode::request_remote_tree() { + get_current_debugger()->request_remote_tree(); +} + +void EditorDebuggerNode::_remote_tree_updated(int p_debugger) { + if (p_debugger != get_current_tab()) + return; + remote_scene_tree->clear(); + remote_scene_tree->update_scene_tree(get_current_debugger()->get_remote_tree(), p_debugger); +} + +void EditorDebuggerNode::_remote_object_updated(ObjectID p_id, int p_debugger) { + if (p_debugger != get_current_tab()) + return; + if (EditorDebuggerRemoteObject *obj = get_inspected_remote_object()) { + if (obj->remote_object_id == p_id) + return; // Already being edited + } + + EditorNode::get_singleton()->push_item(get_current_debugger()->get_remote_object(p_id)); +} + +void EditorDebuggerNode::_remote_object_property_updated(ObjectID p_id, const String &p_property, int p_debugger) { + if (p_debugger != get_current_tab()) + return; + if (EditorDebuggerRemoteObject *obj = get_inspected_remote_object()) { + if (obj->remote_object_id != p_id) + return; + EditorNode::get_singleton()->get_inspector()->update_property(p_property); + } +} + +void EditorDebuggerNode::_remote_object_requested(ObjectID p_id, int p_debugger) { + if (p_debugger != get_current_tab()) + return; + inspect_edited_object_timeout = 0.7; // Temporarily disable timeout to avoid multiple requests. + get_current_debugger()->request_remote_object(p_id); +} + +void EditorDebuggerNode::_save_node_requested(ObjectID p_id, const String &p_file, int p_debugger) { + if (p_debugger != get_current_tab()) + return; + get_current_debugger()->save_node(p_id, p_file); +} + +// Remote inspector/edit. +void EditorDebuggerNode::_method_changeds(void *p_ud, Object *p_base, const StringName &p_name, VARIANT_ARG_DECLARE) { + if (!singleton) + return; + _for_all(singleton, [&](ScriptEditorDebugger *dbg) { + dbg->_method_changed(p_base, p_name, VARIANT_ARG_PASS); + }); +} + +void EditorDebuggerNode::_property_changeds(void *p_ud, Object *p_base, const StringName &p_property, const Variant &p_value) { + if (!singleton) + return; + _for_all(singleton, [&](ScriptEditorDebugger *dbg) { + dbg->_property_changed(p_base, p_property, p_value); + }); +} + +// LiveDebug +void EditorDebuggerNode::set_live_debugging(bool p_enabled) { + + _for_all(this, [&](ScriptEditorDebugger *dbg) { + dbg->set_live_debugging(p_enabled); + }); +} +void EditorDebuggerNode::update_live_edit_root() { + _for_all(this, [&](ScriptEditorDebugger *dbg) { + dbg->update_live_edit_root(); + }); +} +void EditorDebuggerNode::live_debug_create_node(const NodePath &p_parent, const String &p_type, const String &p_name) { + _for_all(this, [&](ScriptEditorDebugger *dbg) { + dbg->live_debug_create_node(p_parent, p_type, p_name); + }); +} +void EditorDebuggerNode::live_debug_instance_node(const NodePath &p_parent, const String &p_path, const String &p_name) { + _for_all(this, [&](ScriptEditorDebugger *dbg) { + dbg->live_debug_instance_node(p_parent, p_path, p_name); + }); +} +void EditorDebuggerNode::live_debug_remove_node(const NodePath &p_at) { + _for_all(this, [&](ScriptEditorDebugger *dbg) { + dbg->live_debug_remove_node(p_at); + }); +} +void EditorDebuggerNode::live_debug_remove_and_keep_node(const NodePath &p_at, ObjectID p_keep_id) { + _for_all(this, [&](ScriptEditorDebugger *dbg) { + dbg->live_debug_remove_and_keep_node(p_at, p_keep_id); + }); +} +void EditorDebuggerNode::live_debug_restore_node(ObjectID p_id, const NodePath &p_at, int p_at_pos) { + _for_all(this, [&](ScriptEditorDebugger *dbg) { + dbg->live_debug_restore_node(p_id, p_at, p_at_pos); + }); +} +void EditorDebuggerNode::live_debug_duplicate_node(const NodePath &p_at, const String &p_new_name) { + _for_all(this, [&](ScriptEditorDebugger *dbg) { + dbg->live_debug_duplicate_node(p_at, p_new_name); + }); +} +void EditorDebuggerNode::live_debug_reparent_node(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos) { + _for_all(this, [&](ScriptEditorDebugger *dbg) { + dbg->live_debug_reparent_node(p_at, p_new_place, p_new_name, p_at_pos); + }); +} diff --git a/editor/debugger/editor_debugger_node.h b/editor/debugger/editor_debugger_node.h new file mode 100644 index 0000000000..df833d4f60 --- /dev/null +++ b/editor/debugger/editor_debugger_node.h @@ -0,0 +1,176 @@ +/*************************************************************************/ +/* editor_debugger_node.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef EDITOR_DEBUGGER_NODE_H +#define EDITOR_DEBUGGER_NODE_H + +#include "core/io/tcp_server.h" +#include "editor/debugger/script_editor_debugger.h" +#include "scene/gui/button.h" +#include "scene/gui/tab_container.h" + +class EditorDebuggerTree; + +class EditorDebuggerNode : public TabContainer { + + GDCLASS(EditorDebuggerNode, TabContainer); + +private: + enum Options { + DEBUG_NEXT, + DEBUG_STEP, + DEBUG_BREAK, + DEBUG_CONTINUE, + DEBUG_SHOW_KEEP_OPEN, + DEBUG_WITH_EXTERNAL_EDITOR, + }; + + class Breakpoint { + public: + String source; + int line = 0; + + bool operator<(const Breakpoint &p_b) const { + if (line == p_b.line) + return line < p_b.line; + return line < p_b.line; + } + + Breakpoint(){}; + + Breakpoint(const String &p_source, int p_line) { + line = p_line; + source = p_source; + } + }; + + Ref<TCP_Server> server = NULL; + Button *debugger_button = NULL; + MenuButton *script_menu = NULL; + + Ref<Script> stack_script; // Why?!? + + int last_error_count = 0; + int last_warning_count = 0; + + float inspect_edited_object_timeout = 0; + EditorDebuggerTree *remote_scene_tree = NULL; + float remote_scene_tree_timeout = 0.0; + bool auto_switch_remote_scene_tree = false; + bool debug_with_external_editor = false; + bool hide_on_stop = true; + ScriptEditorDebugger::CameraOverride camera_override = ScriptEditorDebugger::OVERRIDE_NONE; + Map<Breakpoint, bool> breakpoints; + + ScriptEditorDebugger *_add_debugger(String p_name); + EditorDebuggerRemoteObject *get_inspected_remote_object(); + + friend class DebuggerEditorPlugin; + static EditorDebuggerNode *singleton; + EditorDebuggerNode(); + +protected: + void _debugger_stopped(int p_id); + void _debugger_wants_stop(int p_id); + void _debugger_changed(int p_tab); + void _remote_tree_updated(int p_debugger); + void _remote_object_updated(ObjectID p_id, int p_debugger); + void _remote_object_property_updated(ObjectID p_id, const String &p_property, int p_debugger); + void _remote_object_requested(ObjectID p_id, int p_debugger); + void _save_node_requested(ObjectID p_id, const String &p_file, int p_debugger); + + void _clear_execution(REF p_script) { + emit_signal("clear_execution", p_script); + } + + void _text_editor_stack_goto(const ScriptEditorDebugger *p_debugger); + void _stack_frame_selected(int p_debugger); + void _error_selected(const String &p_file, int p_line, int p_debugger); + void _breaked(bool p_breaked, bool p_can_debug, int p_debugger); + void _paused(); + void _break_state_changed(); + void _menu_option(int p_id); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + static EditorDebuggerNode *get_singleton() { return singleton; } + + ScriptEditorDebugger *get_current_debugger() const; + ScriptEditorDebugger *get_default_debugger() const; + ScriptEditorDebugger *get_debugger(int p_debugger) const; + + void debug_next() { get_default_debugger()->debug_next(); } + void debug_step() { get_default_debugger()->debug_step(); } + void debug_break() { get_default_debugger()->debug_break(); } + void debug_continue() { get_default_debugger()->debug_continue(); } + + void set_script_debug_button(MenuButton *p_button); + + void set_tool_button(Button *p_button) { + debugger_button = p_button; + } + + String get_var_value(const String &p_var) const { return get_default_debugger()->get_var_value(p_var); } + Ref<Script> get_dump_stack_script() const { return stack_script; } // Why do we need this? + + bool get_debug_with_external_editor() { return debug_with_external_editor; } + + bool is_skip_breakpoints() const; + void set_breakpoint(const String &p_path, int p_line, bool p_enabled); + void reload_scripts(); + + // Remote inspector/edit. + void request_remote_tree(); + static void _method_changeds(void *p_ud, Object *p_base, const StringName &p_name, VARIANT_ARG_DECLARE); + static void _property_changeds(void *p_ud, Object *p_base, const StringName &p_property, const Variant &p_value); + + // LiveDebug + void set_live_debugging(bool p_enabled); + void update_live_edit_root(); + void live_debug_create_node(const NodePath &p_parent, const String &p_type, const String &p_name); + void live_debug_instance_node(const NodePath &p_parent, const String &p_path, const String &p_name); + void live_debug_remove_node(const NodePath &p_at); + void live_debug_remove_and_keep_node(const NodePath &p_at, ObjectID p_keep_id); + void live_debug_restore_node(ObjectID p_id, const NodePath &p_at, int p_at_pos); + void live_debug_duplicate_node(const NodePath &p_at, const String &p_new_name); + void live_debug_reparent_node(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos); + + // Camera + void set_camera_override(ScriptEditorDebugger::CameraOverride p_override) { camera_override = p_override; } + ScriptEditorDebugger::CameraOverride get_camera_override() { return camera_override; } + + Error start(); + + void stop(); +}; +#endif // EDITOR_DEBUGGER_NODE_H diff --git a/editor/debugger/editor_debugger_tree.cpp b/editor/debugger/editor_debugger_tree.cpp new file mode 100644 index 0000000000..9ba5d0cbe1 --- /dev/null +++ b/editor/debugger/editor_debugger_tree.cpp @@ -0,0 +1,274 @@ +/*************************************************************************/ +/* editor_debugger_tree.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "editor_debugger_tree.h" + +#include "editor/editor_node.h" +#include "scene/debugger/scene_debugger.h" +#include "scene/resources/packed_scene.h" + +EditorDebuggerTree::EditorDebuggerTree() { + set_v_size_flags(SIZE_EXPAND_FILL); + set_allow_rmb_select(true); + + // Popup + item_menu = memnew(PopupMenu); + item_menu->connect_compat("id_pressed", this, "_item_menu_id_pressed"); + add_child(item_menu); + + // File Dialog + file_dialog = memnew(EditorFileDialog); + file_dialog->connect_compat("file_selected", this, "_file_selected"); + add_child(file_dialog); +} + +void EditorDebuggerTree::_notification(int p_what) { + if (p_what == NOTIFICATION_POSTINITIALIZE) { + connect_compat("cell_selected", this, "_scene_tree_selected"); + connect_compat("item_collapsed", this, "_scene_tree_folded"); + connect_compat("item_rmb_selected", this, "_scene_tree_rmb_selected"); + } +} + +void EditorDebuggerTree::_bind_methods() { + ClassDB::bind_method(D_METHOD("_scene_tree_selected"), &EditorDebuggerTree::_scene_tree_selected); + ClassDB::bind_method(D_METHOD("_scene_tree_folded"), &EditorDebuggerTree::_scene_tree_folded); + ClassDB::bind_method(D_METHOD("_scene_tree_rmb_selected"), &EditorDebuggerTree::_scene_tree_rmb_selected); + ClassDB::bind_method(D_METHOD("_item_menu_id_pressed"), &EditorDebuggerTree::_item_menu_id_pressed); + ClassDB::bind_method(D_METHOD("_file_selected"), &EditorDebuggerTree::_file_selected); + ADD_SIGNAL(MethodInfo("object_selected", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::INT, "debugger"))); + ADD_SIGNAL(MethodInfo("save_node", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "filename"), PropertyInfo(Variant::INT, "debugger"))); +} + +void EditorDebuggerTree::_scene_tree_selected() { + + if (updating_scene_tree) { + return; + } + + TreeItem *item = get_selected(); + if (!item) { + return; + } + + inspected_object_id = uint64_t(item->get_metadata(0)); + + emit_signal("object_selected", inspected_object_id, debugger_id); +} + +void EditorDebuggerTree::_scene_tree_folded(Object *p_obj) { + + if (updating_scene_tree) { + + return; + } + TreeItem *item = Object::cast_to<TreeItem>(p_obj); + + if (!item) + return; + + ObjectID id = ObjectID(uint64_t(item->get_metadata(0))); + if (unfold_cache.has(id)) { + unfold_cache.erase(id); + } else { + unfold_cache.insert(id); + } +} + +void EditorDebuggerTree::_scene_tree_rmb_selected(const Vector2 &p_position) { + + TreeItem *item = get_item_at_position(p_position); + if (!item) + return; + + item->select(0); + + item_menu->clear(); + item_menu->add_icon_item(get_icon("CreateNewSceneFrom", "EditorIcons"), TTR("Save Branch as Scene"), ITEM_MENU_SAVE_REMOTE_NODE); + item_menu->add_icon_item(get_icon("CopyNodePath", "EditorIcons"), TTR("Copy Node Path"), ITEM_MENU_COPY_NODE_PATH); + item_menu->set_global_position(get_global_mouse_position()); + item_menu->popup(); +} + +/// Populates inspect_scene_tree given data in nodes as a flat list, encoded depth first. +/// +/// Given a nodes array like [R,A,B,C,D,E] the following Tree will be generated, assuming +/// filter is an empty String, R and A child count are 2, B is 1 and C, D and E are 0. +/// +/// R +/// |-A +/// | |-B +/// | | |-C +/// | | +/// | |-D +/// | +/// |-E +/// +void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int p_debugger) { + updating_scene_tree = true; + const String last_path = get_selected_path(); + const String filter = EditorNode::get_singleton()->get_scene_tree_dock()->get_filter(); + + // Nodes are in a flatten list, depth first. Use a stack of parents, avoid recursion. + List<Pair<TreeItem *, int> > parents; + for (int i = 0; i < p_tree->nodes.size(); i++) { + TreeItem *parent = NULL; + if (parents.size()) { // Find last parent. + Pair<TreeItem *, int> &p = parents[0]; + parent = p.first; + if (!(--p.second)) { // If no child left, remove it. + parents.pop_front(); + } + } + // Add this node. + const SceneDebuggerTree::RemoteNode &node = p_tree->nodes[i]; + TreeItem *item = create_item(parent); + item->set_text(0, node.name); + item->set_tooltip(0, TTR("Type:") + " " + node.type_name); + Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(node.type_name, ""); + if (icon.is_valid()) { + item->set_icon(0, icon); + } + item->set_metadata(0, node.id); + + // Set current item as collapsed if necessary (root is never collapsed) + if (parent) { + if (!unfold_cache.has(node.id)) { + item->set_collapsed(true); + } + } + // Select previously selected node. + if (debugger_id == p_debugger) { // Can use remote id. + if (node.id == inspected_object_id) { + item->select(0); + } + } else { // Must use path + if (last_path == _get_path(item)) { + updating_scene_tree = false; // Force emission of new selection + item->select(0); + updating_scene_tree = true; + } + } + + // Add in front of the parents stack if children are expected. + if (node.child_count) { + parents.push_front(Pair<TreeItem *, int>(item, node.child_count)); + } else { + // Apply filters. + while (parent) { + const bool had_siblings = item->get_prev() || item->get_next(); + if (filter.is_subsequence_ofi(item->get_text(0))) + break; // Filter matches, must survive. + parent->remove_child(item); + memdelete(item); + if (had_siblings) + break; // Parent must survive. + item = parent; + parent = item->get_parent(); + // Check if parent expects more children. + for (int j = 0; j < parents.size(); j++) { + if (parents[j].first == item) { + parent = NULL; + break; // Might have more children. + } + } + } + } + } + debugger_id = p_debugger; // Needed by hook, could be avoided if every debugger had its own tree + updating_scene_tree = false; +} + +String EditorDebuggerTree::get_selected_path() { + if (!get_selected()) + return ""; + return _get_path(get_selected()); +} + +String EditorDebuggerTree::_get_path(TreeItem *p_item) { + ERR_FAIL_COND_V(!p_item, ""); + + if (p_item->get_parent() == NULL) { + return "/root"; + } + String text = p_item->get_text(0); + TreeItem *cur = p_item->get_parent(); + while (cur) { + text = cur->get_text(0) + "/" + text; + cur = cur->get_parent(); + } + return "/" + text; +} + +void EditorDebuggerTree::_item_menu_id_pressed(int p_option) { + + switch (p_option) { + + case ITEM_MENU_SAVE_REMOTE_NODE: { + + file_dialog->set_access(EditorFileDialog::ACCESS_RESOURCES); + file_dialog->set_mode(EditorFileDialog::MODE_SAVE_FILE); + + List<String> extensions; + Ref<PackedScene> sd = memnew(PackedScene); + ResourceSaver::get_recognized_extensions(sd, &extensions); + file_dialog->clear_filters(); + for (int i = 0; i < extensions.size(); i++) { + file_dialog->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); + } + + file_dialog->popup_centered_ratio(); + } break; + case ITEM_MENU_COPY_NODE_PATH: { + + String text = get_selected_path(); + if (text.empty()) { + return; + } else if (text == "/root") { + text = "."; + } else { + text = text.replace("/root/", ""); + int slash = text.find("/"); + if (slash < 0) { + text = "."; + } else { + text = text.substr(slash + 1); + } + } + OS::get_singleton()->set_clipboard(text); + } break; + } +} + +void EditorDebuggerTree::_file_selected(const String &p_file) { + if (inspected_object_id.is_null()) + return; + emit_signal("save_node", inspected_object_id, p_file, debugger_id); +} diff --git a/editor/debugger/editor_debugger_tree.h b/editor/debugger/editor_debugger_tree.h new file mode 100644 index 0000000000..d9084bc596 --- /dev/null +++ b/editor/debugger/editor_debugger_tree.h @@ -0,0 +1,74 @@ +/*************************************************************************/ +/* editor_debugger_tree.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene/gui/tree.h" + +#ifndef EDITOR_DEBUGGER_TREE_H +#define EDITOR_DEBUGGER_TREE_H + +class SceneDebuggerTree; +class EditorFileDialog; + +class EditorDebuggerTree : public Tree { + + GDCLASS(EditorDebuggerTree, Tree); + +private: + enum ItemMenu { + ITEM_MENU_SAVE_REMOTE_NODE, + ITEM_MENU_COPY_NODE_PATH, + }; + + ObjectID inspected_object_id; + int debugger_id = 0; + bool updating_scene_tree = false; + Set<ObjectID> unfold_cache; + PopupMenu *item_menu = NULL; + EditorFileDialog *file_dialog = NULL; + + String _get_path(TreeItem *p_item); + void _scene_tree_folded(Object *p_obj); + void _scene_tree_selected(); + void _scene_tree_rmb_selected(const Vector2 &p_position); + void _item_menu_id_pressed(int p_option); + void _file_selected(const String &p_file); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + String get_selected_path(); + ObjectID get_selected_object(); + int get_current_debugger(); // Would love to have one tree for every debugger. + void update_scene_tree(const SceneDebuggerTree *p_tree, int p_debugger); + EditorDebuggerTree(); +}; +#endif // EDITOR_DEBUGGER_TREE_H diff --git a/editor/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index cc8fc0a3b9..d113fe8718 100644 --- a/editor/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -32,18 +32,20 @@ #include "core/io/marshalls.h" #include "core/project_settings.h" +#include "core/script_debugger_remote.h" #include "core/ustring.h" #include "editor/editor_log.h" #include "editor/editor_network_profiler.h" +#include "editor/editor_node.h" +#include "editor/editor_profiler.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" #include "editor/editor_visual_profiler.h" #include "editor/plugins/canvas_item_editor_plugin.h" #include "editor/plugins/spatial_editor_plugin.h" -#include "editor_node.h" -#include "editor_profiler.h" -#include "editor_scale.h" -#include "editor_settings.h" +#include "editor/property_editor.h" #include "main/performance.h" -#include "property_editor.h" +#include "scene/debugger/scene_debugger.h" #include "scene/gui/dialogs.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" @@ -56,149 +58,14 @@ #include "scene/gui/tree.h" #include "scene/resources/packed_scene.h" -class ScriptEditorDebuggerVariables : public Object { - - GDCLASS(ScriptEditorDebuggerVariables, Object); - - List<PropertyInfo> props; - Map<StringName, Variant> values; - -protected: - bool _set(const StringName &p_name, const Variant &p_value) { - - return false; - } - - bool _get(const StringName &p_name, Variant &r_ret) const { - - if (!values.has(p_name)) - return false; - r_ret = values[p_name]; - return true; - } - void _get_property_list(List<PropertyInfo> *p_list) const { - - for (const List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) - p_list->push_back(E->get()); - } - -public: - void clear() { - - props.clear(); - values.clear(); - } - - String get_var_value(const String &p_var) const { - - for (Map<StringName, Variant>::Element *E = values.front(); E; E = E->next()) { - String v = E->key().operator String().get_slice("/", 1); - if (v == p_var) - return E->get(); - } - - return ""; - } - - void add_property(const String &p_name, const Variant &p_value, const PropertyHint &p_hint, const String p_hint_string) { - - PropertyInfo pinfo; - pinfo.name = p_name; - pinfo.type = p_value.get_type(); - pinfo.hint = p_hint; - pinfo.hint_string = p_hint_string; - props.push_back(pinfo); - values[p_name] = p_value; - } - - void update() { - _change_notify(); - } - - ScriptEditorDebuggerVariables() { - } -}; - -class ScriptEditorDebuggerInspectedObject : public Object { - - GDCLASS(ScriptEditorDebuggerInspectedObject, Object); - -protected: - bool _set(const StringName &p_name, const Variant &p_value) { - - if (!prop_values.has(p_name) || String(p_name).begins_with("Constants/")) - return false; - - prop_values[p_name] = p_value; - emit_signal("value_edited", p_name, p_value); - return true; - } - - bool _get(const StringName &p_name, Variant &r_ret) const { - - if (!prop_values.has(p_name)) - return false; - - r_ret = prop_values[p_name]; - return true; - } - - void _get_property_list(List<PropertyInfo> *p_list) const { - - p_list->clear(); //sorry, no want category - for (const List<PropertyInfo>::Element *E = prop_list.front(); E; E = E->next()) { - p_list->push_back(E->get()); - } - } - - static void _bind_methods() { - - ClassDB::bind_method(D_METHOD("get_title"), &ScriptEditorDebuggerInspectedObject::get_title); - ClassDB::bind_method(D_METHOD("get_variant"), &ScriptEditorDebuggerInspectedObject::get_variant); - ClassDB::bind_method(D_METHOD("clear"), &ScriptEditorDebuggerInspectedObject::clear); - ClassDB::bind_method(D_METHOD("get_remote_object_id"), &ScriptEditorDebuggerInspectedObject::get_remote_object_id); - - ADD_SIGNAL(MethodInfo("value_edited")); - } - -public: - String type_name; - ObjectID remote_object_id; - List<PropertyInfo> prop_list; - Map<StringName, Variant> prop_values; - - ObjectID get_remote_object_id() { - return remote_object_id; - } - - String get_title() { - if (remote_object_id.is_valid()) - return TTR("Remote ") + String(type_name) + ": " + itos(remote_object_id); - else - return "<null>"; - } - Variant get_variant(const StringName &p_name) { - - Variant var; - _get(p_name, var); - return var; - } - - void clear() { - - prop_list.clear(); - prop_values.clear(); - } - void update() { - _change_notify(); - } - void update_single(const char *p_prop) { - _change_notify(p_prop); - } - - ScriptEditorDebuggerInspectedObject() { +void ScriptEditorDebugger::_put_msg(String p_message, Array p_data) { + if (is_session_active()) { + Array msg; + msg.push_back(p_message); + msg.push_back(p_data); + ppeer->put_var(msg); } -}; +} void ScriptEditorDebugger::debug_copy() { String msg = reason->get_text(); @@ -213,285 +80,149 @@ void ScriptEditorDebugger::debug_skip_breakpoints() { else skip_breakpoints->set_icon(get_icon("DebugSkipBreakpointsOff", "EditorIcons")); - if (connection.is_valid()) { - Array msg; - msg.push_back("set_skip_breakpoints"); - msg.push_back(skip_breakpoints_value); - ppeer->put_var(msg); - } + Array msg; + msg.push_back(skip_breakpoints_value); + _put_msg("set_skip_breakpoints", msg); } void ScriptEditorDebugger::debug_next() { ERR_FAIL_COND(!breaked); - ERR_FAIL_COND(connection.is_null()); - ERR_FAIL_COND(!connection->is_connected_to_host()); - Array msg; - msg.push_back("next"); - ppeer->put_var(msg); + + _put_msg("next", Array()); _clear_execution(); - stack_dump->clear(); } void ScriptEditorDebugger::debug_step() { ERR_FAIL_COND(!breaked); - ERR_FAIL_COND(connection.is_null()); - ERR_FAIL_COND(!connection->is_connected_to_host()); - Array msg; - msg.push_back("step"); - ppeer->put_var(msg); + _put_msg("step", Array()); _clear_execution(); - stack_dump->clear(); } void ScriptEditorDebugger::debug_break() { ERR_FAIL_COND(breaked); - ERR_FAIL_COND(connection.is_null()); - ERR_FAIL_COND(!connection->is_connected_to_host()); - Array msg; - msg.push_back("break"); - ppeer->put_var(msg); + _put_msg("break", Array()); } void ScriptEditorDebugger::debug_continue() { ERR_FAIL_COND(!breaked); - ERR_FAIL_COND(connection.is_null()); - ERR_FAIL_COND(!connection->is_connected_to_host()); - OS::get_singleton()->enable_for_stealing_focus(EditorNode::get_singleton()->get_child_process_id()); + // Allow focus stealing only if we actually run this client for security. + if (remote_pid && EditorNode::get_singleton()->has_child_process(remote_pid)) + OS::get_singleton()->enable_for_stealing_focus(remote_pid); - Array msg; _clear_execution(); - msg.push_back("continue"); - ppeer->put_var(msg); + _put_msg("continue", Array()); } -void ScriptEditorDebugger::_scene_tree_folded(Object *obj) { - - if (updating_scene_tree) { - - return; - } - TreeItem *item = Object::cast_to<TreeItem>(obj); - - if (!item) - return; - - ObjectID id = item->get_metadata(0); - if (unfold_cache.has(id)) { - unfold_cache.erase(id); +void ScriptEditorDebugger::update_tabs() { + if (error_count == 0 && warning_count == 0) { + errors_tab->set_name(TTR("Errors")); + tabs->set_tab_icon(errors_tab->get_index(), Ref<Texture2D>()); } else { - unfold_cache.insert(id); + errors_tab->set_name(TTR("Errors") + " (" + itos(error_count + warning_count) + ")"); + if (error_count == 0) { + tabs->set_tab_icon(errors_tab->get_index(), get_icon("Warning", "EditorIcons")); + } else { + tabs->set_tab_icon(errors_tab->get_index(), get_icon("Error", "EditorIcons")); + } } } -void ScriptEditorDebugger::_scene_tree_selected() { +void ScriptEditorDebugger::save_node(ObjectID p_id, const String &p_file) { + Array msg; + msg.push_back(p_id); + msg.push_back(p_file); + _put_msg("save_node", msg); +} - if (updating_scene_tree) { +void ScriptEditorDebugger::_file_selected(const String &p_file) { + Error err; + FileAccessRef file = FileAccess::open(p_file, FileAccess::WRITE, &err); + if (err != OK) { + ERR_PRINT("Failed to open " + p_file); return; } - TreeItem *item = inspect_scene_tree->get_selected(); - if (!item) { + Vector<String> line; + line.resize(Performance::MONITOR_MAX); - return; + // signatures + for (int i = 0; i < Performance::MONITOR_MAX; i++) { + line.write[i] = Performance::get_singleton()->get_monitor_name(Performance::Monitor(i)); } + file->store_csv_line(line); - inspected_object_id = item->get_metadata(0).operator ObjectID(); - - Array msg; - msg.push_back("inspect_object"); - msg.push_back(inspected_object_id); - ppeer->put_var(msg); -} + // values + List<Vector<float> >::Element *E = perf_history.back(); + while (E) { -void ScriptEditorDebugger::_scene_tree_rmb_selected(const Vector2 &p_position) { + Vector<float> &perf_data = E->get(); + for (int i = 0; i < perf_data.size(); i++) { - TreeItem *item = inspect_scene_tree->get_item_at_position(p_position); - if (!item) - return; - - item->select(0); + line.write[i] = String::num_real(perf_data[i]); + } + file->store_csv_line(line); + E = E->prev(); + } + file->store_string("\n"); - item_menu->clear(); - item_menu->add_icon_item(get_icon("CreateNewSceneFrom", "EditorIcons"), TTR("Save Branch as Scene"), ITEM_MENU_SAVE_REMOTE_NODE); - item_menu->add_icon_item(get_icon("CopyNodePath", "EditorIcons"), TTR("Copy Node Path"), ITEM_MENU_COPY_NODE_PATH); - item_menu->set_global_position(get_global_mouse_position()); - item_menu->popup(); + Vector<Vector<String> > profiler_data = profiler->get_data_as_csv(); + for (int i = 0; i < profiler_data.size(); i++) { + file->store_csv_line(profiler_data[i]); + } } -void ScriptEditorDebugger::_file_selected(const String &p_file) { - switch (file_dialog_mode) { - case SAVE_NODE: { - Array msg; - msg.push_back("save_node"); - msg.push_back(inspected_object_id); - msg.push_back(p_file); - ppeer->put_var(msg); - } break; - case SAVE_CSV: { - Error err; - FileAccessRef file = FileAccess::open(p_file, FileAccess::WRITE, &err); +void ScriptEditorDebugger::request_remote_tree() { - if (err != OK) { - ERR_PRINT("Failed to open " + p_file); - return; - } - Vector<String> line; - line.resize(Performance::MONITOR_MAX); - - // signatures - for (int i = 0; i < Performance::MONITOR_MAX; i++) { - line.write[i] = Performance::get_singleton()->get_monitor_name(Performance::Monitor(i)); - } - file->store_csv_line(line); - - // values - List<Vector<float> >::Element *E = perf_history.back(); - while (E) { - - Vector<float> &perf_data = E->get(); - for (int i = 0; i < perf_data.size(); i++) { - - line.write[i] = String::num_real(perf_data[i]); - } - file->store_csv_line(line); - E = E->prev(); - } - file->store_string("\n"); - - Vector<Vector<String> > profiler_data = profiler->get_data_as_csv(); - for (int i = 0; i < profiler_data.size(); i++) { - file->store_csv_line(profiler_data[i]); - } + _put_msg("request_scene_tree", Array()); +} - } break; - } +const SceneDebuggerTree *ScriptEditorDebugger::get_remote_tree() { + return scene_tree; } -void ScriptEditorDebugger::_scene_tree_property_value_edited(const String &p_prop, const Variant &p_value) { +void ScriptEditorDebugger::update_remote_object(ObjectID p_obj_id, const String &p_prop, const Variant &p_value) { Array msg; - msg.push_back("set_object_property"); - msg.push_back(inspected_object_id); + msg.push_back(p_obj_id); msg.push_back(p_prop); msg.push_back(p_value); - ppeer->put_var(msg); - inspect_edited_object_timeout = 0.7; //avoid annoyance, don't request soon after editing + _put_msg("set_object_property", msg); } -void ScriptEditorDebugger::_scene_tree_property_select_object(ObjectID p_object) { +void ScriptEditorDebugger::request_remote_object(ObjectID p_obj_id) { - inspected_object_id = p_object; + ERR_FAIL_COND(p_obj_id.is_null()); Array msg; - msg.push_back("inspect_object"); - msg.push_back(inspected_object_id); - ppeer->put_var(msg); + msg.push_back(p_obj_id); + _put_msg("inspect_object", msg); } -void ScriptEditorDebugger::_scene_tree_request() { - - ERR_FAIL_COND(connection.is_null()); - ERR_FAIL_COND(!connection->is_connected_to_host()); - - Array msg; - msg.push_back("request_scene_tree"); - ppeer->put_var(msg); -} - -/// Populates inspect_scene_tree recursively given data in nodes. -/// Nodes is an array containing 4 elements for each node, it follows this pattern: -/// nodes[i] == number of direct children of this node -/// nodes[i + 1] == node name -/// nodes[i + 2] == node class -/// nodes[i + 3] == node instance id -/// -/// Returns the number of items parsed in nodes from current_index. -/// -/// Given a nodes array like [R,A,B,C,D,E] the following Tree will be generated, assuming -/// filter is an empty String, R and A child count are 2, B is 1 and C, D and E are 0. -/// -/// R -/// |-A -/// | |-B -/// | | |-C -/// | | -/// | |-D -/// | -/// |-E -/// -int ScriptEditorDebugger::_update_scene_tree(TreeItem *parent, const Array &nodes, int current_index) { - String filter = EditorNode::get_singleton()->get_scene_tree_dock()->get_filter(); - String item_text = nodes[current_index + 1]; - String item_type = nodes[current_index + 2]; - bool keep = filter.is_subsequence_ofi(item_text); - - TreeItem *item = inspect_scene_tree->create_item(parent); - item->set_text(0, item_text); - item->set_tooltip(0, TTR("Type:") + " " + item_type); - ObjectID id = nodes[current_index + 3].operator ObjectID(); - Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(nodes[current_index + 2], ""); - if (icon.is_valid()) { - item->set_icon(0, icon); - } - item->set_metadata(0, id); - - if (id == inspected_object_id) { - TreeItem *cti = item->get_parent(); - while (cti) { - cti->set_collapsed(false); - cti = cti->get_parent(); - } - item->select(0); - } - - // Set current item as collapsed if necessary - if (parent) { - if (!unfold_cache.has(id)) { - item->set_collapsed(true); - } - } +Object *ScriptEditorDebugger::get_remote_object(ObjectID p_id) { + return inspector->get_object(p_id); +} - int children_count = nodes[current_index]; - // Tracks the total number of items parsed in nodes, this is used to skips nodes that - // are not direct children of the current node since we can't know in advance the total - // number of children, direct and not, of a node without traversing the nodes array previously. - // Keeping track of this allows us to build our remote scene tree by traversing the node - // array just once. - int items_count = 1; - for (int i = 0; i < children_count; i++) { - // Called for each direct child of item. - // Direct children of current item might not be adjacent so items_count must - // be incremented by the number of items parsed until now, otherwise we would not - // be able to access the next child of the current item. - // items_count is multiplied by 4 since that's the number of elements in the nodes - // array needed to represent a single node. - items_count += _update_scene_tree(item, nodes, current_index + items_count * 4); - } +void ScriptEditorDebugger::_remote_object_selected(ObjectID p_id) { + emit_signal("remote_object_requested", p_id); +} - // If item has not children and should not be kept delete it - if (!keep && !item->get_children() && parent) { - parent->remove_child(item); - memdelete(item); - } +void ScriptEditorDebugger::_remote_object_edited(ObjectID p_id, const String &p_prop, const Variant &p_value) { + update_remote_object(p_id, p_prop, p_value); + request_remote_object(p_id); +} - return items_count; +void ScriptEditorDebugger::_remote_object_property_updated(ObjectID p_id, const String &p_property) { + emit_signal("remote_object_property_updated", p_id, p_property); } void ScriptEditorDebugger::_video_mem_request() { - if (connection.is_null() || !connection->is_connected_to_host()) { - // Video RAM usage is only available while a project is being debugged. - return; - } - - Array msg; - msg.push_back("request_video_mem"); - ppeer->put_var(msg); + _put_msg("request_video_mem", Array()); } Size2 ScriptEditorDebugger::get_minimum_size() const { @@ -504,184 +235,72 @@ Size2 ScriptEditorDebugger::get_minimum_size() const { void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_data) { if (p_msg == "debug_enter") { - Array msg; - msg.push_back("get_stack_dump"); - ppeer->put_var(msg); + + _put_msg("get_stack_dump", Array()); + ERR_FAIL_COND(p_data.size() != 2); bool can_continue = p_data[0]; String error = p_data[1]; - step->set_disabled(!can_continue); - next->set_disabled(!can_continue); - _set_reason_text(error, MESSAGE_ERROR); - copy->set_disabled(false); breaked = true; - dobreak->set_disabled(true); - docontinue->set_disabled(false); + can_debug = can_continue; + _update_buttons_state(); + _set_reason_text(error, MESSAGE_ERROR); emit_signal("breaked", true, can_continue); OS::get_singleton()->move_window_to_foreground(); if (error != "") { tabs->set_current_tab(0); } profiler->set_enabled(false); - EditorNode::get_singleton()->get_pause_button()->set_pressed(true); - EditorNode::get_singleton()->make_bottom_panel_item_visible(this); - _clear_remote_objects(); + inspector->clear_cache(); // Take a chance to force remote objects update. } else if (p_msg == "debug_exit") { breaked = false; + can_debug = false; _clear_execution(); - copy->set_disabled(true); - step->set_disabled(true); - next->set_disabled(true); - reason->set_text(""); - reason->set_tooltip(""); - back->set_disabled(true); - forward->set_disabled(true); - dobreak->set_disabled(false); - docontinue->set_disabled(true); - emit_signal("breaked", false, false, Variant()); + _update_buttons_state(); + _set_reason_text(TTR("Execution resumed."), MESSAGE_SUCCESS); + emit_signal("breaked", false, false); profiler->set_enabled(true); profiler->disable_seeking(); - EditorNode::get_singleton()->get_pause_button()->set_pressed(false); + } else if (p_msg == "message:set_pid") { + + ERR_FAIL_COND(p_data.size() < 1); + remote_pid = p_data[0]; } else if (p_msg == "message: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 == "message:scene_tree") { - inspect_scene_tree->clear(); - Map<int, TreeItem *> lv; - - updating_scene_tree = true; - - _update_scene_tree(NULL, p_data, 0); - - updating_scene_tree = false; - - le_clear->set_disabled(false); - le_set->set_disabled(false); + scene_tree->nodes.clear(); + scene_tree->deserialize(p_data); + emit_signal("remote_tree_updated"); + _update_buttons_state(); } else if (p_msg == "message:inspect_object") { - ScriptEditorDebuggerInspectedObject *debugObj = NULL; - - ObjectID id = p_data[0]; - String type = p_data[1]; - Array properties = p_data[2]; - - if (remote_objects.has(id)) { - debugObj = remote_objects[id]; - } else { - debugObj = memnew(ScriptEditorDebuggerInspectedObject); - debugObj->remote_object_id = id; - debugObj->type_name = type; - remote_objects[id] = debugObj; - debugObj->connect_compat("value_edited", this, "_scene_tree_property_value_edited"); - } - - int old_prop_size = debugObj->prop_list.size(); - - debugObj->prop_list.clear(); - int new_props_added = 0; - Set<String> changed; - for (int i = 0; i < properties.size(); i++) { - - Array prop = properties[i]; - if (prop.size() != 6) - continue; - - PropertyInfo pinfo; - pinfo.name = prop[0]; - pinfo.type = Variant::Type(int(prop[1])); - pinfo.hint = PropertyHint(int(prop[2])); - pinfo.hint_string = prop[3]; - pinfo.usage = PropertyUsageFlags(int(prop[4])); - Variant var = prop[5]; - - if (pinfo.type == Variant::OBJECT) { - if (var.is_zero()) { - var = RES(); - } else if (var.get_type() == Variant::STRING) { - String path = var; - if (path.find("::") != -1) { - // built-in resource - String base_path = path.get_slice("::", 0); - if (ResourceLoader::get_resource_type(base_path) == "PackedScene") { - if (!EditorNode::get_singleton()->is_scene_open(base_path)) { - EditorNode::get_singleton()->load_scene(base_path); - } - } else { - EditorNode::get_singleton()->load_resource(base_path); - } - } - var = ResourceLoader::load(path); - - if (pinfo.hint_string == "Script") { - if (debugObj->get_script() != var) { - debugObj->set_script(REF()); - Ref<Script> script(var); - if (!script.is_null()) { - ScriptInstance *script_instance = script->placeholder_instance_create(debugObj); - debugObj->set_script_and_instance(var, script_instance); - } - } - } - } else if (var.get_type() == Variant::OBJECT) { - if (((Object *)var)->is_class("EncodedObjectAsID")) { - var = Object::cast_to<EncodedObjectAsID>(var)->get_object_id(); - pinfo.type = var.get_type(); - pinfo.hint = PROPERTY_HINT_OBJECT_ID; - pinfo.hint_string = "Object"; - } - } - } - - //always add the property, since props may have been added or removed - debugObj->prop_list.push_back(pinfo); - - if (!debugObj->prop_values.has(pinfo.name)) { - new_props_added++; - debugObj->prop_values[pinfo.name] = var; - } else { - - if (bool(Variant::evaluate(Variant::OP_NOT_EQUAL, debugObj->prop_values[pinfo.name], var))) { - debugObj->prop_values[pinfo.name] = var; - changed.insert(pinfo.name); - } - } - } - - if (editor->get_editor_history()->get_current() != debugObj->get_instance_id()) { - editor->push_item(debugObj, ""); - } else { - - if (old_prop_size == debugObj->prop_list.size() && new_props_added == 0) { - //only some may have changed, if so, then update those, if exist - for (Set<String>::Element *E = changed.front(); E; E = E->next()) { - EditorNode::get_singleton()->get_inspector()->update_property(E->get()); - } - } else { - //full update, because props were added or removed - debugObj->update(); - } - } + ObjectID id = inspector->add_object(p_data); + if (id.is_valid()) + emit_signal("remote_object_updated", id); } else if (p_msg == "message:video_mem") { vmem_tree->clear(); TreeItem *root = vmem_tree->create_item(); + ScriptDebuggerRemote::ResourceUsage usage; + usage.deserialize(p_data); int total = 0; - for (int i = 0; i < p_data.size(); i += 4) { + for (List<ScriptDebuggerRemote::ResourceInfo>::Element *E = usage.infos.front(); E; E = E->next()) { TreeItem *it = vmem_tree->create_item(root); - String type = p_data[i + 1]; - int bytes = p_data[i + 3].operator int(); - it->set_text(0, p_data[i + 0]); //path - it->set_text(1, type); //type - it->set_text(2, p_data[i + 2]); //type - it->set_text(3, String::humanize_size(bytes)); //type + String type = E->get().type; + int bytes = E->get().vram; + it->set_text(0, E->get().path); + it->set_text(1, type); + it->set_text(2, E->get().format); + it->set_text(3, String::humanize_size(bytes)); total += bytes; if (has_icon(type, "EditorIcons")) @@ -693,18 +312,21 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da } else if (p_msg == "stack_dump") { + ScriptDebuggerRemote::ScriptStackDump stack; + stack.deserialize(p_data); + stack_dump->clear(); + inspector->clear_stack_variables(); TreeItem *r = stack_dump->create_item(); - for (int i = 0; i < p_data.size(); i++) { + for (int i = 0; i < stack.frames.size(); i++) { - Dictionary d = p_data[i]; - ERR_CONTINUE(!d.has("function")); - ERR_CONTINUE(!d.has("file")); - ERR_CONTINUE(!d.has("line")); - ERR_CONTINUE(!d.has("id")); TreeItem *s = stack_dump->create_item(r); + Dictionary d; d["frame"] = i; + d["file"] = stack.frames[i].file; + d["function"] = stack.frames[i].func; + d["line"] = stack.frames[i].line; s->set_metadata(0, d); String line = itos(i) + " - " + String(d["file"]) + ":" + itos(d["line"]) + " - at function: " + d["function"]; @@ -715,97 +337,22 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da } } else if (p_msg == "stack_frame_vars") { - variables->clear(); - - int ofs = 0; - int mcount = p_data[ofs]; - ofs++; - for (int i = 0; i < mcount; i++) { + inspector->clear_stack_variables(); - String n = p_data[ofs + i * 2 + 0]; - Variant v = p_data[ofs + i * 2 + 1]; + } else if (p_msg == "stack_frame_var") { - PropertyHint h = PROPERTY_HINT_NONE; - String hs = String(); - - if (v.get_type() == Variant::OBJECT) { - v = Object::cast_to<EncodedObjectAsID>(v)->get_object_id(); - h = PROPERTY_HINT_OBJECT_ID; - hs = "Object"; - } - - variables->add_property("Locals/" + n, v, h, hs); - } - - ofs += mcount * 2; - mcount = p_data[ofs]; - ofs++; - for (int i = 0; i < mcount; i++) { - - String n = p_data[ofs + i * 2 + 0]; - Variant v = p_data[ofs + i * 2 + 1]; - PropertyHint h = PROPERTY_HINT_NONE; - String hs = String(); - - if (v.get_type() == Variant::OBJECT) { - v = Object::cast_to<EncodedObjectAsID>(v)->get_object_id(); - h = PROPERTY_HINT_OBJECT_ID; - hs = "Object"; - } - - variables->add_property("Members/" + n, v, h, hs); - - if (n == "self") { - _scene_tree_property_select_object(v); - } - } - - ofs += mcount * 2; - mcount = p_data[ofs]; - ofs++; - for (int i = 0; i < mcount; i++) { - - String n = p_data[ofs + i * 2 + 0]; - Variant v = p_data[ofs + i * 2 + 1]; - PropertyHint h = PROPERTY_HINT_NONE; - String hs = String(); - - if (v.get_type() == Variant::OBJECT) { - v = Object::cast_to<EncodedObjectAsID>(v)->get_object_id(); - h = PROPERTY_HINT_OBJECT_ID; - hs = "Object"; - } - - variables->add_property("Globals/" + n, v, h, hs); - } - - variables->update(); - inspector->edit(variables); + inspector->add_stack_variable(p_data); } else if (p_msg == "output") { - - //OUT - for (int i = 0; i < p_data.size(); i++) { - - String t = p_data[i]; - //LOG - - if (!EditorNode::get_log()->is_visible()) { - if (EditorNode::get_singleton()->are_bottom_panels_hidden()) { - if (EDITOR_GET("run/output/always_open_output_on_play")) { - EditorNode::get_singleton()->make_bottom_panel_item_visible(EditorNode::get_log()); - } - } - } - EditorNode::get_log()->add_message(t); - } + ERR_FAIL_COND(p_data.size() < 1); + String t = p_data[0]; + EditorNode::get_log()->add_message(t); } else if (p_msg == "performance") { - Array arr = p_data[0]; Vector<float> p; - p.resize(arr.size()); - for (int i = 0; i < arr.size(); i++) { - p.write[i] = arr[i]; + p.resize(p_data.size()); + for (int i = 0; i < p_data.size(); i++) { + p.write[i] = p_data[i]; if (i < perf_items.size()) { const float value = p[i]; @@ -833,7 +380,9 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da } perf_history.push_front(p); perf_draw->update(); + } else if (p_msg == "visual_profile") { + // TODO check me. uint64_t frame = p_data[0]; Vector<String> names = p_data[1]; Vector<real_t> values = p_data[2]; @@ -857,42 +406,29 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da areas_ptr[i].gpu_time = rr[i * 2 + 1]; } } - visual_profiler->add_frame_metric(metric); } else if (p_msg == "error") { - // Should have at least two elements, error array and stack items count. - ERR_FAIL_COND_MSG(p_data.size() < 2, "Malformed error message from script debugger."); - - // Error or warning data. - Array err = p_data[0]; - ERR_FAIL_COND_MSG(err.size() < 10, "Malformed error message from script debugger."); + ScriptDebuggerRemote::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(err[0]); - time_vals.push_back(err[1]); - time_vals.push_back(err[2]); - time_vals.push_back(err[3]); + 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); + String time = String("%d:%02d:%02d:%04d").sprintf(time_vals, &e); // Rest of the error data. - String method = err[4]; - String source_file = err[5]; - String source_line = err[6]; - String error_cond = err[7]; - String error_msg = err[8]; - bool is_warning = err[9]; - bool has_method = !method.empty(); - bool has_error_msg = !error_msg.empty(); - bool source_is_project_file = source_file.begins_with("res://"); + 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(source_file); - source_meta.push_back(source_line); + 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(); @@ -902,40 +438,42 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da // Also provide the relevant details as tooltip to quickly check without // uncollapsing the tree. - String tooltip = is_warning ? TTR("Warning:") : TTR("Error:"); + String tooltip = oe.warning ? TTR("Warning:") : TTR("Error:"); TreeItem *error = error_tree->create_item(r); error->set_collapsed(true); - error->set_icon(0, get_icon(is_warning ? "Warning" : "Error", "EditorIcons")); + error->set_icon(0, get_icon(oe.warning ? "Warning" : "Error", "EditorIcons")); error->set_text(0, time); error->set_text_align(0, TreeItem::ALIGN_LEFT); String error_title; // Include method name, when given, in error title. - if (has_method) - error_title += method + ": "; + if (!oe.source_func.empty()) + 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 += error_msg.empty() ? error_cond : error_msg; + error_title += oe.error_descr.empty() ? oe.error : oe.error_descr; error->set_text(1, error_title); tooltip += " " + error_title + "\n"; - if (has_error_msg) { + if (!oe.error_descr.empty()) { // Add item for C++ error condition. TreeItem *cpp_cond = error_tree->create_item(error); cpp_cond->set_text(0, "<" + TTR("C++ Error") + ">"); - cpp_cond->set_text(1, error_cond); + cpp_cond->set_text(1, oe.error); cpp_cond->set_text_align(0, TreeItem::ALIGN_LEFT); - tooltip += TTR("C++ Error:") + " " + error_cond + "\n"; + tooltip += TTR("C++ Error:") + " " + oe.error + "\n"; if (source_is_project_file) cpp_cond->set_metadata(0, source_meta); } + Vector<uint8_t> v; + v.resize(100); // Source of the error. - String source_txt = (source_is_project_file ? source_file.get_file() : source_file) + ":" + source_line; - if (has_method) - source_txt += " @ " + method + "()"; + String source_txt = (source_is_project_file ? oe.source_file.get_file() : oe.source_file) + ":" + itos(oe.source_line); + if (!oe.source_func.empty()) + source_txt += " @ " + oe.source_func + "()"; TreeItem *cpp_source = error_tree->create_item(error); cpp_source->set_text(0, "<" + (source_is_project_file ? TTR("Source") : TTR("C++ Source")) + ">"); @@ -955,17 +493,14 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da // 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). - int stack_items_count = p_data[1]; + const ScriptLanguage::StackInfo *infos = oe.callstack.ptr(); + for (unsigned int i = 0; i < (unsigned int)oe.callstack.size(); i++) { - for (int i = 0; i < stack_items_count; i += 3) { - String script = p_data[2 + i]; - String method2 = p_data[3 + i]; - int line = p_data[4 + i]; TreeItem *stack_trace = error_tree->create_item(error); Array meta; - meta.push_back(script); - meta.push_back(line); + meta.push_back(infos[i].file); + meta.push_back(infos[i].line); stack_trace->set_metadata(0, meta); if (i == 0) { @@ -973,29 +508,32 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da stack_trace->set_text_align(0, TreeItem::ALIGN_LEFT); error->set_metadata(0, meta); } - stack_trace->set_text(1, script.get_file() + ":" + itos(line) + " @ " + method2 + "()"); + stack_trace->set_text(1, infos[i].file.get_file() + ":" + itos(infos[i].line) + " @ " + infos[i].func + "()"); } - if (is_warning) + if (oe.warning) warning_count++; else error_count++; } else if (p_msg == "profile_sig") { - //cache a signature - profiler_signature[p_data[1]] = p_data[0]; + // Cache a profiler signature. + ScriptDebuggerRemote::ProfilerSignature sig; + sig.deserialize(p_data); + profiler_signature[sig.id] = sig.name; } else if (p_msg == "profile_frame" || p_msg == "profile_total") { - EditorProfiler::Metric metric; + ScriptDebuggerRemote::ProfilerFrame frame; + frame.deserialize(p_data); metric.valid = true; - metric.frame_number = p_data[0]; - metric.frame_time = p_data[1]; - metric.idle_time = p_data[2]; - metric.physics_time = p_data[3]; - metric.physics_frame_time = p_data[4]; - int frame_data_amount = p_data[6]; - int frame_function_amount = p_data[7]; + metric.frame_number = frame.frame_number; + metric.frame_time = frame.frame_time; + metric.idle_time = frame.idle_time; + metric.physics_time = frame.physics_time; + metric.physics_frame_time = frame.physics_frame_time; + int frame_data_amount = frame.frames_data.size(); + int frame_function_amount = frame.frame_functions.size(); if (frame_data_amount) { EditorProfiler::Metric::Category frame_time; @@ -1031,12 +569,11 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da metric.categories.push_back(frame_time); } - int idx = 8; for (int i = 0; i < frame_data_amount; i++) { EditorProfiler::Metric::Category c; - String name = p_data[idx++]; - Array values = p_data[idx++]; + String name = frame.frames_data[i].name; + Array values = frame.frames_data[i].data; c.name = name.capitalize(); c.items.resize(values.size() / 2); c.total_time = 0; @@ -1058,16 +595,16 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da } EditorProfiler::Metric::Category funcs; - funcs.total_time = p_data[5]; //script time + funcs.total_time = frame.script_time; funcs.items.resize(frame_function_amount); funcs.name = "Script Functions"; funcs.signature = "script_functions"; for (int i = 0; i < frame_function_amount; i++) { - int signature = p_data[idx++]; - int calls = p_data[idx++]; - float total = p_data[idx++]; - float self = p_data[idx++]; + int signature = frame.frame_functions[i].sig_id; + int calls = frame.frame_functions[i].call_count; + float total = frame.frame_functions[i].total_time; + float self = frame.frame_functions[i].self_time; EditorProfiler::Metric::Category::Item item; if (profiler_signature.has(signature)) { @@ -1102,23 +639,20 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da profiler->add_frame_metric(metric, false); else profiler->add_frame_metric(metric, true); + } else if (p_msg == "network_profile") { - int frame_size = 6; - for (int i = 0; i < p_data.size(); i += frame_size) { - MultiplayerAPI::ProfilingInfo pi; - pi.node = p_data[i + 0].operator ObjectID(); - pi.node_path = p_data[i + 1]; - pi.incoming_rpc = p_data[i + 2]; - pi.incoming_rset = p_data[i + 3]; - pi.outgoing_rpc = p_data[i + 4]; - pi.outgoing_rset = p_data[i + 5]; - network_profiler->add_node_frame_data(pi); + ScriptDebuggerRemote::NetworkProfilerFrame frame; + frame.deserialize(p_data); + for (int i = 0; i < frame.infos.size(); i++) { + network_profiler->add_node_frame_data(frame.infos[i]); } } else if (p_msg == "network_bandwidth") { + ERR_FAIL_COND(p_data.size() < 2); network_profiler->set_bandwidth(p_data[0], p_data[1]); } else if (p_msg == "kill_me") { - editor->call_deferred("stop_child_process"); + emit_signal("stop_requested"); + _stop_and_notify(); } } @@ -1218,14 +752,11 @@ void ScriptEditorDebugger::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { - inspector->edit(variables); skip_breakpoints->set_icon(get_icon("DebugSkipBreakpointsOff", "EditorIcons")); copy->set_icon(get_icon("ActionCopy", "EditorIcons")); step->set_icon(get_icon("DebugStep", "EditorIcons")); next->set_icon(get_icon("DebugNext", "EditorIcons")); - back->set_icon(get_icon("Back", "EditorIcons")); - forward->set_icon(get_icon("Forward", "EditorIcons")); dobreak->set_icon(get_icon("Pause", "EditorIcons")); docontinue->set_icon(get_icon("DebugContinue", "EditorIcons")); le_set->connect_compat("pressed", this, "_live_edit_set"); @@ -1239,31 +770,7 @@ void ScriptEditorDebugger::_notification(int p_what) { } break; case NOTIFICATION_PROCESS: { - if (connection.is_valid()) { - - inspect_scene_tree_timeout -= get_process_delta_time(); - if (inspect_scene_tree_timeout < 0) { - inspect_scene_tree_timeout = EditorSettings::get_singleton()->get("debugger/remote_scene_tree_refresh_interval"); - if (inspect_scene_tree->is_visible_in_tree()) { - _scene_tree_request(); - } - } - - inspect_edited_object_timeout -= get_process_delta_time(); - if (inspect_edited_object_timeout < 0) { - inspect_edited_object_timeout = EditorSettings::get_singleton()->get("debugger/remote_inspect_refresh_interval"); - if (inspected_object_id.is_valid()) { - if (ScriptEditorDebuggerInspectedObject *obj = Object::cast_to<ScriptEditorDebuggerInspectedObject>(ObjectDB::get_instance(editor->get_editor_history()->get_current()))) { - if (obj->remote_object_id == inspected_object_id) { - //take the chance and re-inspect selected object - Array msg; - msg.push_back("inspect_object"); - msg.push_back(inspected_object_id); - ppeer->put_var(msg); - } - } - } - } + if (is_session_active()) { if (camera_override == OVERRIDE_2D) { CanvasItemEditor *editor = CanvasItemEditor::get_singleton(); @@ -1277,9 +784,8 @@ void ScriptEditorDebugger::_notification(int p_what) { transform.elements[2] = -offset * zoom; Array msg; - msg.push_back("override_camera_2D:transform"); msg.push_back(transform); - ppeer->put_var(msg); + _put_msg("override_camera_2D:transform", msg); } else if (camera_override >= OVERRIDE_3D_1) { int viewport_idx = camera_override - OVERRIDE_3D_1; @@ -1287,7 +793,6 @@ void ScriptEditorDebugger::_notification(int p_what) { Camera *const cam = viewport->get_camera(); Array msg; - msg.push_back("override_camera_3D:transform"); msg.push_back(cam->get_camera_transform()); if (cam->get_projection() == Camera::PROJECTION_ORTHOGONAL) { msg.push_back(false); @@ -1298,86 +803,12 @@ void ScriptEditorDebugger::_notification(int p_what) { } msg.push_back(cam->get_znear()); msg.push_back(cam->get_zfar()); - ppeer->put_var(msg); - } - } - - if (error_count != last_error_count || warning_count != last_warning_count) { - - if (error_count == 0 && warning_count == 0) { - errors_tab->set_name(TTR("Errors")); - debugger_button->set_text(TTR("Debugger")); - debugger_button->set_icon(Ref<Texture2D>()); - tabs->set_tab_icon(errors_tab->get_index(), Ref<Texture2D>()); - } else { - errors_tab->set_name(TTR("Errors") + " (" + itos(error_count + warning_count) + ")"); - debugger_button->set_text(TTR("Debugger") + " (" + itos(error_count + warning_count) + ")"); - if (error_count == 0) { - debugger_button->set_icon(get_icon("Warning", "EditorIcons")); - tabs->set_tab_icon(errors_tab->get_index(), get_icon("Warning", "EditorIcons")); - } else { - debugger_button->set_icon(get_icon("Error", "EditorIcons")); - tabs->set_tab_icon(errors_tab->get_index(), get_icon("Error", "EditorIcons")); - } - } - last_error_count = error_count; - last_warning_count = warning_count; - } - - if (server->is_connection_available()) { - if (connection.is_valid()) { - // We already have a valid connection. Disconnecting any new connecting client to prevent it from hanging. - // (If we don't keep a reference to the connection it will be destroyed and disconnect_from_host will be called internally) - server->take_connection(); - } else { - // We just got the first connection. - connection = server->take_connection(); - if (connection.is_null()) - break; - - EditorNode::get_log()->add_message("--- Debugging process started ---", EditorLog::MSG_TYPE_EDITOR); - - ppeer->set_stream_peer(connection); - - //EditorNode::get_singleton()->make_bottom_panel_item_visible(this); - //emit_signal("show_debugger",true); - - dobreak->set_disabled(false); - tabs->set_current_tab(0); - - _set_reason_text(TTR("Child process connected."), MESSAGE_SUCCESS); - profiler->clear(); - - inspect_scene_tree->clear(); - le_set->set_disabled(true); - le_clear->set_disabled(false); - vmem_refresh->set_disabled(false); - error_tree->clear(); - error_count = 0; - warning_count = 0; - profiler_signature.clear(); - //live_edit_root->set_text("/root"); - - EditorNode::get_singleton()->get_pause_button()->set_pressed(false); - EditorNode::get_singleton()->get_pause_button()->set_disabled(false); - - update_live_edit_root(); - if (profiler->is_profiling()) { - _profiler_activate(true); - } - - if (network_profiler->is_profiling()) { - _network_profiler_activate(true); - } + _put_msg("override_camera_3D:transform", msg); } } - if (connection.is_null()) - break; - - if (!connection->is_connected_to_host()) { - stop(); - editor->notify_child_process_exited(); //somehow, exited + if (!is_session_active()) { + _stop_and_notify(); break; }; @@ -1389,67 +820,22 @@ void ScriptEditorDebugger::_notification(int p_what) { while (ppeer->get_available_packet_count() > 0) { - if (pending_in_queue) { - - int todo = MIN(ppeer->get_available_packet_count(), pending_in_queue); - - for (int i = 0; i < todo; i++) { - - Variant cmd; - Error ret = ppeer->get_var(cmd); - if (ret != OK) { - stop(); - ERR_FAIL_COND(ret != OK); - } - - message.push_back(cmd); - pending_in_queue--; - } - - if (pending_in_queue == 0) { - _parse_message(message_type, message); - message.clear(); - } - - } else { - - if (ppeer->get_available_packet_count() >= 2) { - - Variant cmd; - Error ret = ppeer->get_var(cmd); - if (ret != OK) { - stop(); - ERR_FAIL_COND(ret != OK); - } - if (cmd.get_type() != Variant::STRING) { - stop(); - ERR_FAIL_COND(cmd.get_type() != Variant::STRING); - } - - message_type = cmd; - - ret = ppeer->get_var(cmd); - if (ret != OK) { - stop(); - ERR_FAIL_COND(ret != OK); - } - if (cmd.get_type() != Variant::INT) { - stop(); - ERR_FAIL_COND(cmd.get_type() != Variant::INT); - } - - pending_in_queue = cmd; - - if (pending_in_queue == 0) { - _parse_message(message_type, Array()); - message.clear(); - } - - } else { - - break; - } + Variant cmd; + Error ret = ppeer->get_var(cmd); + if (ret != OK) { + _stop_and_notify(); + ERR_FAIL_MSG("Error decoding variant from peer"); + } + if (cmd.get_type() != Variant::ARRAY) { + _stop_and_notify(); + ERR_FAIL_MSG("Invalid message format received from peer"); } + Array arr = cmd; + if (arr.size() != 2 || arr[0].get_type() != Variant::STRING || arr[1].get_type() != Variant::ARRAY) { + _stop_and_notify(); + ERR_FAIL_MSG("Invalid message format received from peer"); + } + _parse_message(arr[0], arr[1]); if (OS::get_singleton()->get_ticks_msec() > until) break; @@ -1460,15 +846,13 @@ void ScriptEditorDebugger::_notification(int p_what) { add_constant_override("margin_left", -EditorNode::get_singleton()->get_gui_base()->get_stylebox("BottomPanelDebuggerOverride", "EditorStyles")->get_margin(MARGIN_LEFT)); add_constant_override("margin_right", -EditorNode::get_singleton()->get_gui_base()->get_stylebox("BottomPanelDebuggerOverride", "EditorStyles")->get_margin(MARGIN_RIGHT)); - tabs->add_style_override("panel", editor->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles")); - tabs->add_style_override("tab_fg", editor->get_gui_base()->get_stylebox("DebuggerTabFG", "EditorStyles")); - tabs->add_style_override("tab_bg", editor->get_gui_base()->get_stylebox("DebuggerTabBG", "EditorStyles")); + tabs->add_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles")); + tabs->add_style_override("tab_fg", EditorNode::get_singleton()->get_gui_base()->get_stylebox("DebuggerTabFG", "EditorStyles")); + tabs->add_style_override("tab_bg", EditorNode::get_singleton()->get_gui_base()->get_stylebox("DebuggerTabBG", "EditorStyles")); copy->set_icon(get_icon("ActionCopy", "EditorIcons")); step->set_icon(get_icon("DebugStep", "EditorIcons")); next->set_icon(get_icon("DebugNext", "EditorIcons")); - back->set_icon(get_icon("Back", "EditorIcons")); - forward->set_icon(get_icon("Forward", "EditorIcons")); dobreak->set_icon(get_icon("Pause", "EditorIcons")); docontinue->set_icon(get_icon("DebugContinue", "EditorIcons")); vmem_refresh->set_icon(get_icon("Reload", "EditorIcons")); @@ -1486,15 +870,18 @@ void ScriptEditorDebugger::_clear_execution() { stack_script = ResourceLoader::load(d["file"]); emit_signal("clear_execution", stack_script); stack_script.unref(); + stack_dump->clear(); + inspector->clear_stack_variables(); } -void ScriptEditorDebugger::start() { +void ScriptEditorDebugger::start(Ref<StreamPeerTCP> p_connection) { + error_count = 0; + warning_count = 0; stop(); - if (is_visible_in_tree()) { - EditorNode::get_singleton()->make_bottom_panel_item_visible(this); - } + connection = p_connection; + ppeer->set_stream_peer(connection); perf_history.clear(); for (int i = 0; i < Performance::MONITOR_MAX; i++) { @@ -1502,91 +889,81 @@ void ScriptEditorDebugger::start() { perf_max.write[i] = 0; } - int remote_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port"); - if (server->listen(remote_port) != OK) { - EditorNode::get_log()->add_message(String("Error listening on port ") + itos(remote_port), EditorLog::MSG_TYPE_ERROR); - return; - } - - EditorNode::get_singleton()->get_scene_tree_dock()->show_tab_buttons(); - auto_switch_remote_scene_tree = (bool)EditorSettings::get_singleton()->get("debugger/auto_switch_to_remote_scene_tree"); - if (auto_switch_remote_scene_tree) { - EditorNode::get_singleton()->get_scene_tree_dock()->show_remote_tree(); - } - set_process(true); breaked = false; + can_debug = true; camera_override = OVERRIDE_NONE; + + tabs->set_current_tab(0); + _set_reason_text(TTR("Debug session started."), MESSAGE_SUCCESS); + _update_buttons_state(); + + if (profiler->is_profiling()) { + _profiler_activate(true); + } + + if (network_profiler->is_profiling()) { + _network_profiler_activate(true); + } } -void ScriptEditorDebugger::pause() { +void ScriptEditorDebugger::_update_buttons_state() { + const bool active = is_session_active(); + const bool has_editor_tree = active && editor_remote_tree && editor_remote_tree->get_selected(); + vmem_refresh->set_disabled(!active); + step->set_disabled(!active || !breaked || !can_debug); + next->set_disabled(!active || !breaked || !can_debug); + copy->set_disabled(!active || !breaked); + docontinue->set_disabled(!active || !breaked); + dobreak->set_disabled(!active || breaked); + le_clear->set_disabled(!active); + le_set->set_disabled(!has_editor_tree); } -void ScriptEditorDebugger::unpause() { +void ScriptEditorDebugger::_stop_and_notify() { + stop(); + emit_signal("stopped"); + _set_reason_text(TTR("Debug session closed."), MESSAGE_WARNING); } void ScriptEditorDebugger::stop() { set_process(false); breaked = false; + can_debug = false; + remote_pid = 0; _clear_execution(); - server->stop(); - _clear_remote_objects(); + inspector->clear_cache(); ppeer->set_stream_peer(Ref<StreamPeer>()); if (connection.is_valid()) { - EditorNode::get_log()->add_message("--- Debugging process stopped ---", EditorLog::MSG_TYPE_EDITOR); connection.unref(); - reason->set_text(""); reason->set_tooltip(""); } - pending_in_queue = 0; - message.clear(); - node_path_cache.clear(); res_path_cache.clear(); profiler_signature.clear(); - le_clear->set_disabled(false); - le_set->set_disabled(true); - profiler->set_enabled(true); - vmem_refresh->set_disabled(true); - inspect_scene_tree->clear(); inspector->edit(NULL); - EditorNode::get_singleton()->get_pause_button()->set_pressed(false); - EditorNode::get_singleton()->get_pause_button()->set_disabled(true); - EditorNode::get_singleton()->get_scene_tree_dock()->hide_remote_tree(); - EditorNode::get_singleton()->get_scene_tree_dock()->hide_tab_buttons(); - - if (hide_on_stop) { - if (is_visible_in_tree()) - EditorNode::get_singleton()->hide_bottom_panel(); - emit_signal("show_debugger", false); - } + _update_buttons_state(); } void ScriptEditorDebugger::_profiler_activate(bool p_enable) { - if (!connection.is_valid()) - return; - if (p_enable) { profiler_signature.clear(); Array msg; - msg.push_back("start_profiling"); int max_funcs = EditorSettings::get_singleton()->get("debugger/profiler_frame_max_functions"); max_funcs = CLAMP(max_funcs, 16, 512); msg.push_back(max_funcs); - ppeer->put_var(msg); + _put_msg("start_profiling", msg); print_verbose("Starting profiling."); } else { - Array msg; - msg.push_back("stop_profiling"); - ppeer->put_var(msg); + _put_msg("stop_profiling", Array()); print_verbose("Ending profiling."); } } @@ -1598,44 +975,30 @@ void ScriptEditorDebugger::_visual_profiler_activate(bool p_enable) { if (p_enable) { profiler_signature.clear(); - Array msg; - msg.push_back("start_visual_profiling"); - ppeer->put_var(msg); + _put_msg("start_visual_profiling", Array()); print_verbose("Starting visual profiling."); } else { - Array msg; - msg.push_back("stop_visual_profiling"); - ppeer->put_var(msg); + _put_msg("stop_visual_profiling", Array()); print_verbose("Ending visual profiling."); } } void ScriptEditorDebugger::_network_profiler_activate(bool p_enable) { - if (!connection.is_valid()) - return; - if (p_enable) { profiler_signature.clear(); - Array msg; - msg.push_back("start_network_profiling"); - ppeer->put_var(msg); + _put_msg("start_network_profiling", Array()); print_verbose("Starting network profiling."); } else { - Array msg; - msg.push_back("stop_network_profiling"); - ppeer->put_var(msg); + _put_msg("stop_network_profiling", Array()); print_verbose("Ending network profiling."); } } void ScriptEditorDebugger::_profiler_seeked() { - if (!connection.is_valid() || !connection->is_connected_to_host()) - return; - if (breaked) return; debug_break(); @@ -1643,45 +1006,30 @@ void ScriptEditorDebugger::_profiler_seeked() { void ScriptEditorDebugger::_stack_dump_frame_selected() { - TreeItem *ti = stack_dump->get_selected(); - if (!ti) - return; + emit_signal("stack_frame_selected"); - Dictionary d = ti->get_metadata(0); - - stack_script = ResourceLoader::load(d["file"]); - emit_signal("goto_script_line", stack_script, int(d["line"]) - 1); - emit_signal("set_execution", stack_script, int(d["line"]) - 1); - stack_script.unref(); + int frame = get_stack_script_frame(); - if (connection.is_valid() && connection->is_connected_to_host()) { + if (is_session_active() && frame >= 0) { Array msg; - msg.push_back("get_stack_frame_vars"); - msg.push_back(d["frame"]); - ppeer->put_var(msg); + msg.push_back(frame); + _put_msg("get_stack_frame_vars", msg); } else { inspector->edit(NULL); } } -void ScriptEditorDebugger::_output_clear() { - - //output->clear(); - //output->push_color(Color(0,0,0)); -} - void ScriptEditorDebugger::_export_csv() { file_dialog->set_mode(EditorFileDialog::MODE_SAVE_FILE); file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); - file_dialog_mode = SAVE_CSV; file_dialog->popup_centered_ratio(); } String ScriptEditorDebugger::get_var_value(const String &p_var) const { if (!breaked) return String(); - return variables->get_var_value(p_var); + return inspector->get_stack_variable(p_var); } int ScriptEditorDebugger::_get_node_path_cache(const NodePath &p_path) { @@ -1694,10 +1042,9 @@ int ScriptEditorDebugger::_get_node_path_cache(const NodePath &p_path) { node_path_cache[p_path] = last_path_id; Array msg; - msg.push_back("live_node_path"); msg.push_back(p_path); msg.push_back(last_path_id); - ppeer->put_var(msg); + _put_msg("live_node_path", msg); return last_path_id; } @@ -1713,17 +1060,16 @@ int ScriptEditorDebugger::_get_res_path_cache(const String &p_path) { res_path_cache[p_path] = last_path_id; Array msg; - msg.push_back("live_res_path"); msg.push_back(p_path); msg.push_back(last_path_id); - ppeer->put_var(msg); + _put_msg("live_res_path", msg); return last_path_id; } void ScriptEditorDebugger::_method_changed(Object *p_base, const StringName &p_name, VARIANT_ARG_DECLARE) { - if (!p_base || !live_debug || !connection.is_valid() || !editor->get_edited_scene()) + if (!p_base || !live_debug || !is_session_active() || !editor->get_edited_scene()) return; Node *node = Object::cast_to<Node>(p_base); @@ -1742,14 +1088,13 @@ void ScriptEditorDebugger::_method_changed(Object *p_base, const StringName &p_n int pathid = _get_node_path_cache(path); Array msg; - msg.push_back("live_node_call"); msg.push_back(pathid); msg.push_back(p_name); for (int i = 0; i < VARIANT_ARG_MAX; i++) { //no pointers, sorry msg.push_back(*argptr[i]); } - ppeer->put_var(msg); + _put_msg("live_node_call", msg); return; } @@ -1762,14 +1107,13 @@ void ScriptEditorDebugger::_method_changed(Object *p_base, const StringName &p_n int pathid = _get_res_path_cache(respath); Array msg; - msg.push_back("live_res_call"); msg.push_back(pathid); msg.push_back(p_name); for (int i = 0; i < VARIANT_ARG_MAX; i++) { //no pointers, sorry msg.push_back(*argptr[i]); } - ppeer->put_var(msg); + _put_msg("live_res_call", msg); return; } @@ -1777,7 +1121,7 @@ void ScriptEditorDebugger::_method_changed(Object *p_base, const StringName &p_n void ScriptEditorDebugger::_property_changed(Object *p_base, const StringName &p_property, const Variant &p_value) { - if (!p_base || !live_debug || !connection.is_valid() || !editor->get_edited_scene()) + if (!p_base || !live_debug || !editor->get_edited_scene()) return; Node *node = Object::cast_to<Node>(p_base); @@ -1792,20 +1136,18 @@ void ScriptEditorDebugger::_property_changed(Object *p_base, const StringName &p if (res.is_valid() && res->get_path() != String()) { Array msg; - msg.push_back("live_node_prop_res"); msg.push_back(pathid); msg.push_back(p_property); msg.push_back(res->get_path()); - ppeer->put_var(msg); + _put_msg("live_node_prop_res", msg); } } else { Array msg; - msg.push_back("live_node_prop"); msg.push_back(pathid); msg.push_back(p_property); msg.push_back(p_value); - ppeer->put_var(msg); + _put_msg("live_node_prop", msg); } return; @@ -1823,36 +1165,46 @@ void ScriptEditorDebugger::_property_changed(Object *p_base, const StringName &p if (res2.is_valid() && res2->get_path() != String()) { Array msg; - msg.push_back("live_res_prop_res"); msg.push_back(pathid); msg.push_back(p_property); msg.push_back(res2->get_path()); - ppeer->put_var(msg); + _put_msg("live_res_prop_res", msg); } } else { Array msg; - msg.push_back("live_res_prop"); msg.push_back(pathid); msg.push_back(p_property); msg.push_back(p_value); - ppeer->put_var(msg); + _put_msg("live_res_prop", msg); } return; } } -void ScriptEditorDebugger::_method_changeds(void *p_ud, Object *p_base, const StringName &p_name, VARIANT_ARG_DECLARE) { - - ScriptEditorDebugger *sed = (ScriptEditorDebugger *)p_ud; - sed->_method_changed(p_base, p_name, VARIANT_ARG_PASS); +String ScriptEditorDebugger::get_stack_script_file() const { + TreeItem *ti = stack_dump->get_selected(); + if (!ti) + return ""; + Dictionary d = ti->get_metadata(0); + return d["file"]; } -void ScriptEditorDebugger::_property_changeds(void *p_ud, Object *p_base, const StringName &p_property, const Variant &p_value) { +int ScriptEditorDebugger::get_stack_script_line() const { + TreeItem *ti = stack_dump->get_selected(); + if (!ti) + return -1; + Dictionary d = ti->get_metadata(0); + return d["line"]; +} - ScriptEditorDebugger *sed = (ScriptEditorDebugger *)p_ud; - sed->_property_changed(p_base, p_property, p_value); +int ScriptEditorDebugger::get_stack_script_frame() const { + TreeItem *ti = stack_dump->get_selected(); + if (!ti) + return -1; + Dictionary d = ti->get_metadata(0); + return d["frame"]; } void ScriptEditorDebugger::set_live_debugging(bool p_enable) { @@ -1862,12 +1214,13 @@ void ScriptEditorDebugger::set_live_debugging(bool p_enable) { void ScriptEditorDebugger::_live_edit_set() { - if (!connection.is_valid()) + if (!is_session_active() || !editor_remote_tree) return; - TreeItem *ti = inspect_scene_tree->get_selected(); + TreeItem *ti = editor_remote_tree->get_selected(); if (!ti) return; + String path; while (ti) { @@ -1895,92 +1248,82 @@ void ScriptEditorDebugger::update_live_edit_root() { NodePath np = editor->get_editor_data().get_edited_scene_live_edit_root(); - if (connection.is_valid()) { - Array msg; - msg.push_back("live_set_root"); - msg.push_back(np); - if (editor->get_edited_scene()) - msg.push_back(editor->get_edited_scene()->get_filename()); - else - msg.push_back(""); - ppeer->put_var(msg); - } + Array msg; + msg.push_back(np); + if (editor->get_edited_scene()) + msg.push_back(editor->get_edited_scene()->get_filename()); + else + msg.push_back(""); + _put_msg("live_set_root", msg); live_edit_root->set_text(np); } void ScriptEditorDebugger::live_debug_create_node(const NodePath &p_parent, const String &p_type, const String &p_name) { - if (live_debug && connection.is_valid()) { + if (live_debug) { Array msg; - msg.push_back("live_create_node"); msg.push_back(p_parent); msg.push_back(p_type); msg.push_back(p_name); - ppeer->put_var(msg); + _put_msg("live_create_node", msg); } } void ScriptEditorDebugger::live_debug_instance_node(const NodePath &p_parent, const String &p_path, const String &p_name) { - if (live_debug && connection.is_valid()) { + if (live_debug) { Array msg; - msg.push_back("live_instance_node"); msg.push_back(p_parent); msg.push_back(p_path); msg.push_back(p_name); - ppeer->put_var(msg); + _put_msg("live_instance_node", msg); } } void ScriptEditorDebugger::live_debug_remove_node(const NodePath &p_at) { - if (live_debug && connection.is_valid()) { + if (live_debug) { Array msg; - msg.push_back("live_remove_node"); msg.push_back(p_at); - ppeer->put_var(msg); + _put_msg("live_remove_node", msg); } } void ScriptEditorDebugger::live_debug_remove_and_keep_node(const NodePath &p_at, ObjectID p_keep_id) { - if (live_debug && connection.is_valid()) { + if (live_debug) { Array msg; - msg.push_back("live_remove_and_keep_node"); msg.push_back(p_at); msg.push_back(p_keep_id); - ppeer->put_var(msg); + _put_msg("live_remove_and_keep_node", msg); } } void ScriptEditorDebugger::live_debug_restore_node(ObjectID p_id, const NodePath &p_at, int p_at_pos) { - if (live_debug && connection.is_valid()) { + if (live_debug) { Array msg; - msg.push_back("live_restore_node"); msg.push_back(p_id); msg.push_back(p_at); msg.push_back(p_at_pos); - ppeer->put_var(msg); + _put_msg("live_restore_node", msg); } } void ScriptEditorDebugger::live_debug_duplicate_node(const NodePath &p_at, const String &p_new_name) { - if (live_debug && connection.is_valid()) { + if (live_debug) { Array msg; - msg.push_back("live_duplicate_node"); msg.push_back(p_at); msg.push_back(p_new_name); - ppeer->put_var(msg); + _put_msg("live_duplicate_node", msg); } } void ScriptEditorDebugger::live_debug_reparent_node(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos) { - if (live_debug && connection.is_valid()) { + if (live_debug) { Array msg; - msg.push_back("live_reparent_node"); msg.push_back(p_at); msg.push_back(p_new_place); msg.push_back(p_new_name); msg.push_back(p_at_pos); - ppeer->put_var(msg); + _put_msg("live_reparent_node", msg); } } @@ -1991,33 +1334,21 @@ ScriptEditorDebugger::CameraOverride ScriptEditorDebugger::get_camera_override() void ScriptEditorDebugger::set_camera_override(CameraOverride p_override) { if (p_override == OVERRIDE_2D && camera_override != OVERRIDE_2D) { - if (connection.is_valid()) { - Array msg; - msg.push_back("override_camera_2D:set"); - msg.push_back(true); - ppeer->put_var(msg); - } + Array msg; + msg.push_back(true); + _put_msg("override_camera_2D:set", msg); } else if (p_override != OVERRIDE_2D && camera_override == OVERRIDE_2D) { - if (connection.is_valid()) { - Array msg; - msg.push_back("override_camera_2D:set"); - msg.push_back(false); - ppeer->put_var(msg); - } + Array msg; + msg.push_back(false); + _put_msg("override_camera_2D:set", msg); } else if (p_override >= OVERRIDE_3D_1 && camera_override < OVERRIDE_3D_1) { - if (connection.is_valid()) { - Array msg; - msg.push_back("override_camera_3D:set"); - msg.push_back(true); - ppeer->put_var(msg); - } + Array msg; + msg.push_back(true); + _put_msg("override_camera_3D:set", msg); } else if (p_override < OVERRIDE_3D_1 && camera_override >= OVERRIDE_3D_1) { - if (connection.is_valid()) { - Array msg; - msg.push_back("override_camera_3D:set"); - msg.push_back(false); - ppeer->put_var(msg); - } + Array msg; + msg.push_back(false); + _put_msg("override_camera_3D:set", msg); } camera_override = p_override; @@ -2025,23 +1356,16 @@ void ScriptEditorDebugger::set_camera_override(CameraOverride p_override) { void ScriptEditorDebugger::set_breakpoint(const String &p_path, int p_line, bool p_enabled) { - if (connection.is_valid()) { - Array msg; - msg.push_back("breakpoint"); - msg.push_back(p_path); - msg.push_back(p_line); - msg.push_back(p_enabled); - ppeer->put_var(msg); - } + Array msg; + msg.push_back(p_path); + msg.push_back(p_line); + msg.push_back(p_enabled); + _put_msg("breakpoint", msg); } void ScriptEditorDebugger::reload_scripts() { - if (connection.is_valid()) { - Array msg; - msg.push_back("reload_scripts"); - ppeer->put_var(msg); - } + _put_msg("reload_scripts", Array()); } bool ScriptEditorDebugger::is_skip_breakpoints() { @@ -2059,15 +1383,12 @@ void ScriptEditorDebugger::_error_activated() { void ScriptEditorDebugger::_error_selected() { TreeItem *selected = error_tree->get_selected(); - Array meta = selected->get_metadata(0); - if (meta.size() == 0) { return; } - Ref<Script> s = ResourceLoader::load(meta[0]); - emit_signal("goto_script_line", s, int(meta[1]) - 1); + emit_signal("error_selected", String(meta[0]), int(meta[1])); } void ScriptEditorDebugger::_expand_errors_list() { @@ -2096,58 +1417,6 @@ void ScriptEditorDebugger::_collapse_errors_list() { } } -void ScriptEditorDebugger::set_hide_on_stop(bool p_hide) { - - hide_on_stop = p_hide; -} - -bool ScriptEditorDebugger::get_debug_with_external_editor() const { - - return enable_external_editor; -} - -void ScriptEditorDebugger::set_debug_with_external_editor(bool p_enabled) { - - enable_external_editor = p_enabled; -} - -Ref<Script> ScriptEditorDebugger::get_dump_stack_script() const { - - return stack_script; -} - -void ScriptEditorDebugger::_paused() { - - ERR_FAIL_COND(connection.is_null()); - ERR_FAIL_COND(!connection->is_connected_to_host()); - - if (!breaked && EditorNode::get_singleton()->get_pause_button()->is_pressed()) { - debug_break(); - } - - if (breaked && !EditorNode::get_singleton()->get_pause_button()->is_pressed()) { - debug_continue(); - } -} - -void ScriptEditorDebugger::_set_remote_object(ObjectID p_id, ScriptEditorDebuggerInspectedObject *p_obj) { - - if (remote_objects.has(p_id)) - memdelete(remote_objects[p_id]); - remote_objects[p_id] = p_obj; -} - -void ScriptEditorDebugger::_clear_remote_objects() { - - for (Map<ObjectID, ScriptEditorDebuggerInspectedObject *>::Element *E = remote_objects.front(); E; E = E->next()) { - if (editor->get_editor_history()->get_current() == E->value()->get_instance_id()) { - editor->push_item(NULL); - } - memdelete(E->value()); - } - remote_objects.clear(); -} - void ScriptEditorDebugger::_clear_errors_list() { error_tree->clear(); @@ -2163,7 +1432,7 @@ void ScriptEditorDebugger::_error_tree_item_rmb_selected(const Vector2 &p_pos) { item_menu->set_size(Size2(1, 1)); if (error_tree->is_anything_selected()) { - item_menu->add_icon_item(get_icon("ActionCopy", "EditorIcons"), TTR("Copy Error"), ITEM_MENU_COPY_ERROR); + item_menu->add_icon_item(get_icon("ActionCopy", "EditorIcons"), TTR("Copy Error"), 0); } if (item_menu->get_item_count() > 0) { @@ -2173,70 +1442,29 @@ void ScriptEditorDebugger::_error_tree_item_rmb_selected(const Vector2 &p_pos) { } void ScriptEditorDebugger::_item_menu_id_pressed(int p_option) { + TreeItem *ti = error_tree->get_selected(); + while (ti->get_parent() != error_tree->get_root()) + ti = ti->get_parent(); - switch (p_option) { - - case ITEM_MENU_COPY_ERROR: { - TreeItem *ti = error_tree->get_selected(); - while (ti->get_parent() != error_tree->get_root()) - ti = ti->get_parent(); - - String type; - - if (ti->get_icon(0) == get_icon("Warning", "EditorIcons")) { - type = "W "; - } else if (ti->get_icon(0) == get_icon("Error", "EditorIcons")) { - type = "E "; - } - - String text = ti->get_text(0) + " "; - int rpad_len = text.length(); - - text = type + text + ti->get_text(1) + "\n"; - TreeItem *ci = ti->get_children(); - while (ci) { - text += " " + ci->get_text(0).rpad(rpad_len) + ci->get_text(1) + "\n"; - ci = ci->get_next(); - } - - OS::get_singleton()->set_clipboard(text); - - } break; - case ITEM_MENU_SAVE_REMOTE_NODE: { - - file_dialog->set_access(EditorFileDialog::ACCESS_RESOURCES); - file_dialog->set_mode(EditorFileDialog::MODE_SAVE_FILE); - file_dialog_mode = SAVE_NODE; - - List<String> extensions; - Ref<PackedScene> sd = memnew(PackedScene); - ResourceSaver::get_recognized_extensions(sd, &extensions); - file_dialog->clear_filters(); - for (int i = 0; i < extensions.size(); i++) { - file_dialog->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); - } + String type; - file_dialog->popup_centered_ratio(); - } break; - case ITEM_MENU_COPY_NODE_PATH: { + if (ti->get_icon(0) == get_icon("Warning", "EditorIcons")) { + type = "W "; + } else if (ti->get_icon(0) == get_icon("Error", "EditorIcons")) { + type = "E "; + } - TreeItem *ti = inspect_scene_tree->get_selected(); - String text = ti->get_text(0); + String text = ti->get_text(0) + " "; + int rpad_len = text.length(); - if (ti->get_parent() == NULL) { - text = "."; - } else if (ti->get_parent()->get_parent() == NULL) { - text = "."; - } else { - while (ti->get_parent()->get_parent() != inspect_scene_tree->get_root()) { - ti = ti->get_parent(); - text = ti->get_text(0) + "/" + text; - } - } - - OS::get_singleton()->set_clipboard(text); - } break; + text = type + text + ti->get_text(1) + "\n"; + TreeItem *ci = ti->get_children(); + while (ci) { + text += " " + ci->get_text(0).rpad(rpad_len) + ci->get_text(1) + "\n"; + ci = ci->get_next(); } + + OS::get_singleton()->set_clipboard(text); } void ScriptEditorDebugger::_tab_changed(int p_tab) { @@ -2257,11 +1485,9 @@ void ScriptEditorDebugger::_bind_methods() { ClassDB::bind_method(D_METHOD("debug_step"), &ScriptEditorDebugger::debug_step); ClassDB::bind_method(D_METHOD("debug_break"), &ScriptEditorDebugger::debug_break); ClassDB::bind_method(D_METHOD("debug_continue"), &ScriptEditorDebugger::debug_continue); - ClassDB::bind_method(D_METHOD("_output_clear"), &ScriptEditorDebugger::_output_clear); ClassDB::bind_method(D_METHOD("_export_csv"), &ScriptEditorDebugger::_export_csv); ClassDB::bind_method(D_METHOD("_performance_draw"), &ScriptEditorDebugger::_performance_draw); ClassDB::bind_method(D_METHOD("_performance_select"), &ScriptEditorDebugger::_performance_select); - ClassDB::bind_method(D_METHOD("_scene_tree_request"), &ScriptEditorDebugger::_scene_tree_request); ClassDB::bind_method(D_METHOD("_video_mem_request"), &ScriptEditorDebugger::_video_mem_request); ClassDB::bind_method(D_METHOD("_live_edit_set"), &ScriptEditorDebugger::_live_edit_set); ClassDB::bind_method(D_METHOD("_live_edit_clear"), &ScriptEditorDebugger::_live_edit_clear); @@ -2279,13 +1505,10 @@ void ScriptEditorDebugger::_bind_methods() { ClassDB::bind_method(D_METHOD("_error_tree_item_rmb_selected"), &ScriptEditorDebugger::_error_tree_item_rmb_selected); ClassDB::bind_method(D_METHOD("_item_menu_id_pressed"), &ScriptEditorDebugger::_item_menu_id_pressed); ClassDB::bind_method(D_METHOD("_tab_changed"), &ScriptEditorDebugger::_tab_changed); - - ClassDB::bind_method(D_METHOD("_paused"), &ScriptEditorDebugger::_paused); - - ClassDB::bind_method(D_METHOD("_scene_tree_selected"), &ScriptEditorDebugger::_scene_tree_selected); - ClassDB::bind_method(D_METHOD("_scene_tree_folded"), &ScriptEditorDebugger::_scene_tree_folded); - ClassDB::bind_method(D_METHOD("_scene_tree_rmb_selected"), &ScriptEditorDebugger::_scene_tree_rmb_selected); ClassDB::bind_method(D_METHOD("_file_selected"), &ScriptEditorDebugger::_file_selected); + ClassDB::bind_method(D_METHOD("_remote_object_selected", "id"), &ScriptEditorDebugger::_remote_object_selected); + ClassDB::bind_method(D_METHOD("_remote_object_edited", "id", "property", "value"), &ScriptEditorDebugger::_remote_object_edited); + ClassDB::bind_method(D_METHOD("_remote_object_property_updated", "id", "property"), &ScriptEditorDebugger::_remote_object_property_updated); ClassDB::bind_method(D_METHOD("live_debug_create_node"), &ScriptEditorDebugger::live_debug_create_node); ClassDB::bind_method(D_METHOD("live_debug_instance_node"), &ScriptEditorDebugger::live_debug_instance_node); @@ -2294,14 +1517,20 @@ void ScriptEditorDebugger::_bind_methods() { ClassDB::bind_method(D_METHOD("live_debug_restore_node"), &ScriptEditorDebugger::live_debug_restore_node); ClassDB::bind_method(D_METHOD("live_debug_duplicate_node"), &ScriptEditorDebugger::live_debug_duplicate_node); ClassDB::bind_method(D_METHOD("live_debug_reparent_node"), &ScriptEditorDebugger::live_debug_reparent_node); - ClassDB::bind_method(D_METHOD("_scene_tree_property_select_object"), &ScriptEditorDebugger::_scene_tree_property_select_object); - ClassDB::bind_method(D_METHOD("_scene_tree_property_value_edited"), &ScriptEditorDebugger::_scene_tree_property_value_edited); + ClassDB::bind_method(D_METHOD("request_remote_object", "id"), &ScriptEditorDebugger::request_remote_object); + ClassDB::bind_method(D_METHOD("update_remote_object", "id", "property", "value"), &ScriptEditorDebugger::update_remote_object); - ADD_SIGNAL(MethodInfo("goto_script_line")); + ADD_SIGNAL(MethodInfo("stopped")); + ADD_SIGNAL(MethodInfo("stop_requested")); + ADD_SIGNAL(MethodInfo("stack_frame_selected", PropertyInfo(Variant::INT, "frame"))); + ADD_SIGNAL(MethodInfo("error_selected", PropertyInfo(Variant::INT, "error"))); ADD_SIGNAL(MethodInfo("set_execution", PropertyInfo("script"), PropertyInfo(Variant::INT, "line"))); ADD_SIGNAL(MethodInfo("clear_execution", PropertyInfo("script"))); ADD_SIGNAL(MethodInfo("breaked", PropertyInfo(Variant::BOOL, "reallydid"), PropertyInfo(Variant::BOOL, "can_debug"))); - ADD_SIGNAL(MethodInfo("show_debugger", PropertyInfo(Variant::BOOL, "reallydid"))); + ADD_SIGNAL(MethodInfo("remote_object_requested", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("remote_object_updated", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("remote_object_property_updated", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "property"))); + ADD_SIGNAL(MethodInfo("remote_tree_updated")); } ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { @@ -2312,7 +1541,6 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { ppeer = Ref<PacketPeerStream>(memnew(PacketPeerStream)); ppeer->set_input_buffer_max_size((1024 * 1024 * 8) - 4); // 8 MiB should be enough, minus 4 bytes for separator. editor = p_editor; - editor->get_inspector()->connect_compat("object_id_selected", this, "_scene_tree_property_select_object"); tabs = memnew(TabContainer); tabs->set_tab_align(TabContainer::ALIGN_LEFT); @@ -2381,16 +1609,6 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { docontinue->set_shortcut(ED_GET_SHORTCUT("debugger/continue")); docontinue->connect_compat("pressed", this, "debug_continue"); - back = memnew(Button); - hbc->add_child(back); - back->set_tooltip(TTR("Inspect Previous Instance")); - back->hide(); - - forward = memnew(Button); - hbc->add_child(forward); - forward->set_tooltip(TTR("Inspect Next Instance")); - forward->hide(); - HSplitContainer *sc = memnew(HSplitContainer); vbc->add_child(sc); sc->set_v_size_flags(SIZE_EXPAND_FILL); @@ -2405,21 +1623,14 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { stack_dump->connect_compat("cell_selected", this, "_stack_dump_frame_selected"); sc->add_child(stack_dump); - inspector = memnew(EditorInspector); + inspector = memnew(EditorDebuggerInspector); inspector->set_h_size_flags(SIZE_EXPAND_FILL); inspector->set_enable_capitalize_paths(false); inspector->set_read_only(true); - inspector->connect_compat("object_id_selected", this, "_scene_tree_property_select_object"); + inspector->connect_compat("object_selected", this, "_remote_object_selected"); + inspector->connect_compat("object_edited", this, "_remote_object_edited"); + inspector->connect_compat("object_property_updated", this, "_remote_object_property_updated"); sc->add_child(inspector); - - server.instance(); - - pending_in_queue = 0; - - variables = memnew(ScriptEditorDebuggerVariables); - - breaked = false; - tabs->add_child(dbg); } @@ -2472,23 +1683,6 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { tabs->add_child(errors_tab); } - { // remote scene tree - - inspect_scene_tree = memnew(Tree); - EditorNode::get_singleton()->get_scene_tree_dock()->add_remote_tree_editor(inspect_scene_tree); - EditorNode::get_singleton()->get_scene_tree_dock()->connect_compat("remote_tree_selected", this, "_scene_tree_selected"); - inspect_scene_tree->set_v_size_flags(SIZE_EXPAND_FILL); - inspect_scene_tree->connect_compat("cell_selected", this, "_scene_tree_selected"); - inspect_scene_tree->connect_compat("item_collapsed", this, "_scene_tree_folded"); - inspect_scene_tree->set_allow_rmb_select(true); - inspect_scene_tree->connect_compat("item_rmb_selected", this, "_scene_tree_rmb_selected"); - auto_switch_remote_scene_tree = EDITOR_DEF("debugger/auto_switch_to_remote_scene_tree", false); - inspect_scene_tree_timeout = EDITOR_DEF("debugger/remote_scene_tree_refresh_interval", 1.0); - inspect_edited_object_timeout = EDITOR_DEF("debugger/remote_inspect_refresh_interval", 0.2); - inspected_object_id = ObjectID(); - updating_scene_tree = false; - } - { // File dialog file_dialog = memnew(EditorFileDialog); file_dialog->connect_compat("file_selected", this, "_file_selected"); @@ -2508,6 +1702,7 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { visual_profiler->set_name(TTR("Visual Profiler")); tabs->add_child(visual_profiler); visual_profiler->connect_compat("enable_profiling", this, "_visual_profiler_activate"); + visual_profiler->connect_compat("break_request", this, "_profiler_seeked"); } { //network profiler @@ -2590,7 +1785,6 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { vmem_total->set_custom_minimum_size(Size2(100, 0) * EDSCALE); vmem_hb->add_child(vmem_total); vmem_refresh = memnew(ToolButton); - vmem_refresh->set_disabled(true); vmem_hb->add_child(vmem_refresh); vmem_vb->add_child(vmem_hb); vmem_refresh->connect_compat("pressed", this, "_video_mem_request"); @@ -2638,6 +1832,7 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { info_left->add_child(memnew(Label(TTR("Clicked Control Type:")))); info_left->add_child(clicked_ctrl_type); + scene_tree = memnew(SceneDebuggerTree); live_edit_root = memnew(LineEdit); live_edit_root->set_h_size_flags(SIZE_EXPAND_FILL); @@ -2651,8 +1846,6 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { le_clear = memnew(Button(TTR("Clear"))); lehb->add_child(le_clear); info_left->add_child(lehb); - le_set->set_disabled(true); - le_clear->set_disabled(true); } misc->add_child(memnew(VSeparator)); @@ -2669,27 +1862,18 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { msgdialog = memnew(AcceptDialog); add_child(msgdialog); - p_editor->get_undo_redo()->set_method_notify_callback(_method_changeds, this); - p_editor->get_undo_redo()->set_property_notify_callback(_property_changeds, this); live_debug = true; camera_override = OVERRIDE_NONE; last_path_id = false; error_count = 0; warning_count = 0; - hide_on_stop = true; - enable_external_editor = false; - last_error_count = 0; - last_warning_count = 0; - - EditorNode::get_singleton()->get_pause_button()->connect_compat("pressed", this, "_paused"); + _update_buttons_state(); } ScriptEditorDebugger::~ScriptEditorDebugger() { - memdelete(variables); - ppeer->set_stream_peer(Ref<StreamPeer>()); - server->stop(); - _clear_remote_objects(); + inspector->clear_cache(); + memdelete(scene_tree); } diff --git a/editor/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h index 5a821a4a88..ce205e95b1 100644 --- a/editor/script_editor_debugger.h +++ b/editor/debugger/script_editor_debugger.h @@ -32,7 +32,8 @@ #define SCRIPT_EDITOR_DEBUGGER_H #include "core/io/packet_peer.h" -#include "core/io/tcp_server.h" +#include "core/io/stream_peer_tcp.h" +#include "editor/debugger/editor_debugger_inspector.h" #include "editor/editor_inspector.h" #include "editor/property_editor.h" #include "scene/3d/camera.h" @@ -41,7 +42,6 @@ class Tree; class EditorNode; -class ScriptEditorDebuggerVariables; class LineEdit; class TabContainer; class RichTextLabel; @@ -53,13 +53,14 @@ class ItemList; class EditorProfiler; class EditorVisualProfiler; class EditorNetworkProfiler; - -class ScriptEditorDebuggerInspectedObject; +class SceneDebuggerTree; class ScriptEditorDebugger : public MarginContainer { GDCLASS(ScriptEditorDebugger, MarginContainer); + friend class EditorDebuggerNode; + public: enum CameraOverride { OVERRIDE_NONE, @@ -77,16 +78,8 @@ private: MESSAGE_SUCCESS, }; - enum ItemMenu { - ITEM_MENU_COPY_ERROR, - ITEM_MENU_SAVE_REMOTE_NODE, - ITEM_MENU_COPY_NODE_PATH, - }; - AcceptDialog *msgdialog; - Button *debugger_button; - LineEdit *clicked_ctrl; LineEdit *clicked_ctrl_type; LineEdit *live_edit_root; @@ -94,35 +87,15 @@ private: Button *le_clear; Button *export_csv; - bool updating_scene_tree; - float inspect_scene_tree_timeout; - float inspect_edited_object_timeout; - bool auto_switch_remote_scene_tree; - ObjectID inspected_object_id; - ScriptEditorDebuggerVariables *variables; - Map<ObjectID, ScriptEditorDebuggerInspectedObject *> remote_objects; - Set<ObjectID> unfold_cache; - VBoxContainer *errors_tab; Tree *error_tree; - Tree *inspect_scene_tree; Button *clearbutton; PopupMenu *item_menu; EditorFileDialog *file_dialog; - enum FileDialogMode { - SAVE_CSV, - SAVE_NODE, - }; - FileDialogMode file_dialog_mode; int error_count; int warning_count; - int last_error_count; - int last_warning_count; - - bool hide_on_stop; - bool enable_external_editor; bool skip_breakpoints_value = false; Ref<Script> stack_script; @@ -135,10 +108,11 @@ private: Button *copy; Button *step; Button *next; - Button *back; - Button *forward; Button *dobreak; Button *docontinue; + // Reference to "Remote" tab in scene tree. Needed by _live_edit_set and buttons state. + // Each debugger should have it's tree in the future I guess. + const Tree *editor_remote_tree = NULL; List<Vector<float> > perf_history; Vector<float> perf_max; @@ -155,16 +129,12 @@ private: LineEdit *vmem_total; Tree *stack_dump; - EditorInspector *inspector; + EditorDebuggerInspector *inspector; + SceneDebuggerTree *scene_tree; - Ref<TCP_Server> server; Ref<StreamPeerTCP> connection; Ref<PacketPeerStream> ppeer; - String message_type; - Array message; - int pending_in_queue; - HashMap<NodePath, int> node_path_cache; int last_path_id; Map<String, int> res_path_cache; @@ -175,7 +145,9 @@ private: EditorNode *editor; - bool breaked; + OS::ProcessID remote_pid = 0; + bool breaked = false; + bool can_debug = false; bool live_debug; @@ -184,18 +156,14 @@ private: void _performance_draw(); void _performance_select(); void _stack_dump_frame_selected(); - void _output_clear(); - void _scene_tree_folded(Object *obj); - void _scene_tree_selected(); - void _scene_tree_rmb_selected(const Vector2 &p_position); void _file_selected(const String &p_file); - void _scene_tree_request(); void _parse_message(const String &p_msg, const Array &p_data); void _set_reason_text(const String &p_reason, MessageType p_type); - void _scene_tree_property_select_object(ObjectID p_object); - void _scene_tree_property_value_edited(const String &p_prop, const Variant &p_value); - int _update_scene_tree(TreeItem *parent, const Array &nodes, int current_index); + void _update_buttons_state(); + void _remote_object_selected(ObjectID p_object); + void _remote_object_edited(ObjectID, const String &p_prop, const Variant &p_value); + void _remote_object_property_updated(ObjectID p_id, const String &p_property); void _video_mem_request(); @@ -221,28 +189,34 @@ private: void _network_profiler_activate(bool p_enable); - void _paused(); - - void _set_remote_object(ObjectID p_id, ScriptEditorDebuggerInspectedObject *p_obj); - void _clear_remote_objects(); void _clear_errors_list(); void _error_tree_item_rmb_selected(const Vector2 &p_pos); void _item_menu_id_pressed(int p_option); void _tab_changed(int p_tab); + void _put_msg(String p_message, Array p_data); void _export_csv(); void _clear_execution(); + void _stop_and_notify(); protected: void _notification(int p_what); static void _bind_methods(); public: - void start(); - void pause(); - void unpause(); + void request_remote_object(ObjectID p_obj_id); + void update_remote_object(ObjectID p_obj_id, const String &p_prop, const Variant &p_value); + Object *get_remote_object(ObjectID p_id); + + // Needed by _live_edit_set, buttons state. + void set_editor_remote_tree(const Tree *p_tree) { editor_remote_tree = p_tree; } + + void request_remote_tree(); + const SceneDebuggerTree *get_remote_tree(); + + void start(Ref<StreamPeerTCP> p_connection); void stop(); void debug_skip_breakpoints(); @@ -252,14 +226,23 @@ public: void debug_step(); void debug_break(); void debug_continue(); - + bool is_breaked() const { return breaked; } + bool is_debuggable() const { return can_debug; } + bool is_session_active() { return connection.is_valid() && connection->is_connected_to_host(); }; + int get_remote_pid() const { return remote_pid; } + + int get_error_count() const { return error_count; } + int get_warning_count() const { return warning_count; } + String get_stack_script_file() const; + int get_stack_script_line() const; + int get_stack_script_frame() const; + + void update_tabs(); String get_var_value(const String &p_var) const; + void save_node(ObjectID p_id, const String &p_file); void set_live_debugging(bool p_enable); - static void _method_changeds(void *p_ud, Object *p_base, const StringName &p_name, VARIANT_ARG_DECLARE); - static void _property_changeds(void *p_ud, Object *p_base, const StringName &p_property, const Variant &p_value); - void live_debug_create_node(const NodePath &p_parent, const String &p_type, const String &p_name); void live_debug_instance_node(const NodePath &p_parent, const String &p_path, const String &p_name); void live_debug_remove_node(const NodePath &p_at); @@ -275,15 +258,6 @@ public: void update_live_edit_root(); - void set_hide_on_stop(bool p_hide); - - bool get_debug_with_external_editor() const; - void set_debug_with_external_editor(bool p_enabled); - - Ref<Script> get_dump_stack_script() const; - - void set_tool_button(Button *p_tb) { debugger_button = p_tb; } - void reload_scripts(); bool is_skip_breakpoints(); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 59fa40846f..413959bb99 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -123,6 +123,7 @@ #include "editor/plugins/cpu_particles_2d_editor_plugin.h" #include "editor/plugins/cpu_particles_editor_plugin.h" #include "editor/plugins/curve_editor_plugin.h" +#include "editor/plugins/debugger_editor_plugin.h" #include "editor/plugins/editor_preview_plugins.h" #include "editor/plugins/gi_probe_editor_plugin.h" #include "editor/plugins/gradient_editor_plugin.h" @@ -168,7 +169,6 @@ #include "editor/quick_open.h" #include "editor/register_exporters.h" #include "editor/run_settings_dialog.h" -#include "editor/script_editor_debugger.h" #include "editor/settings_config_dialog.h" #include <stdio.h> @@ -472,7 +472,7 @@ void EditorNode::_notification(int p_what) { recent_scenes->set_as_minsize(); // debugger area - if (ScriptEditor::get_singleton()->get_debugger()->is_visible()) + if (EditorDebuggerNode::get_singleton()->is_visible()) bottom_panel->add_style_override("panel", gui_base->get_stylebox("BottomPanelDebuggerOverride", "EditorStyles")); // update_icons @@ -1846,7 +1846,7 @@ void EditorNode::_edit_current() { Node *selected_node = NULL; - if (current_obj->is_class("ScriptEditorDebuggerInspectedObject")) { + if (current_obj->is_class("EditorDebuggerRemoteObject")) { editable_warning = TTR("This is a remote object, so changes to it won't be kept.\nPlease read the documentation relevant to debugging to better understand this workflow."); capitalize = false; disable_folding = true; @@ -2048,9 +2048,13 @@ void EditorNode::_run(bool p_current, const String &p_custom) { editor_data.get_editor_breakpoints(&breakpoints); args = ProjectSettings::get_singleton()->get("editor/main_run_args"); - skip_breakpoints = ScriptEditor::get_singleton()->get_debugger()->is_skip_breakpoints(); + skip_breakpoints = EditorDebuggerNode::get_singleton()->is_skip_breakpoints(); - Error error = editor_run.run(run_filename, args, breakpoints, skip_breakpoints); + int instances = 1; + if (debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_TWO))) + instances = 2; + + Error error = editor_run.run(run_filename, args, breakpoints, skip_breakpoints, instances); if (error != OK) { @@ -2481,6 +2485,16 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { run_settings_dialog->popup_run_settings(); } break; + case RUN_DEBUG_ONE: { + debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_ONE), true); + debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_TWO), false); + + } break; + case RUN_DEBUG_TWO: { + debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_TWO), true); + debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_ONE), false); + + } break; case RUN_SETTINGS: { project_settings->popup_project_settings(); @@ -2571,7 +2585,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_LIVE_DEBUG)); debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_LIVE_DEBUG), !ischecked); - ScriptEditor::get_singleton()->get_debugger()->set_live_debugging(!ischecked); + EditorDebuggerNode::get_singleton()->set_live_debugging(!ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_live_debug", !ischecked); } break; @@ -3242,7 +3256,7 @@ void EditorNode::_set_main_scene_state(Dictionary p_state, Node *p_for_scene) { //this should only happen at the very end - ScriptEditor::get_singleton()->get_debugger()->update_live_edit_root(); + EditorDebuggerNode::get_singleton()->update_live_edit_root(); ScriptEditor::get_singleton()->set_scene_root_script(editor_data.get_scene_root_script(editor_data.get_edited_scene())); editor_data.notify_edited_scene_changed(); } @@ -3477,7 +3491,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b opening_prev = false; scene_tree_dock->set_selected(new_scene); - ScriptEditor::get_singleton()->get_debugger()->update_live_edit_root(); + EditorDebuggerNode::get_singleton()->update_live_edit_root(); push_item(new_scene); @@ -3619,7 +3633,7 @@ void EditorNode::_quick_run() { _run(false, quick_run->get_selected()); } -void EditorNode::notify_child_process_exited() { +void EditorNode::notify_all_debug_sessions_exited() { _menu_option_confirm(RUN_STOP, false); stop_button->set_pressed(false); @@ -3703,9 +3717,13 @@ void EditorNode::unregister_editor_types() { _init_callbacks.clear(); } -void EditorNode::stop_child_process() { +void EditorNode::stop_child_process(OS::ProcessID p_pid) { - _menu_option_confirm(RUN_STOP, false); + if (has_child_process(p_pid)) { + editor_run.stop_child_process(p_pid); + if (!editor_run.get_child_process_count()) // All children stopped. Closing. + _menu_option_confirm(RUN_STOP, false); + } } Ref<Script> EditorNode::get_object_custom_type_base(const Object *p_object) const { @@ -4888,7 +4906,7 @@ void EditorNode::_bottom_panel_switch(bool p_enable, int p_idx) { bottom_panel_items[i].button->set_pressed(i == p_idx); bottom_panel_items[i].control->set_visible(i == p_idx); } - if (ScriptEditor::get_singleton()->get_debugger() == bottom_panel_items[p_idx].control) { // this is the debug panel which uses tabs, so the top section should be smaller + if (EditorDebuggerNode::get_singleton() == bottom_panel_items[p_idx].control) { // this is the debug panel which uses tabs, so the top section should be smaller bottom_panel->add_style_override("panel", gui_base->get_stylebox("BottomPanelDebuggerOverride", "EditorStyles")); } else { bottom_panel->add_style_override("panel", gui_base->get_stylebox("panel", "TabContainer")); @@ -6254,6 +6272,13 @@ EditorNode::EditorNode() { p->add_check_shortcut(ED_SHORTCUT("editor/sync_script_changes", TTR("Sync Script Changes")), RUN_RELOAD_SCRIPTS); p->set_item_tooltip(p->get_item_count() - 1, TTR("When this option is turned on, any script that is saved will be reloaded on the running game.\nWhen used remotely on a device, this is more efficient with network filesystem.")); p->set_item_checked(p->get_item_count() - 1, true); + + // Multi-instance, start/stop + p->add_separator(); + p->add_radio_check_item(TTR("Debug 1 instance"), RUN_DEBUG_ONE); + p->add_radio_check_item(TTR("Debug 2 instances"), RUN_DEBUG_TWO); + p->set_item_checked(p->get_item_index(RUN_DEBUG_ONE), true); + p->connect_compat("id_pressed", this, "_menu_option"); menu_hb->add_spacer(); @@ -6639,6 +6664,7 @@ EditorNode::EditorNode() { file_server = memnew(EditorFileServer); + add_editor_plugin(memnew(DebuggerEditorPlugin(this))); add_editor_plugin(memnew(AnimationPlayerEditorPlugin(this))); add_editor_plugin(memnew(CanvasItemEditorPlugin(this))); add_editor_plugin(memnew(SpatialEditorPlugin(this))); diff --git a/editor/editor_node.h b/editor/editor_node.h index ef9be0677d..a982b9d85f 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -162,6 +162,8 @@ private: RUN_PLAY_NATIVE, RUN_PLAY_CUSTOM_SCENE, RUN_SCENE_SETTINGS, + RUN_DEBUG_ONE, + RUN_DEBUG_TWO, RUN_SETTINGS, RUN_PROJECT_DATA_FOLDER, RUN_PROJECT_MANAGER, @@ -764,10 +766,10 @@ public: void set_convert_old_scene(bool p_old) { convert_old = p_old; } - void notify_child_process_exited(); + void notify_all_debug_sessions_exited(); - OS::ProcessID get_child_process_id() const { return editor_run.get_pid(); } - void stop_child_process(); + OS::ProcessID has_child_process(OS::ProcessID p_pid) const { return editor_run.has_child_process(p_pid); } + void stop_child_process(OS::ProcessID p_pid); Ref<Theme> get_editor_theme() const { return theme; } Ref<Script> get_object_custom_type_base(const Object *p_object) const; diff --git a/editor/editor_path.cpp b/editor/editor_path.cpp index cfb70a087a..696474d4b1 100644 --- a/editor/editor_path.cpp +++ b/editor/editor_path.cpp @@ -106,7 +106,7 @@ void EditorPath::update_path() { if (name == "") name = r->get_class(); - } else if (obj->is_class("ScriptEditorDebuggerInspectedObject")) + } else if (obj->is_class("EditorDebuggerRemoteObject")) name = obj->call("get_title"); else if (Object::cast_to<Node>(obj)) name = Object::cast_to<Node>(obj)->get_name(); diff --git a/editor/editor_run.cpp b/editor/editor_run.cpp index ff7420e19b..3200a0ac8b 100644 --- a/editor/editor_run.cpp +++ b/editor/editor_run.cpp @@ -38,7 +38,7 @@ EditorRun::Status EditorRun::get_status() const { return status; } -Error EditorRun::run(const String &p_scene, const String &p_custom_args, const List<String> &p_breakpoints, const bool &p_skip_breakpoints) { +Error EditorRun::run(const String &p_scene, const String &p_custom_args, const List<String> &p_breakpoints, const bool &p_skip_breakpoints, const int &p_instances) { List<String> args; @@ -187,20 +187,40 @@ Error EditorRun::run(const String &p_scene, const String &p_custom_args, const L }; printf("\n"); - pid = 0; - Error err = OS::get_singleton()->execute(exec, args, false, &pid); - ERR_FAIL_COND_V(err, err); + for (int i = 0; i < p_instances; i++) { + OS::ProcessID pid = 0; + Error err = OS::get_singleton()->execute(exec, args, false, &pid); + ERR_FAIL_COND_V(err, err); + pids.push_back(pid); + } status = STATUS_PLAY; return OK; } +bool EditorRun::has_child_process(OS::ProcessID p_pid) const { + for (const List<OS::ProcessID>::Element *E = pids.front(); E; E = E->next()) { + if (E->get() == p_pid) + return true; + } + return false; +} + +void EditorRun::stop_child_process(OS::ProcessID p_pid) { + if (has_child_process(p_pid)) { + OS::get_singleton()->kill(p_pid); + pids.erase(p_pid); + } +} + void EditorRun::stop() { - if (status != STATUS_STOP && pid != 0) { + if (status != STATUS_STOP && pids.size() > 0) { - OS::get_singleton()->kill(pid); + for (List<OS::ProcessID>::Element *E = pids.front(); E; E = E->next()) { + OS::get_singleton()->kill(E->get()); + } } status = STATUS_STOP; diff --git a/editor/editor_run.h b/editor/editor_run.h index b50a2c2f0e..389a1e6b20 100644 --- a/editor/editor_run.h +++ b/editor/editor_run.h @@ -42,7 +42,7 @@ public: STATUS_STOP }; - OS::ProcessID pid; + List<OS::ProcessID> pids; private: bool debug_collisions; @@ -51,11 +51,13 @@ private: public: Status get_status() const; - Error run(const String &p_scene, const String &p_custom_args, const List<String> &p_breakpoints, const bool &p_skip_breakpoints = false); + Error run(const String &p_scene, const String &p_custom_args, const List<String> &p_breakpoints, const bool &p_skip_breakpoints = false, const int &p_instances = 1); void run_native_notify() { status = STATUS_PLAY; } void stop(); - OS::ProcessID get_pid() const { return pid; } + void stop_child_process(OS::ProcessID p_pid); + bool has_child_process(OS::ProcessID p_pid) const; + int get_child_process_count() const { return pids.size(); } void set_debug_collisions(bool p_debug); bool get_debug_collisions() const; diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp index 6eaf6eb04a..4effdc63d1 100644 --- a/editor/inspector_dock.cpp +++ b/editor/inspector_dock.cpp @@ -247,7 +247,7 @@ void InspectorDock::_prepare_history() { } } else if (Object::cast_to<Node>(obj)) { text = Object::cast_to<Node>(obj)->get_name(); - } else if (obj->is_class("ScriptEditorDebuggerInspectedObject")) { + } else if (obj->is_class("EditorDebuggerRemoteObject")) { text = obj->call("get_title"); } else { text = obj->get_class(); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index b91b346089..dfcaf5cdd2 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -34,12 +34,12 @@ #include "core/os/keyboard.h" #include "core/print_string.h" #include "core/project_settings.h" +#include "editor/debugger/editor_debugger_node.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/plugins/animation_player_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" -#include "editor/script_editor_debugger.h" #include "scene/2d/light_2d.h" #include "scene/2d/particles_2d.h" #include "scene/2d/polygon_2d.h" @@ -3990,7 +3990,7 @@ void CanvasItemEditor::_notification(int p_what) { if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { if (!is_visible() && override_camera_button->is_pressed()) { - ScriptEditorDebugger *debugger = ScriptEditor::get_singleton()->get_debugger(); + EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton(); debugger->set_camera_override(ScriptEditorDebugger::OVERRIDE_NONE); override_camera_button->set_pressed(false); @@ -4345,7 +4345,7 @@ void CanvasItemEditor::_button_toggle_grid_snap(bool p_status) { viewport->update(); } void CanvasItemEditor::_button_override_camera(bool p_pressed) { - ScriptEditorDebugger *debugger = ScriptEditor::get_singleton()->get_debugger(); + EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton(); if (p_pressed) { debugger->set_camera_override(ScriptEditorDebugger::OVERRIDE_2D); @@ -5960,9 +5960,9 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String & if (parent) { String new_name = parent->validate_child_name(child); - ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger(); - editor_data->get_undo_redo().add_do_method(sed, "live_debug_create_node", editor->get_edited_scene()->get_path_to(parent), child->get_class(), new_name); - editor_data->get_undo_redo().add_undo_method(sed, "live_debug_remove_node", NodePath(String(editor->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); + EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); + editor_data->get_undo_redo().add_do_method(ed, "live_debug_create_node", editor->get_edited_scene()->get_path_to(parent), child->get_class(), new_name); + editor_data->get_undo_redo().add_undo_method(ed, "live_debug_remove_node", NodePath(String(editor->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); } // handle with different property for texture @@ -6030,9 +6030,9 @@ bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, cons editor_data->get_undo_redo().add_undo_method(parent, "remove_child", instanced_scene); String new_name = parent->validate_child_name(instanced_scene); - ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger(); - editor_data->get_undo_redo().add_do_method(sed, "live_debug_instance_node", editor->get_edited_scene()->get_path_to(parent), path, new_name); - editor_data->get_undo_redo().add_undo_method(sed, "live_debug_remove_node", NodePath(String(editor->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); + EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); + editor_data->get_undo_redo().add_do_method(ed, "live_debug_instance_node", editor->get_edited_scene()->get_path_to(parent), path, new_name); + editor_data->get_undo_redo().add_undo_method(ed, "live_debug_remove_node", NodePath(String(editor->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); CanvasItem *parent_ci = Object::cast_to<CanvasItem>(parent); if (parent_ci) { diff --git a/editor/plugins/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp new file mode 100644 index 0000000000..2534a2cc17 --- /dev/null +++ b/editor/plugins/debugger_editor_plugin.cpp @@ -0,0 +1,51 @@ +/*************************************************************************/ +/* debugger_editor_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "debugger_editor_plugin.h" + +#include "core/os/keyboard.h" +#include "editor/debugger/editor_debugger_node.h" + +DebuggerEditorPlugin::DebuggerEditorPlugin(EditorNode *p_editor) { + ED_SHORTCUT("debugger/step_into", TTR("Step Into"), KEY_F11); + ED_SHORTCUT("debugger/step_over", TTR("Step Over"), KEY_F10); + ED_SHORTCUT("debugger/break", TTR("Break")); + ED_SHORTCUT("debugger/continue", TTR("Continue"), KEY_F12); + ED_SHORTCUT("debugger/keep_debugger_open", TTR("Keep Debugger Open")); + ED_SHORTCUT("debugger/debug_with_external_editor", TTR("Debug with External Editor")); + + EditorDebuggerNode *debugger = memnew(EditorDebuggerNode); + Button *db = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Debugger"), debugger); + debugger->set_tool_button(db); +} + +DebuggerEditorPlugin::~DebuggerEditorPlugin() { + // Should delete debugger? +} diff --git a/editor/plugins/debugger_editor_plugin.h b/editor/plugins/debugger_editor_plugin.h new file mode 100644 index 0000000000..05d6ece72d --- /dev/null +++ b/editor/plugins/debugger_editor_plugin.h @@ -0,0 +1,50 @@ +/*************************************************************************/ +/* debugger_editor_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef DEBUGGER_EDITOR_PLUGIN_H +#define DEBUGGER_EDITOR_PLUGIN_H + +#include "editor/debugger/editor_debugger_node.h" +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" + +class DebuggerEditorPlugin : public EditorPlugin { + + GDCLASS(DebuggerEditorPlugin, EditorPlugin); + +public: + virtual String get_name() const { return "Debugger"; } + bool has_main_screen() const { return false; } + + DebuggerEditorPlugin(EditorNode *p_node); + ~DebuggerEditorPlugin(); +}; + +#endif // DEBUGGER_EDITOR_PLUGIN_H diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index cea18b63f1..c0928ba79b 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -36,6 +36,8 @@ #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/project_settings.h" +#include "editor/debugger/editor_debugger_node.h" +#include "editor/debugger/script_editor_debugger.h" #include "editor/editor_node.h" #include "editor/editor_run_script.h" #include "editor/editor_scale.h" @@ -44,7 +46,6 @@ #include "editor/find_in_files.h" #include "editor/node_dock.h" #include "editor/plugins/shader_editor_plugin.h" -#include "editor/script_editor_debugger.h" #include "scene/main/viewport.h" #include "scene/scene_string_names.h" #include "script_text_editor.h" @@ -261,7 +262,7 @@ ScriptEditor *ScriptEditor::script_editor = NULL; String ScriptEditor::_get_debug_tooltip(const String &p_text, Node *_se) { - String val = debugger->get_var_value(p_text); + String val = EditorDebuggerNode::get_singleton()->get_var_value(p_text); if (val != String()) { return p_text + ": " + val; } else { @@ -276,11 +277,6 @@ void ScriptEditor::_breaked(bool p_breaked, bool p_can_debug) { return; } - debug_menu->get_popup()->set_item_disabled(debug_menu->get_popup()->get_item_index(DEBUG_NEXT), !(p_breaked && p_can_debug)); - debug_menu->get_popup()->set_item_disabled(debug_menu->get_popup()->get_item_index(DEBUG_STEP), !(p_breaked && p_can_debug)); - debug_menu->get_popup()->set_item_disabled(debug_menu->get_popup()->get_item_index(DEBUG_BREAK), p_breaked); - debug_menu->get_popup()->set_item_disabled(debug_menu->get_popup()->get_item_index(DEBUG_CONTINUE), !p_breaked); - for (int i = 0; i < tab_container->get_child_count(); i++) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); @@ -292,11 +288,6 @@ void ScriptEditor::_breaked(bool p_breaked, bool p_can_debug) { } } -void ScriptEditor::_show_debugger(bool p_show) { - - //debug_menu->get_popup()->set_item_checked( debug_menu->get_popup()->get_item_index(DEBUG_SHOW), p_show); -} - void ScriptEditor::_script_created(Ref<Script> p_script) { editor->push_item(p_script.operator->()); } @@ -843,7 +834,7 @@ void ScriptEditor::_res_saved_callback(const Ref<Resource> &p_res) { void ScriptEditor::_live_auto_reload_running_scripts() { pending_auto_reload = false; - debugger->reload_scripts(); + EditorDebuggerNode::get_singleton()->reload_scripts(); } bool ScriptEditor::_test_script_times_on_disk(RES p_for_script) { @@ -1123,27 +1114,6 @@ void ScriptEditor::_menu_option(int p_option) { _sort_list_on_update = true; _update_script_names(); } break; - case DEBUG_SHOW: { - if (debugger) { - bool visible = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(DEBUG_SHOW)); - debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(DEBUG_SHOW), !visible); - if (visible) - debugger->hide(); - else - debugger->show(); - } - } break; - case DEBUG_SHOW_KEEP_OPEN: { - bool visible = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(DEBUG_SHOW_KEEP_OPEN)); - if (debugger) - debugger->set_hide_on_stop(visible); - debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(DEBUG_SHOW_KEEP_OPEN), !visible); - } break; - case DEBUG_WITH_EXTERNAL_EDITOR: { - bool debug_with_external_editor = !debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(DEBUG_WITH_EXTERNAL_EDITOR)); - debugger->set_debug_with_external_editor(debug_with_external_editor); - debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(DEBUG_WITH_EXTERNAL_EDITOR), debug_with_external_editor); - } break; case TOGGLE_SCRIPTS_PANEL: { if (current) { ScriptTextEditor *editor = Object::cast_to<ScriptTextEditor>(current); @@ -1294,29 +1264,6 @@ void ScriptEditor::_menu_option(int p_option) { case CLOSE_ALL: { _close_all_tabs(); } break; - case DEBUG_NEXT: { - - if (debugger) - debugger->debug_next(); - } break; - case DEBUG_STEP: { - - if (debugger) - debugger->debug_step(); - - } break; - case DEBUG_BREAK: { - - if (debugger) - debugger->debug_break(); - - } break; - case DEBUG_CONTINUE: { - - if (debugger) - debugger->debug_continue(); - - } break; case WINDOW_MOVE_UP: { if (tab_container->get_current_tab() > 0) { @@ -1439,8 +1386,6 @@ void ScriptEditor::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { - editor->connect_compat("play_pressed", this, "_editor_play"); - editor->connect_compat("pause_pressed", this, "_editor_pause"); editor->connect_compat("stop_pressed", this, "_editor_stop"); editor->connect_compat("script_add_function_request", this, "_add_callback"); editor->connect_compat("resource_saved", this, "_res_saved_callback"); @@ -1481,8 +1426,6 @@ void ScriptEditor::_notification(int p_what) { case NOTIFICATION_EXIT_TREE: { - editor->disconnect_compat("play_pressed", this, "_editor_play"); - editor->disconnect_compat("pause_pressed", this, "_editor_pause"); editor->disconnect_compat("stop_pressed", this, "_editor_stop"); } break; @@ -2062,7 +2005,7 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra return false; } - if ((debugger->get_dump_stack_script() != p_resource || debugger->get_debug_with_external_editor()) && + if ((EditorDebuggerNode::get_singleton()->get_dump_stack_script() != p_resource || EditorDebuggerNode::get_singleton()->get_debug_with_external_editor()) && p_resource->get_path().is_resource_file() && p_resource->get_class_name() != StringName("VisualScript") && bool(EditorSettings::get_singleton()->get("text_editor/external/use_external_editor"))) { @@ -2277,26 +2220,7 @@ void ScriptEditor::open_script_create_dialog(const String &p_base_name, const St script_create_dialog->config(p_base_name, p_base_path); } -void ScriptEditor::_editor_play() { - - debugger->start(); - debug_menu->get_popup()->grab_focus(); - debug_menu->get_popup()->set_item_disabled(debug_menu->get_popup()->get_item_index(DEBUG_NEXT), true); - debug_menu->get_popup()->set_item_disabled(debug_menu->get_popup()->get_item_index(DEBUG_STEP), true); - debug_menu->get_popup()->set_item_disabled(debug_menu->get_popup()->get_item_index(DEBUG_BREAK), false); - debug_menu->get_popup()->set_item_disabled(debug_menu->get_popup()->get_item_index(DEBUG_CONTINUE), true); -} - -void ScriptEditor::_editor_pause() { -} void ScriptEditor::_editor_stop() { - - debugger->stop(); - debug_menu->get_popup()->set_item_disabled(debug_menu->get_popup()->get_item_index(DEBUG_NEXT), true); - debug_menu->get_popup()->set_item_disabled(debug_menu->get_popup()->get_item_index(DEBUG_STEP), true); - debug_menu->get_popup()->set_item_disabled(debug_menu->get_popup()->get_item_index(DEBUG_BREAK), true); - debug_menu->get_popup()->set_item_disabled(debug_menu->get_popup()->get_item_index(DEBUG_CONTINUE), true); - for (int i = 0; i < tab_container->get_child_count(); i++) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); @@ -3125,8 +3049,6 @@ void ScriptEditor::_bind_methods() { ClassDB::bind_method("_close_other_tabs", &ScriptEditor::_close_other_tabs); ClassDB::bind_method("_open_recent_script", &ScriptEditor::_open_recent_script); ClassDB::bind_method("_theme_option", &ScriptEditor::_theme_option); - ClassDB::bind_method("_editor_play", &ScriptEditor::_editor_play); - ClassDB::bind_method("_editor_pause", &ScriptEditor::_editor_pause); ClassDB::bind_method("_editor_stop", &ScriptEditor::_editor_stop); ClassDB::bind_method("_add_callback", &ScriptEditor::_add_callback); ClassDB::bind_method("_reload_scripts", &ScriptEditor::_reload_scripts); @@ -3141,7 +3063,6 @@ void ScriptEditor::_bind_methods() { ClassDB::bind_method("_copy_script_path", &ScriptEditor::_copy_script_path); ClassDB::bind_method("_breaked", &ScriptEditor::_breaked); - ClassDB::bind_method("_show_debugger", &ScriptEditor::_show_debugger); ClassDB::bind_method("_get_debug_tooltip", &ScriptEditor::_get_debug_tooltip); ClassDB::bind_method("_autosave_scripts", &ScriptEditor::_autosave_scripts); ClassDB::bind_method("_update_autosave_timer", &ScriptEditor::_update_autosave_timer); @@ -3358,26 +3279,16 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { script_search_menu->get_popup()->set_hide_on_window_lose_focus(true); script_search_menu->get_popup()->connect_compat("id_pressed", this, "_menu_option"); - debug_menu = memnew(MenuButton); + MenuButton *debug_menu = memnew(MenuButton); menu_hb->add_child(debug_menu); - debug_menu->set_text(TTR("Debug")); - debug_menu->set_switch_on_hover(true); - debug_menu->get_popup()->set_hide_on_window_lose_focus(true); - debug_menu->get_popup()->add_shortcut(ED_SHORTCUT("debugger/step_into", TTR("Step Into"), KEY_F11), DEBUG_STEP); - debug_menu->get_popup()->add_shortcut(ED_SHORTCUT("debugger/step_over", TTR("Step Over"), KEY_F10), DEBUG_NEXT); - debug_menu->get_popup()->add_separator(); - debug_menu->get_popup()->add_shortcut(ED_SHORTCUT("debugger/break", TTR("Break")), DEBUG_BREAK); - debug_menu->get_popup()->add_shortcut(ED_SHORTCUT("debugger/continue", TTR("Continue"), KEY_F12), DEBUG_CONTINUE); - debug_menu->get_popup()->add_separator(); - //debug_menu->get_popup()->add_check_item("Show Debugger",DEBUG_SHOW); - debug_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("debugger/keep_debugger_open", TTR("Keep Debugger Open")), DEBUG_SHOW_KEEP_OPEN); - debug_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("debugger/debug_with_external_editor", TTR("Debug with External Editor")), DEBUG_WITH_EXTERNAL_EDITOR); - debug_menu->get_popup()->connect_compat("id_pressed", this, "_menu_option"); - - debug_menu->get_popup()->set_item_disabled(debug_menu->get_popup()->get_item_index(DEBUG_NEXT), true); - debug_menu->get_popup()->set_item_disabled(debug_menu->get_popup()->get_item_index(DEBUG_STEP), true); - debug_menu->get_popup()->set_item_disabled(debug_menu->get_popup()->get_item_index(DEBUG_BREAK), true); - debug_menu->get_popup()->set_item_disabled(debug_menu->get_popup()->get_item_index(DEBUG_CONTINUE), true); + debug_menu->hide(); // Handled by EditorDebuggerNode below. + + EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton(); + debugger->set_script_debug_button(debug_menu); + debugger->connect_compat("goto_script_line", this, "_goto_script_line"); + debugger->connect_compat("set_execution", this, "_set_execution"); + debugger->connect_compat("clear_execution", this, "_clear_execution"); + debugger->connect_compat("breaked", this, "_breaked"); menu_hb->add_spacer(); @@ -3445,12 +3356,6 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { error_dialog = memnew(AcceptDialog); add_child(error_dialog); - debugger = memnew(ScriptEditorDebugger(editor)); - debugger->connect_compat("goto_script_line", this, "_goto_script_line"); - debugger->connect_compat("set_execution", this, "_set_execution"); - debugger->connect_compat("clear_execution", this, "_clear_execution"); - debugger->connect_compat("show_debugger", this, "_show_debugger"); - disk_changed = memnew(ConfirmationDialog); { VBoxContainer *vbc = memnew(VBoxContainer); @@ -3475,11 +3380,6 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { script_editor = this; - Button *db = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Debugger"), debugger); - debugger->set_tool_button(db); - - debugger->connect_compat("breaked", this, "_breaked"); - autosave_timer = memnew(Timer); autosave_timer->set_one_shot(false); autosave_timer->connect_compat(SceneStringNames::get_singleton()->tree_entered, this, "_update_autosave_timer"); @@ -3505,7 +3405,6 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { find_in_files_button->hide(); history_pos = -1; - //debugger_gui->hide(); edit_pass = 0; trim_trailing_whitespace_on_save = EditorSettings::get_singleton()->get("text_editor/files/trim_trailing_whitespace_on_save"); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index ec84bcb461..b4b4f33fc5 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -73,7 +73,7 @@ public: ScriptEditorQuickOpen(); }; -class ScriptEditorDebugger; +class EditorDebuggerNode; class ScriptEditorBase : public VBoxContainer { @@ -155,13 +155,6 @@ class ScriptEditor : public PanelContainer { FILE_COPY_PATH, FILE_TOOL_RELOAD, FILE_TOOL_RELOAD_SOFT, - DEBUG_NEXT, - DEBUG_STEP, - DEBUG_BREAK, - DEBUG_CONTINUE, - DEBUG_SHOW, - DEBUG_SHOW_KEEP_OPEN, - DEBUG_WITH_EXTERNAL_EDITOR, SEARCH_IN_FILES, REPLACE_IN_FILES, SEARCH_HELP, @@ -233,7 +226,6 @@ class ScriptEditor : public PanelContainer { AcceptDialog *error_dialog; ConfirmationDialog *erase_tab_confirm; ScriptCreateDialog *script_create_dialog; - ScriptEditorDebugger *debugger; ToolButton *scripts_visible; String current_theme; @@ -315,8 +307,6 @@ class ScriptEditor : public PanelContainer { EditorScriptCodeCompletionCache *completion_cache; - void _editor_play(); - void _editor_pause(); void _editor_stop(); int edit_pass; @@ -335,7 +325,6 @@ class ScriptEditor : public PanelContainer { void _set_execution(REF p_script, int p_line); void _clear_execution(REF p_script); void _breaked(bool p_breaked, bool p_can_debug); - void _show_debugger(bool p_show); void _update_window_menu(); void _script_created(Ref<Script> p_script); @@ -457,7 +446,6 @@ public: VSplitContainer *get_left_list_split() { return list_split; } - ScriptEditorDebugger *get_debugger() { return debugger; } void set_live_auto_reload_running_scripts(bool p_enabled); static void register_create_syntax_highlighter_function(CreateSyntaxHighlighterFunc p_func); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 2069c035fb..4986e60ff0 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -32,10 +32,10 @@ #include "core/math/expression.h" #include "core/os/keyboard.h" +#include "editor/debugger/editor_debugger_node.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" -#include "editor/script_editor_debugger.h" void ConnectionInfoDialog::ok_pressed() { } @@ -870,7 +870,7 @@ void ScriptTextEditor::_breakpoint_item_pressed(int p_idx) { void ScriptTextEditor::_breakpoint_toggled(int p_row) { - ScriptEditor::get_singleton()->get_debugger()->set_breakpoint(script->get_path(), p_row + 1, code_editor->get_text_edit()->is_line_set_as_breakpoint(p_row)); + EditorDebuggerNode::get_singleton()->set_breakpoint(script->get_path(), p_row + 1, code_editor->get_text_edit()->is_line_set_as_breakpoint(p_row)); } void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_column) { @@ -1294,7 +1294,7 @@ void ScriptTextEditor::_edit_option(int p_op) { int line = tx->cursor_get_line(); bool dobreak = !tx->is_line_set_as_breakpoint(line); tx->set_line_as_breakpoint(line, dobreak); - ScriptEditor::get_singleton()->get_debugger()->set_breakpoint(script->get_path(), line + 1, dobreak); + EditorDebuggerNode::get_singleton()->set_breakpoint(script->get_path(), line + 1, dobreak); } break; case DEBUG_REMOVE_ALL_BREAKPOINTS: { @@ -1305,7 +1305,7 @@ void ScriptTextEditor::_edit_option(int p_op) { int line = E->get(); bool dobreak = !tx->is_line_set_as_breakpoint(line); tx->set_line_as_breakpoint(line, dobreak); - ScriptEditor::get_singleton()->get_debugger()->set_breakpoint(script->get_path(), line + 1, dobreak); + EditorDebuggerNode::get_singleton()->set_breakpoint(script->get_path(), line + 1, dobreak); } } break; case DEBUG_GOTO_NEXT_BREAKPOINT: { diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp index 45f2a5063c..6e8eeaec82 100644 --- a/editor/plugins/spatial_editor_plugin.cpp +++ b/editor/plugins/spatial_editor_plugin.cpp @@ -36,12 +36,12 @@ #include "core/print_string.h" #include "core/project_settings.h" #include "core/sort_array.h" +#include "editor/debugger/editor_debugger_node.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/plugins/animation_player_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" -#include "editor/script_editor_debugger.h" #include "editor/spatial_editor_gizmos.h" #include "scene/3d/camera.h" #include "scene/3d/collision_shape.h" @@ -3421,9 +3421,9 @@ bool SpatialEditorViewport::_create_instance(Node *parent, String &path, const P editor_data->get_undo_redo().add_undo_method(parent, "remove_child", instanced_scene); String new_name = parent->validate_child_name(instanced_scene); - ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger(); - editor_data->get_undo_redo().add_do_method(sed, "live_debug_instance_node", editor->get_edited_scene()->get_path_to(parent), path, new_name); - editor_data->get_undo_redo().add_undo_method(sed, "live_debug_remove_node", NodePath(String(editor->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); + EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); + editor_data->get_undo_redo().add_do_method(ed, "live_debug_instance_node", editor->get_edited_scene()->get_path_to(parent), path, new_name); + editor_data->get_undo_redo().add_undo_method(ed, "live_debug_remove_node", NodePath(String(editor->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); Transform global_transform; Spatial *parent_spatial = Object::cast_to<Spatial>(parent); @@ -4497,7 +4497,7 @@ void SpatialEditor::_menu_item_toggled(bool pressed, int p_option) { } break; case MENU_TOOL_OVERRIDE_CAMERA: { - ScriptEditorDebugger *const debugger = ScriptEditor::get_singleton()->get_debugger(); + EditorDebuggerNode *const debugger = EditorDebuggerNode::get_singleton(); if (pressed) { using Override = ScriptEditorDebugger::CameraOverride; @@ -4554,7 +4554,7 @@ void SpatialEditor::_update_camera_override_viewport(Object *p_viewport) { if (!current_viewport) return; - ScriptEditorDebugger *const debugger = ScriptEditor::get_singleton()->get_debugger(); + EditorDebuggerNode *const debugger = EditorDebuggerNode::get_singleton(); camera_override_viewport_id = current_viewport->index; if (debugger->get_camera_override() >= ScriptEditorDebugger::OVERRIDE_3D_1) { @@ -5513,7 +5513,7 @@ void SpatialEditor::_notification(int p_what) { _init_grid(); } else if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { if (!is_visible() && tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->is_pressed()) { - ScriptEditorDebugger *debugger = ScriptEditor::get_singleton()->get_debugger(); + EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton(); debugger->set_camera_override(ScriptEditorDebugger::OVERRIDE_NONE); tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_pressed(false); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index beba7f0ed6..ebc7d56b24 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -35,6 +35,7 @@ #include "core/os/keyboard.h" #include "core/project_settings.h" +#include "editor/debugger/editor_debugger_node.h" #include "editor/editor_feature_profile.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" @@ -44,7 +45,6 @@ #include "editor/plugins/canvas_item_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/plugins/spatial_editor_plugin.h" -#include "editor/script_editor_debugger.h" #include "scene/main/viewport.h" #include "scene/resources/packed_scene.h" @@ -229,9 +229,9 @@ void SceneTreeDock::_perform_instance_scenes(const Vector<String> &p_files, Node editor_data->get_undo_redo().add_undo_method(parent, "remove_child", instanced_scene); String new_name = parent->validate_child_name(instanced_scene); - ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger(); - editor_data->get_undo_redo().add_do_method(sed, "live_debug_instance_node", edited_scene->get_path_to(parent), p_files[i], new_name); - editor_data->get_undo_redo().add_undo_method(sed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)).plus_file(new_name))); + EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); + editor_data->get_undo_redo().add_do_method(ed, "live_debug_instance_node", edited_scene->get_path_to(parent), p_files[i], new_name); + editor_data->get_undo_redo().add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)).plus_file(new_name))); } editor_data->get_undo_redo().commit_action(); @@ -591,10 +591,10 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { editor_data->get_undo_redo().add_undo_method(parent, "remove_child", dup); editor_data->get_undo_redo().add_do_reference(dup); - ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger(); + EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); - editor_data->get_undo_redo().add_do_method(sed, "live_debug_duplicate_node", edited_scene->get_path_to(node), dup->get_name()); - editor_data->get_undo_redo().add_undo_method(sed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)).plus_file(dup->get_name()))); + editor_data->get_undo_redo().add_do_method(ed, "live_debug_duplicate_node", edited_scene->get_path_to(node), dup->get_name()); + editor_data->get_undo_redo().add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)).plus_file(dup->get_name()))); } editor_data->get_undo_redo().commit_action(); @@ -1584,7 +1584,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V if (p_position_in_parent >= 0) editor_data->get_undo_redo().add_do_method(new_parent, "move_child", node, p_position_in_parent + inc); - ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger(); + EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); String old_name = former_names[ni]; String new_name = new_parent->validate_child_name(node); @@ -1609,8 +1609,8 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V path_renames[ni].second = fixed_node_path; } - editor_data->get_undo_redo().add_do_method(sed, "live_debug_reparent_node", edited_scene->get_path_to(node), edited_scene->get_path_to(new_parent), new_name, p_position_in_parent + inc); - editor_data->get_undo_redo().add_undo_method(sed, "live_debug_reparent_node", NodePath(String(edited_scene->get_path_to(new_parent)).plus_file(new_name)), edited_scene->get_path_to(node->get_parent()), node->get_name(), node->get_index()); + editor_data->get_undo_redo().add_do_method(ed, "live_debug_reparent_node", edited_scene->get_path_to(node), edited_scene->get_path_to(new_parent), new_name, p_position_in_parent + inc); + editor_data->get_undo_redo().add_undo_method(ed, "live_debug_reparent_node", NodePath(String(edited_scene->get_path_to(new_parent)).plus_file(new_name)), edited_scene->get_path_to(node->get_parent()), node->get_name(), node->get_index()); if (p_keep_global_xform) { if (Object::cast_to<Node2D>(node)) @@ -1849,9 +1849,9 @@ void SceneTreeDock::_delete_confirm() { editor_data->get_undo_redo().add_undo_method(this, "_set_owners", edited_scene, owners); editor_data->get_undo_redo().add_undo_reference(n); - ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger(); - editor_data->get_undo_redo().add_do_method(sed, "live_debug_remove_and_keep_node", edited_scene->get_path_to(n), n->get_instance_id()); - editor_data->get_undo_redo().add_undo_method(sed, "live_debug_restore_node", n->get_instance_id(), edited_scene->get_path_to(n->get_parent()), n->get_index()); + EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); + editor_data->get_undo_redo().add_do_method(ed, "live_debug_remove_and_keep_node", edited_scene->get_path_to(n), n->get_instance_id()); + editor_data->get_undo_redo().add_undo_method(ed, "live_debug_restore_node", n->get_instance_id(), edited_scene->get_path_to(n->get_parent()), n->get_index()); } } editor_data->get_undo_redo().commit_action(); @@ -1950,9 +1950,9 @@ void SceneTreeDock::_do_create(Node *p_parent) { editor_data->get_undo_redo().add_undo_method(p_parent, "remove_child", child); String new_name = p_parent->validate_child_name(child); - ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger(); - editor_data->get_undo_redo().add_do_method(sed, "live_debug_create_node", edited_scene->get_path_to(p_parent), child->get_class(), new_name); - editor_data->get_undo_redo().add_undo_method(sed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(p_parent)).plus_file(new_name))); + EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); + editor_data->get_undo_redo().add_do_method(ed, "live_debug_create_node", edited_scene->get_path_to(p_parent), child->get_class(), new_name); + editor_data->get_undo_redo().add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(p_parent)).plus_file(new_name))); } else { diff --git a/editor/settings_config_dialog.cpp b/editor/settings_config_dialog.cpp index c1a902bdcc..9f8a531762 100644 --- a/editor/settings_config_dialog.cpp +++ b/editor/settings_config_dialog.cpp @@ -32,13 +32,13 @@ #include "core/os/keyboard.h" #include "core/project_settings.h" +#include "editor/debugger/editor_debugger_node.h" #include "editor_file_system.h" #include "editor_log.h" #include "editor_node.h" #include "editor_scale.h" #include "editor_settings.h" #include "scene/gui/margin_container.h" -#include "script_editor_debugger.h" void EditorSettingsDialog::ok_pressed() { @@ -119,9 +119,8 @@ void EditorSettingsDialog::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { - ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger(); - undo_redo->set_method_notify_callback(sed->_method_changeds, sed); - undo_redo->set_property_notify_callback(sed->_property_changeds, sed); + undo_redo->set_method_notify_callback(EditorDebuggerNode::_method_changeds, NULL); + undo_redo->set_property_notify_callback(EditorDebuggerNode::_property_changeds, NULL); undo_redo->set_commit_notify_callback(_undo_redo_callback, this); } break; case NOTIFICATION_ENTER_TREE: { diff --git a/main/main.cpp b/main/main.cpp index 9f1d9ce5fe..efcbf04585 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -44,6 +44,7 @@ #include "core/project_settings.h" #include "core/register_core_types.h" #include "core/script_debugger_local.h" +#include "core/script_debugger_remote.h" #include "core/script_language.h" #include "core/translation.h" #include "core/version.h" @@ -58,7 +59,6 @@ #include "main/tests/test_main.h" #include "modules/register_module_types.h" #include "platform/register_platform_apis.h" -#include "scene/debugger/script_debugger_remote.h" #include "scene/main/scene_tree.h" #include "scene/main/viewport.h" #include "scene/register_scene_types.h" @@ -1657,12 +1657,6 @@ bool Main::start() { if (!project_manager && !editor) { // game if (game_path != "" || script != "") { - if (script_debugger && script_debugger->is_remote()) { - ScriptDebuggerRemote *remote_debugger = static_cast<ScriptDebuggerRemote *>(script_debugger); - - remote_debugger->set_scene_tree(sml); - } - //autoload List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); diff --git a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs index 4c76d2abf1..bd7eb59913 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs @@ -172,7 +172,7 @@ namespace GodotTools return; // Notify running game for hot-reload - Internal.ScriptEditorDebuggerReloadScripts(); + Internal.EditorDebuggerNodeReloadScripts(); // Hot-reload in the editor GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer(); diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index de361ba844..2e121ba879 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -34,7 +34,7 @@ namespace GodotTools.Internals public static void ReloadAssemblies(bool softReload) => internal_ReloadAssemblies(softReload); - public static void ScriptEditorDebuggerReloadScripts() => internal_ScriptEditorDebuggerReloadScripts(); + public static void EditorDebuggerNodeReloadScripts() => internal_EditorDebuggerNodeReloadScripts(); public static bool ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus = true) => internal_ScriptEditorEdit(resource, line, col, grabFocus); @@ -88,7 +88,7 @@ namespace GodotTools.Internals private static extern void internal_ReloadAssemblies(bool softReload); [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_ScriptEditorDebuggerReloadScripts(); + private static extern void internal_EditorDebuggerNodeReloadScripts(); [MethodImpl(MethodImplOptions.InternalCall)] private static extern bool internal_ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus); diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index c8d20e80be..31996a03d0 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -36,10 +36,10 @@ #include "core/os/os.h" #include "core/version.h" +#include "editor/debugger/editor_debugger_node.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/plugins/script_editor_plugin.h" -#include "editor/script_editor_debugger.h" #include "main/main.h" #include "../csharp_script.h" @@ -305,8 +305,8 @@ void godot_icall_Internal_ReloadAssemblies(MonoBoolean p_soft_reload) { #endif } -void godot_icall_Internal_ScriptEditorDebuggerReloadScripts() { - ScriptEditor::get_singleton()->get_debugger()->reload_scripts(); +void godot_icall_Internal_EditorDebuggerNodeReloadScripts() { + EditorDebuggerNode::get_singleton()->reload_scripts(); } MonoBoolean godot_icall_Internal_ScriptEditorEdit(MonoObject *p_resource, int32_t p_line, int32_t p_col, MonoBoolean p_grab_focus) { @@ -348,9 +348,9 @@ void godot_icall_Internal_EditorRunStop() { } void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() { - ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger(); - if (sed) { - sed->reload_scripts(); + EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); + if (ed) { + ed->reload_scripts(); } } @@ -446,7 +446,7 @@ void register_editor_internal_calls() { mono_add_internal_call("GodotTools.Internals.Internal::internal_GetEditorApiHash", (void *)godot_icall_Internal_GetEditorApiHash); mono_add_internal_call("GodotTools.Internals.Internal::internal_IsAssembliesReloadingNeeded", (void *)godot_icall_Internal_IsAssembliesReloadingNeeded); mono_add_internal_call("GodotTools.Internals.Internal::internal_ReloadAssemblies", (void *)godot_icall_Internal_ReloadAssemblies); - mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebuggerReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebuggerReloadScripts); + mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorDebuggerNodeReloadScripts", (void *)godot_icall_Internal_EditorDebuggerNodeReloadScripts); mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", (void *)godot_icall_Internal_ScriptEditorEdit); mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", (void *)godot_icall_Internal_EditorNodeShowScriptScreen); mono_add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", (void *)godot_icall_Internal_GetScriptsMetadataOrNothing); diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp index 05077a00c4..ae6625a6c6 100644 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -38,7 +38,7 @@ #include "core/reference.h" #ifdef TOOLS_ENABLED -#include "editor/script_editor_debugger.h" +#include "editor/debugger/script_editor_debugger.h" #endif #include "../csharp_script.h" diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index b9cd277657..41720360c3 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -34,6 +34,7 @@ #include "os_windows.h" #include "core/io/marshalls.h" +#include "core/script_language.h" #include "core/version_generated.gen.h" #if defined(OPENGL_ENABLED) diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp new file mode 100644 index 0000000000..9b9f0455a1 --- /dev/null +++ b/scene/debugger/scene_debugger.cpp @@ -0,0 +1,867 @@ +/*************************************************************************/ +/* scene_debugger.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "scene_debugger.h" + +#include "core/io/marshalls.h" +#include "core/script_debugger_remote.h" +#include "scene/main/scene_tree.h" +#include "scene/main/viewport.h" +#include "scene/resources/packed_scene.h" + +void SceneDebugger::initialize() { +#ifdef DEBUG_ENABLED + LiveEditor::singleton = memnew(LiveEditor); + ScriptDebuggerRemote::scene_tree_parse_func = SceneDebugger::parse_message; +#endif +} + +void SceneDebugger::deinitialize() { +#ifdef DEBUG_ENABLED + if (LiveEditor::singleton) { + memdelete(LiveEditor::singleton); + LiveEditor::singleton = NULL; + } +#endif +} + +#ifdef DEBUG_ENABLED +Error SceneDebugger::parse_message(const String &p_msg, const Array &p_args) { + SceneTree *scene_tree = SceneTree::get_singleton(); + if (!scene_tree) + return ERR_UNCONFIGURED; + LiveEditor *live_editor = LiveEditor::get_singleton(); + if (!live_editor) + return ERR_UNCONFIGURED; + if (p_msg == "request_scene_tree") { // Scene tree + live_editor->_send_tree(); + + } else if (p_msg == "save_node") { // Save node. + ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); + _save_node(p_args[0], p_args[1]); + + } else if (p_msg == "inspect_object") { // Object Inspect + ERR_FAIL_COND_V(p_args.size() < 1, ERR_INVALID_DATA); + ObjectID id = p_args[0]; + _send_object_id(id); + + } else if (p_msg == "override_camera_2D:set") { // Camera + ERR_FAIL_COND_V(p_args.size() < 1, ERR_INVALID_DATA); + bool enforce = p_args[0]; + scene_tree->get_root()->enable_canvas_transform_override(enforce); + + } else if (p_msg == "override_camera_2D:transform") { + ERR_FAIL_COND_V(p_args.size() < 1, ERR_INVALID_DATA); + Transform2D transform = p_args[1]; + scene_tree->get_root()->set_canvas_transform_override(transform); + + } else if (p_msg == "override_camera_3D:set") { + ERR_FAIL_COND_V(p_args.size() < 1, ERR_INVALID_DATA); + bool enable = p_args[0]; + scene_tree->get_root()->enable_camera_override(enable); + + } else if (p_msg == "override_camera_3D:transform") { + ERR_FAIL_COND_V(p_args.size() < 5, ERR_INVALID_DATA); + Transform transform = p_args[0]; + bool is_perspective = p_args[1]; + float size_or_fov = p_args[2]; + float near = p_args[3]; + float far = p_args[4]; + if (is_perspective) { + scene_tree->get_root()->set_camera_override_perspective(size_or_fov, near, far); + } else { + scene_tree->get_root()->set_camera_override_orthogonal(size_or_fov, near, far); + } + scene_tree->get_root()->set_camera_override_transform(transform); + + } else if (p_msg == "set_object_property") { + ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); + _set_object_property(p_args[0], p_args[1], p_args[2]); + + } else if (!p_msg.begins_with("live_")) { // Live edits below. + return ERR_SKIP; + } else if (p_msg == "live_set_root") { + ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); + live_editor->_root_func(p_args[0], p_args[1]); + + } else if (p_msg == "live_node_path") { + ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); + live_editor->_node_path_func(p_args[0], p_args[1]); + + } else if (p_msg == "live_res_path") { + ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); + live_editor->_res_path_func(p_args[0], p_args[1]); + + } else if (p_msg == "live_node_prop_res") { + ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); + live_editor->_node_set_res_func(p_args[0], p_args[1], p_args[2]); + + } else if (p_msg == "live_node_prop") { + ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); + live_editor->_node_set_func(p_args[0], p_args[1], p_args[2]); + + } else if (p_msg == "live_res_prop_res") { + ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); + live_editor->_res_set_res_func(p_args[0], p_args[1], p_args[2]); + + } else if (p_msg == "live_res_prop") { + ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); + live_editor->_res_set_func(p_args[0], p_args[1], p_args[2]); + + } else if (p_msg == "live_node_call") { + ERR_FAIL_COND_V(p_args.size() < 7, ERR_INVALID_DATA); + live_editor->_node_call_func(p_args[0], p_args[1], p_args[2], p_args[3], p_args[4], p_args[5], p_args[6]); + + } else if (p_msg == "live_res_call") { + ERR_FAIL_COND_V(p_args.size() < 7, ERR_INVALID_DATA); + live_editor->_res_call_func(p_args[0], p_args[1], p_args[2], p_args[3], p_args[4], p_args[5], p_args[6]); + + } else if (p_msg == "live_create_node") { + ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); + live_editor->_create_node_func(p_args[0], p_args[1], p_args[2]); + + } else if (p_msg == "live_instance_node") { + ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); + live_editor->_instance_node_func(p_args[0], p_args[1], p_args[2]); + + } else if (p_msg == "live_remove_node") { + ERR_FAIL_COND_V(p_args.size() < 1, ERR_INVALID_DATA); + live_editor->_remove_node_func(p_args[0]); + + } else if (p_msg == "live_remove_and_keep_node") { + ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); + live_editor->_remove_and_keep_node_func(p_args[0], p_args[1]); + + } else if (p_msg == "live_restore_node") { + ERR_FAIL_COND_V(p_args.size() < 3, ERR_INVALID_DATA); + live_editor->_restore_node_func(p_args[0], p_args[1], p_args[2]); + + } else if (p_msg == "live_duplicate_node") { + ERR_FAIL_COND_V(p_args.size() < 2, ERR_INVALID_DATA); + live_editor->_duplicate_node_func(p_args[0], p_args[1]); + + } else if (p_msg == "live_reparent_node") { + ERR_FAIL_COND_V(p_args.size() < 4, ERR_INVALID_DATA); + live_editor->_reparent_node_func(p_args[0], p_args[1], p_args[2], p_args[3]); + } else { + return ERR_SKIP; + } + return OK; +} + +void SceneDebugger::_save_node(ObjectID id, const String &p_path) { + Node *node = Object::cast_to<Node>(ObjectDB::get_instance(id)); + ERR_FAIL_COND(!node); + + WARN_PRINT("SAVING " + itos(id) + " TO " + p_path); + Ref<PackedScene> ps = memnew(PackedScene); + ps->pack(node); + ResourceSaver::save(p_path, ps); +} + +void SceneDebugger::_send_object_id(ObjectID p_id, int p_max_size) { + SceneDebuggerObject obj(p_id); + if (obj.id.is_null()) + return; + + Array arr; + obj.serialize(arr); + ScriptDebugger::get_singleton()->send_message("inspect_object", arr); +} + +void SceneDebugger::_set_object_property(ObjectID p_id, const String &p_property, const Variant &p_value) { + + Object *obj = ObjectDB::get_instance(p_id); + if (!obj) + return; + + String prop_name = p_property; + if (p_property.begins_with("Members/")) { + Vector<String> ss = p_property.split("/"); + prop_name = ss[ss.size() - 1]; + } + + obj->set(prop_name, p_value); +} + +void SceneDebugger::add_to_cache(const String &p_filename, Node *p_node) { + LiveEditor *debugger = LiveEditor::get_singleton(); + if (!debugger) + return; + + if (ScriptDebugger::get_singleton() && p_filename != String()) { + debugger->live_scene_edit_cache[p_filename].insert(p_node); + } +} +void SceneDebugger::remove_from_cache(const String &p_filename, Node *p_node) { + LiveEditor *debugger = LiveEditor::get_singleton(); + if (!debugger) + return; + + Map<String, Set<Node *> > &edit_cache = debugger->live_scene_edit_cache; + Map<String, Set<Node *> >::Element *E = edit_cache.find(p_filename); + if (E) { + E->get().erase(p_node); + if (E->get().size() == 0) { + edit_cache.erase(E); + } + } + + Map<Node *, Map<ObjectID, Node *> > &remove_list = debugger->live_edit_remove_list; + Map<Node *, Map<ObjectID, Node *> >::Element *F = remove_list.find(p_node); + if (F) { + for (Map<ObjectID, Node *>::Element *G = F->get().front(); G; G = G->next()) { + + memdelete(G->get()); + } + remove_list.erase(F); + } +} + +/// SceneDebuggerObject +SceneDebuggerObject::SceneDebuggerObject(ObjectID p_id) { + id = ObjectID(); + Object *obj = ObjectDB::get_instance(p_id); + if (!obj) + return; + + id = p_id; + class_name = obj->get_class(); + + if (ScriptInstance *si = obj->get_script_instance()) { + // Read script instance constants and variables + if (!si->get_script().is_null()) { + Script *s = si->get_script().ptr(); + _parse_script_properties(s, si); + } + } + + if (Node *node = Object::cast_to<Node>(obj)) { + // Add specialized NodePath info (if inside tree). + if (node->is_inside_tree()) { + PropertyInfo pi(Variant::NODE_PATH, String("Node/path")); + properties.push_back(SceneDebuggerProperty(pi, node->get_path())); + } else { // Can't ask for path if a node is not in tree. + PropertyInfo pi(Variant::STRING, String("Node/path")); + properties.push_back(SceneDebuggerProperty(pi, "[Orphan]")); + } + } else if (Script *s = Object::cast_to<Script>(obj)) { + // Add script constants (no instance). + _parse_script_properties(s, NULL); + } + + // Add base object properties. + List<PropertyInfo> pinfo; + obj->get_property_list(&pinfo, true); + for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { + if (E->get().usage & (PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CATEGORY)) { + properties.push_back(SceneDebuggerProperty(E->get(), obj->get(E->get().name))); + } + } +} + +void SceneDebuggerObject::_parse_script_properties(Script *p_script, ScriptInstance *p_instance) { + typedef Map<const Script *, Set<StringName> > ScriptMemberMap; + typedef Map<const Script *, Map<StringName, Variant> > ScriptConstantsMap; + + ScriptMemberMap members; + if (p_instance) { + members[p_script] = Set<StringName>(); + p_script->get_members(&(members[p_script])); + } + + ScriptConstantsMap constants; + constants[p_script] = Map<StringName, Variant>(); + p_script->get_constants(&(constants[p_script])); + + Ref<Script> base = p_script->get_base_script(); + while (base.is_valid()) { + if (p_instance) { + members[base.ptr()] = Set<StringName>(); + base->get_members(&(members[base.ptr()])); + } + + constants[base.ptr()] = Map<StringName, Variant>(); + base->get_constants(&(constants[base.ptr()])); + + base = base->get_base_script(); + } + + // Members + for (ScriptMemberMap::Element *sm = members.front(); sm; sm = sm->next()) { + for (Set<StringName>::Element *E = sm->get().front(); E; E = E->next()) { + Variant m; + if (p_instance->get(E->get(), m)) { + String script_path = sm->key() == p_script ? "" : sm->key()->get_path().get_file() + "/"; + PropertyInfo pi(m.get_type(), "Members/" + script_path + E->get()); + properties.push_back(SceneDebuggerProperty(pi, m)); + } + } + } + // Constants + for (ScriptConstantsMap::Element *sc = constants.front(); sc; sc = sc->next()) { + for (Map<StringName, Variant>::Element *E = sc->get().front(); E; E = E->next()) { + String script_path = sc->key() == p_script ? "" : sc->key()->get_path().get_file() + "/"; + if (E->value().get_type() == Variant::OBJECT) { + Variant id = ((Object *)E->value())->get_instance_id(); + PropertyInfo pi(id.get_type(), "Constants/" + E->key(), PROPERTY_HINT_OBJECT_ID, "Object"); + properties.push_back(SceneDebuggerProperty(pi, id)); + } else { + PropertyInfo pi(E->value().get_type(), "Constants/" + script_path + E->key()); + properties.push_back(SceneDebuggerProperty(pi, E->value())); + } + } + } +} + +void SceneDebuggerObject::serialize(Array &r_arr, int p_max_size) { + Array send_props; + for (int i = 0; i < properties.size(); i++) { + const PropertyInfo &pi = properties[i].first; + Variant &var = properties[i].second; + + WeakRef *ref = Object::cast_to<WeakRef>(var); + if (ref) { + var = ref->get_ref(); + } + + RES res = var; + + Array prop; + prop.push_back(pi.name); + prop.push_back(pi.type); + + PropertyHint hint = pi.hint; + String hint_string = pi.hint_string; + if (!res.is_null()) { + var = res->get_path(); + } else { //only send information that can be sent.. + int len = 0; //test how big is this to encode + encode_variant(var, NULL, len); + if (len > p_max_size) { //limit to max size + hint = PROPERTY_HINT_OBJECT_TOO_BIG; + hint_string = ""; + var = Variant(); + } + } + prop.push_back(hint); + prop.push_back(hint_string); + prop.push_back(pi.usage); + prop.push_back(var); + send_props.push_back(prop); + } + r_arr.push_back(uint64_t(id)); + r_arr.push_back(class_name); + r_arr.push_back(send_props); +} + +void SceneDebuggerObject::deserialize(const Array &p_arr) { +#define CHECK_TYPE(p_what, p_type) ERR_FAIL_COND(p_what.get_type() != Variant::p_type); + ERR_FAIL_COND(p_arr.size() < 3); + CHECK_TYPE(p_arr[0], INT); + CHECK_TYPE(p_arr[1], STRING); + CHECK_TYPE(p_arr[2], ARRAY); + + id = uint64_t(p_arr[0]); + class_name = p_arr[1]; + Array props = p_arr[2]; + + for (int i = 0; i < props.size(); i++) { + + CHECK_TYPE(props[i], ARRAY); + Array prop = props[i]; + + ERR_FAIL_COND(prop.size() != 6); + CHECK_TYPE(prop[0], STRING); + CHECK_TYPE(prop[1], INT); + CHECK_TYPE(prop[2], INT); + CHECK_TYPE(prop[3], STRING); + CHECK_TYPE(prop[4], INT); + + PropertyInfo pinfo; + pinfo.name = prop[0]; + pinfo.type = Variant::Type(int(prop[1])); + pinfo.hint = PropertyHint(int(prop[2])); + pinfo.hint_string = prop[3]; + pinfo.usage = PropertyUsageFlags(int(prop[4])); + Variant var = prop[5]; + + if (pinfo.type == Variant::OBJECT) { + if (var.is_zero()) { + var = RES(); + } else if (var.get_type() == Variant::OBJECT) { + if (((Object *)var)->is_class("EncodedObjectAsID")) { + var = Object::cast_to<EncodedObjectAsID>(var)->get_object_id(); + pinfo.type = var.get_type(); + pinfo.hint = PROPERTY_HINT_OBJECT_ID; + pinfo.hint_string = "Object"; + } + } + } + properties.push_back(SceneDebuggerProperty(pinfo, var)); + } +} + +/// SceneDebuggerTree +SceneDebuggerTree::SceneDebuggerTree(Node *p_root) { + // Flatten tree into list, depth first, use stack to avoid recursion. + List<Node *> stack; + stack.push_back(p_root); + while (stack.size()) { + Node *n = stack[0]; + stack.pop_front(); + int count = n->get_child_count(); + nodes.push_back(RemoteNode(count, n->get_name(), n->get_class(), n->get_instance_id())); + for (int i = 0; i < count; i++) { + stack.push_front(n->get_child(count - i - 1)); + } + } +} + +void SceneDebuggerTree::serialize(Array &p_arr) { + for (List<RemoteNode>::Element *E = nodes.front(); E; E = E->next()) { + RemoteNode &n = E->get(); + p_arr.push_back(n.child_count); + p_arr.push_back(n.name); + p_arr.push_back(n.type_name); + p_arr.push_back(n.id); + } +} + +void SceneDebuggerTree::deserialize(const Array &p_arr) { + int idx = 0; + while (p_arr.size() > idx) { + ERR_FAIL_COND(p_arr.size() < 4); + CHECK_TYPE(p_arr[idx], INT); + CHECK_TYPE(p_arr[idx + 1], STRING); + CHECK_TYPE(p_arr[idx + 2], STRING); + CHECK_TYPE(p_arr[idx + 3], INT); + nodes.push_back(RemoteNode(p_arr[idx], p_arr[idx + 1], p_arr[idx + 2], p_arr[idx + 3])); + idx += 4; + } +} + +/// LiveEditor +LiveEditor *LiveEditor::singleton = NULL; +LiveEditor *LiveEditor::get_singleton() { + return singleton; +} + +void LiveEditor::_send_tree() { + SceneTree *scene_tree = SceneTree::get_singleton(); + if (!scene_tree) + return; + + Array arr; + // Encoded as a flat list depth fist. + SceneDebuggerTree tree(scene_tree->root); + tree.serialize(arr); + ScriptDebugger::get_singleton()->send_message("scene_tree", arr); +} + +void LiveEditor::_node_path_func(const NodePath &p_path, int p_id) { + + live_edit_node_path_cache[p_id] = p_path; +} + +void LiveEditor::_res_path_func(const String &p_path, int p_id) { + + live_edit_resource_cache[p_id] = p_path; +} + +void LiveEditor::_node_set_func(int p_id, const StringName &p_prop, const Variant &p_value) { + + SceneTree *scene_tree = SceneTree::get_singleton(); + if (!scene_tree) + return; + + if (!live_edit_node_path_cache.has(p_id)) + return; + + NodePath np = live_edit_node_path_cache[p_id]; + Node *base = NULL; + if (scene_tree->root->has_node(live_edit_root)) + base = scene_tree->root->get_node(live_edit_root); + + Map<String, Set<Node *> >::Element *E = live_scene_edit_cache.find(live_edit_scene); + if (!E) + return; //scene not editable + + for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) { + + Node *n = F->get(); + + if (base && !base->is_a_parent_of(n)) + continue; + + if (!n->has_node(np)) + continue; + Node *n2 = n->get_node(np); + + n2->set(p_prop, p_value); + } +} + +void LiveEditor::_node_set_res_func(int p_id, const StringName &p_prop, const String &p_value) { + + RES r = ResourceLoader::load(p_value); + if (!r.is_valid()) + return; + _node_set_func(p_id, p_prop, r); +} +void LiveEditor::_node_call_func(int p_id, const StringName &p_method, VARIANT_ARG_DECLARE) { + SceneTree *scene_tree = SceneTree::get_singleton(); + if (!scene_tree) + return; + if (!live_edit_node_path_cache.has(p_id)) + return; + + NodePath np = live_edit_node_path_cache[p_id]; + Node *base = NULL; + if (scene_tree->root->has_node(live_edit_root)) + base = scene_tree->root->get_node(live_edit_root); + + Map<String, Set<Node *> >::Element *E = live_scene_edit_cache.find(live_edit_scene); + if (!E) + return; //scene not editable + + for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) { + + Node *n = F->get(); + + if (base && !base->is_a_parent_of(n)) + continue; + + if (!n->has_node(np)) + continue; + Node *n2 = n->get_node(np); + + n2->call(p_method, VARIANT_ARG_PASS); + } +} +void LiveEditor::_res_set_func(int p_id, const StringName &p_prop, const Variant &p_value) { + + if (!live_edit_resource_cache.has(p_id)) + return; + + String resp = live_edit_resource_cache[p_id]; + + if (!ResourceCache::has(resp)) + return; + + RES r = ResourceCache::get(resp); + if (!r.is_valid()) + return; + + r->set(p_prop, p_value); +} +void LiveEditor::_res_set_res_func(int p_id, const StringName &p_prop, const String &p_value) { + + RES r = ResourceLoader::load(p_value); + if (!r.is_valid()) + return; + _res_set_func(p_id, p_prop, r); +} +void LiveEditor::_res_call_func(int p_id, const StringName &p_method, VARIANT_ARG_DECLARE) { + + if (!live_edit_resource_cache.has(p_id)) + return; + + String resp = live_edit_resource_cache[p_id]; + + if (!ResourceCache::has(resp)) + return; + + RES r = ResourceCache::get(resp); + if (!r.is_valid()) + return; + + r->call(p_method, VARIANT_ARG_PASS); +} + +void LiveEditor::_root_func(const NodePath &p_scene_path, const String &p_scene_from) { + + live_edit_root = p_scene_path; + live_edit_scene = p_scene_from; +} + +void LiveEditor::_create_node_func(const NodePath &p_parent, const String &p_type, const String &p_name) { + SceneTree *scene_tree = SceneTree::get_singleton(); + if (!scene_tree) + return; + + Node *base = NULL; + if (scene_tree->root->has_node(live_edit_root)) + base = scene_tree->root->get_node(live_edit_root); + + Map<String, Set<Node *> >::Element *E = live_scene_edit_cache.find(live_edit_scene); + if (!E) + return; //scene not editable + + for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) { + + Node *n = F->get(); + + if (base && !base->is_a_parent_of(n)) + continue; + + if (!n->has_node(p_parent)) + continue; + Node *n2 = n->get_node(p_parent); + + Node *no = Object::cast_to<Node>(ClassDB::instance(p_type)); + if (!no) { + continue; + } + + no->set_name(p_name); + n2->add_child(no); + } +} +void LiveEditor::_instance_node_func(const NodePath &p_parent, const String &p_path, const String &p_name) { + SceneTree *scene_tree = SceneTree::get_singleton(); + if (!scene_tree) + return; + + Ref<PackedScene> ps = ResourceLoader::load(p_path); + + if (!ps.is_valid()) + return; + + Node *base = NULL; + if (scene_tree->root->has_node(live_edit_root)) + base = scene_tree->root->get_node(live_edit_root); + + Map<String, Set<Node *> >::Element *E = live_scene_edit_cache.find(live_edit_scene); + if (!E) + return; //scene not editable + + for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) { + + Node *n = F->get(); + + if (base && !base->is_a_parent_of(n)) + continue; + + if (!n->has_node(p_parent)) + continue; + Node *n2 = n->get_node(p_parent); + + Node *no = ps->instance(); + if (!no) { + continue; + } + + no->set_name(p_name); + n2->add_child(no); + } +} +void LiveEditor::_remove_node_func(const NodePath &p_at) { + SceneTree *scene_tree = SceneTree::get_singleton(); + if (!scene_tree) + return; + + Node *base = NULL; + if (scene_tree->root->has_node(live_edit_root)) + base = scene_tree->root->get_node(live_edit_root); + + Map<String, Set<Node *> >::Element *E = live_scene_edit_cache.find(live_edit_scene); + if (!E) + return; //scene not editable + + for (Set<Node *>::Element *F = E->get().front(); F;) { + + Set<Node *>::Element *N = F->next(); + + Node *n = F->get(); + + if (base && !base->is_a_parent_of(n)) + continue; + + if (!n->has_node(p_at)) + continue; + Node *n2 = n->get_node(p_at); + + memdelete(n2); + + F = N; + } +} +void LiveEditor::_remove_and_keep_node_func(const NodePath &p_at, ObjectID p_keep_id) { + SceneTree *scene_tree = SceneTree::get_singleton(); + if (!scene_tree) + return; + + Node *base = NULL; + if (scene_tree->root->has_node(live_edit_root)) + base = scene_tree->root->get_node(live_edit_root); + + Map<String, Set<Node *> >::Element *E = live_scene_edit_cache.find(live_edit_scene); + if (!E) + return; //scene not editable + + for (Set<Node *>::Element *F = E->get().front(); F;) { + + Set<Node *>::Element *N = F->next(); + + Node *n = F->get(); + + if (base && !base->is_a_parent_of(n)) + continue; + + if (!n->has_node(p_at)) + continue; + + Node *n2 = n->get_node(p_at); + + n2->get_parent()->remove_child(n2); + + live_edit_remove_list[n][p_keep_id] = n2; + + F = N; + } +} +void LiveEditor::_restore_node_func(ObjectID p_id, const NodePath &p_at, int p_at_pos) { + SceneTree *scene_tree = SceneTree::get_singleton(); + if (!scene_tree) + return; + + Node *base = NULL; + if (scene_tree->root->has_node(live_edit_root)) + base = scene_tree->root->get_node(live_edit_root); + + Map<String, Set<Node *> >::Element *E = live_scene_edit_cache.find(live_edit_scene); + if (!E) + return; //scene not editable + + for (Set<Node *>::Element *F = E->get().front(); F;) { + + Set<Node *>::Element *N = F->next(); + + Node *n = F->get(); + + if (base && !base->is_a_parent_of(n)) + continue; + + if (!n->has_node(p_at)) + continue; + Node *n2 = n->get_node(p_at); + + Map<Node *, Map<ObjectID, Node *> >::Element *EN = live_edit_remove_list.find(n); + + if (!EN) + continue; + + Map<ObjectID, Node *>::Element *FN = EN->get().find(p_id); + + if (!FN) + continue; + n2->add_child(FN->get()); + + EN->get().erase(FN); + + if (EN->get().size() == 0) { + live_edit_remove_list.erase(EN); + } + + F = N; + } +} +void LiveEditor::_duplicate_node_func(const NodePath &p_at, const String &p_new_name) { + SceneTree *scene_tree = SceneTree::get_singleton(); + if (!scene_tree) + return; + + Node *base = NULL; + if (scene_tree->root->has_node(live_edit_root)) + base = scene_tree->root->get_node(live_edit_root); + + Map<String, Set<Node *> >::Element *E = live_scene_edit_cache.find(live_edit_scene); + if (!E) + return; //scene not editable + + for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) { + + Node *n = F->get(); + + if (base && !base->is_a_parent_of(n)) + continue; + + if (!n->has_node(p_at)) + continue; + Node *n2 = n->get_node(p_at); + + Node *dup = n2->duplicate(Node::DUPLICATE_SIGNALS | Node::DUPLICATE_GROUPS | Node::DUPLICATE_SCRIPTS); + + if (!dup) + continue; + + dup->set_name(p_new_name); + n2->get_parent()->add_child(dup); + } +} +void LiveEditor::_reparent_node_func(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos) { + SceneTree *scene_tree = SceneTree::get_singleton(); + if (!scene_tree) + return; + + Node *base = NULL; + if (scene_tree->root->has_node(live_edit_root)) + base = scene_tree->root->get_node(live_edit_root); + + Map<String, Set<Node *> >::Element *E = live_scene_edit_cache.find(live_edit_scene); + if (!E) + return; //scene not editable + + for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) { + + Node *n = F->get(); + + if (base && !base->is_a_parent_of(n)) + continue; + + if (!n->has_node(p_at)) + continue; + Node *nfrom = n->get_node(p_at); + + if (!n->has_node(p_new_place)) + continue; + Node *nto = n->get_node(p_new_place); + + nfrom->get_parent()->remove_child(nfrom); + nfrom->set_name(p_new_name); + + nto->add_child(nfrom); + if (p_at_pos >= 0) + nto->move_child(nfrom, p_at_pos); + } +} + +#endif diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h new file mode 100644 index 0000000000..d22f8e8e18 --- /dev/null +++ b/scene/debugger/scene_debugger.h @@ -0,0 +1,151 @@ +/*************************************************************************/ +/* scene_debugger.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SCENE_DEBUGGER_H +#define SCENE_DEBUGGER_H + +#include "core/array.h" +#include "core/object.h" +#include "core/pair.h" +#include "core/script_language.h" +#include "core/ustring.h" + +class SceneDebugger { + +public: + static void initialize(); + static void deinitialize(); + +#ifdef DEBUG_ENABLED +private: + static void _save_node(ObjectID id, const String &p_path); + static void _set_object_property(ObjectID p_id, const String &p_property, const Variant &p_value); + static void _send_object_id(ObjectID p_id, int p_max_size = 1 << 20); + +public: + static Error parse_message(const String &p_msg, const Array &p_args); + static void add_to_cache(const String &p_filename, Node *p_node); + static void remove_from_cache(const String &p_filename, Node *p_node); +#endif +}; + +#ifdef DEBUG_ENABLED +class SceneDebuggerObject { + +private: + void _parse_script_properties(Script *p_script, ScriptInstance *p_instance); + +public: + typedef Pair<PropertyInfo, Variant> SceneDebuggerProperty; + ObjectID id; + String class_name; + List<SceneDebuggerProperty> properties; + + SceneDebuggerObject(ObjectID p_id); + SceneDebuggerObject() {} + + void serialize(Array &r_arr, int p_max_size = 1 << 20); + void deserialize(const Array &p_arr); +}; + +class SceneDebuggerTree { + +public: + struct RemoteNode { + int child_count; + String name; + String type_name; + ObjectID id; + + RemoteNode(int p_child, const String &p_name, const String &p_type, ObjectID p_id) { + child_count = p_child; + name = p_name; + type_name = p_type; + id = p_id; + } + + RemoteNode() {} + }; + + List<RemoteNode> nodes; + + void serialize(Array &r_arr); + void deserialize(const Array &p_arr); + SceneDebuggerTree(Node *p_root); + SceneDebuggerTree(){}; +}; + +class LiveEditor { + +private: + friend class SceneDebugger; + Map<int, NodePath> live_edit_node_path_cache; + Map<int, String> live_edit_resource_cache; + + NodePath live_edit_root; + String live_edit_scene; + + Map<String, Set<Node *> > live_scene_edit_cache; + Map<Node *, Map<ObjectID, Node *> > live_edit_remove_list; + + void _send_tree(); + + void _node_path_func(const NodePath &p_path, int p_id); + void _res_path_func(const String &p_path, int p_id); + + void _node_set_func(int p_id, const StringName &p_prop, const Variant &p_value); + void _node_set_res_func(int p_id, const StringName &p_prop, const String &p_value); + void _node_call_func(int p_id, const StringName &p_method, VARIANT_ARG_DECLARE); + void _res_set_func(int p_id, const StringName &p_prop, const Variant &p_value); + void _res_set_res_func(int p_id, const StringName &p_prop, const String &p_value); + void _res_call_func(int p_id, const StringName &p_method, VARIANT_ARG_DECLARE); + void _root_func(const NodePath &p_scene_path, const String &p_scene_from); + + void _create_node_func(const NodePath &p_parent, const String &p_type, const String &p_name); + void _instance_node_func(const NodePath &p_parent, const String &p_path, const String &p_name); + void _remove_node_func(const NodePath &p_at); + void _remove_and_keep_node_func(const NodePath &p_at, ObjectID p_keep_id); + void _restore_node_func(ObjectID p_id, const NodePath &p_at, int p_at_pos); + void _duplicate_node_func(const NodePath &p_at, const String &p_new_name); + void _reparent_node_func(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos); + + LiveEditor() { + singleton = this; + live_edit_root = NodePath("/root"); + }; + + static LiveEditor *singleton; + +public: + static LiveEditor *get_singleton(); +}; +#endif + +#endif diff --git a/scene/debugger/script_debugger_remote.cpp b/scene/debugger/script_debugger_remote.cpp deleted file mode 100644 index 0e61a5746b..0000000000 --- a/scene/debugger/script_debugger_remote.cpp +++ /dev/null @@ -1,1313 +0,0 @@ -/*************************************************************************/ -/* script_debugger_remote.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "script_debugger_remote.h" - -#include "core/engine.h" -#include "core/io/ip.h" -#include "core/io/marshalls.h" -#include "core/os/input.h" -#include "core/os/os.h" -#include "core/project_settings.h" -#include "scene/main/node.h" -#include "scene/main/scene_tree.h" -#include "scene/main/viewport.h" -#include "scene/resources/packed_scene.h" -#include "servers/visual_server.h" - -void ScriptDebuggerRemote::_send_video_memory() { - - List<ResourceUsage> usage; - if (resource_usage_func) - resource_usage_func(&usage); - - usage.sort(); - - packet_peer_stream->put_var("message:video_mem"); - packet_peer_stream->put_var(usage.size() * 4); - - for (List<ResourceUsage>::Element *E = usage.front(); E; E = E->next()) { - - packet_peer_stream->put_var(E->get().path); - packet_peer_stream->put_var(E->get().type); - packet_peer_stream->put_var(E->get().format); - packet_peer_stream->put_var(E->get().vram); - } -} - -Error ScriptDebuggerRemote::connect_to_host(const String &p_host, uint16_t p_port) { - - IP_Address ip; - if (p_host.is_valid_ip_address()) - ip = p_host; - else - ip = IP::get_singleton()->resolve_hostname(p_host); - - int port = p_port; - - const int tries = 6; - int waits[tries] = { 1, 10, 100, 1000, 1000, 1000 }; - - tcp_client->connect_to_host(ip, port); - - for (int i = 0; i < tries; i++) { - - if (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTED) { - print_verbose("Remote Debugger: Connected!"); - break; - } else { - - const int ms = waits[i]; - OS::get_singleton()->delay_usec(ms * 1000); - print_verbose("Remote Debugger: Connection failed with status: '" + String::num(tcp_client->get_status()) + "', retrying in " + String::num(ms) + " msec."); - }; - }; - - if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) { - - ERR_PRINT("Remote Debugger: Unable to connect. Status: " + String::num(tcp_client->get_status()) + "."); - return FAILED; - }; - - packet_peer_stream->set_stream_peer(tcp_client); - - return OK; -} - -void ScriptDebuggerRemote::_put_variable(const String &p_name, const Variant &p_variable) { - - packet_peer_stream->put_var(p_name); - - Variant var = p_variable; - if (p_variable.get_type() == Variant::OBJECT && p_variable.get_validated_object() == nullptr) { - var = Variant(); - } - - int len = 0; - Error err = encode_variant(var, NULL, len, true); - if (err != OK) - ERR_PRINT("Failed to encode variant."); - - if (len > packet_peer_stream->get_output_buffer_max_size()) { //limit to max size - packet_peer_stream->put_var(Variant()); - } else { - packet_peer_stream->put_var(var); - } -} - -void ScriptDebuggerRemote::_save_node(ObjectID id, const String &p_path) { - - Node *node = Object::cast_to<Node>(ObjectDB::get_instance(id)); - ERR_FAIL_COND(!node); - - Ref<PackedScene> ps = memnew(PackedScene); - ps->pack(node); - ResourceSaver::save(p_path, ps); -} - -void ScriptDebuggerRemote::debug(ScriptLanguage *p_script, bool p_can_continue, bool p_is_error_breakpoint) { - - //this function is called when there is a debugger break (bug on script) - //or when execution is paused from editor - - if (skip_breakpoints && !p_is_error_breakpoint) - return; - - ERR_FAIL_COND_MSG(!tcp_client->is_connected_to_host(), "Script Debugger failed to connect, but being used anyway."); - - packet_peer_stream->put_var("debug_enter"); - packet_peer_stream->put_var(2); - packet_peer_stream->put_var(p_can_continue); - packet_peer_stream->put_var(p_script->debug_get_error()); - - skip_profile_frame = true; // to avoid super long frame time for the frame - - Input::MouseMode mouse_mode = Input::get_singleton()->get_mouse_mode(); - if (mouse_mode != Input::MOUSE_MODE_VISIBLE) - Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); - - uint64_t loop_begin_usec = 0; - uint64_t loop_time_sec = 0; - while (true) { - loop_begin_usec = OS::get_singleton()->get_ticks_usec(); - - _get_output(); - - if (packet_peer_stream->get_available_packet_count() > 0) { - - Variant var; - Error err = packet_peer_stream->get_var(var); - - ERR_CONTINUE(err != OK); - ERR_CONTINUE(var.get_type() != Variant::ARRAY); - - Array cmd = var; - - ERR_CONTINUE(cmd.size() == 0); - ERR_CONTINUE(cmd[0].get_type() != Variant::STRING); - - String command = cmd[0]; - - if (command == "get_stack_dump") { - - packet_peer_stream->put_var("stack_dump"); - int slc = p_script->debug_get_stack_level_count(); - packet_peer_stream->put_var(slc); - - for (int i = 0; i < slc; i++) { - - Dictionary d; - d["file"] = p_script->debug_get_stack_level_source(i); - d["line"] = p_script->debug_get_stack_level_line(i); - d["function"] = p_script->debug_get_stack_level_function(i); - //d["id"]=p_script->debug_get_stack_level_ - d["id"] = 0; - - packet_peer_stream->put_var(d); - } - - } else if (command == "get_stack_frame_vars") { - - cmd.remove(0); - ERR_CONTINUE(cmd.size() != 1); - int lv = cmd[0]; - - List<String> members; - List<Variant> member_vals; - if (ScriptInstance *inst = p_script->debug_get_stack_level_instance(lv)) { - members.push_back("self"); - member_vals.push_back(inst->get_owner()); - } - p_script->debug_get_stack_level_members(lv, &members, &member_vals); - ERR_CONTINUE(members.size() != member_vals.size()); - - List<String> locals; - List<Variant> local_vals; - p_script->debug_get_stack_level_locals(lv, &locals, &local_vals); - ERR_CONTINUE(locals.size() != local_vals.size()); - - List<String> globals; - List<Variant> globals_vals; - p_script->debug_get_globals(&globals, &globals_vals); - ERR_CONTINUE(globals.size() != globals_vals.size()); - - packet_peer_stream->put_var("stack_frame_vars"); - packet_peer_stream->put_var(3 + (locals.size() + members.size() + globals.size()) * 2); - - { //locals - packet_peer_stream->put_var(locals.size()); - - List<String>::Element *E = locals.front(); - List<Variant>::Element *F = local_vals.front(); - - while (E) { - _put_variable(E->get(), F->get()); - - E = E->next(); - F = F->next(); - } - } - - { //members - packet_peer_stream->put_var(members.size()); - - List<String>::Element *E = members.front(); - List<Variant>::Element *F = member_vals.front(); - - while (E) { - - _put_variable(E->get(), F->get()); - - E = E->next(); - F = F->next(); - } - } - - { //globals - packet_peer_stream->put_var(globals.size()); - - List<String>::Element *E = globals.front(); - List<Variant>::Element *F = globals_vals.front(); - - while (E) { - _put_variable(E->get(), F->get()); - - E = E->next(); - F = F->next(); - } - } - - } else if (command == "step") { - - set_depth(-1); - set_lines_left(1); - break; - } else if (command == "next") { - - set_depth(0); - set_lines_left(1); - break; - - } else if (command == "continue") { - set_depth(-1); - set_lines_left(-1); - OS::get_singleton()->move_window_to_foreground(); - break; - } else if (command == "break") { - ERR_PRINT("Got break when already broke!"); - break; - } else if (command == "request_scene_tree") { - -#ifdef DEBUG_ENABLED - if (scene_tree) - scene_tree->_debugger_request_tree(); -#endif - } else if (command == "request_video_mem") { - - _send_video_memory(); - } else if (command == "inspect_object") { - - ObjectID id = cmd[1]; - _send_object_id(id); - } else if (command == "set_object_property") { - - _set_object_property(cmd[1], cmd[2], cmd[3]); - - } else if (command == "override_camera_2D:set") { - bool enforce = cmd[1]; - - if (scene_tree) { - scene_tree->get_root()->enable_canvas_transform_override(enforce); - } - } else if (command == "override_camera_2D:transform") { - Transform2D transform = cmd[1]; - - if (scene_tree) { - scene_tree->get_root()->set_canvas_transform_override(transform); - } - } else if (command == "override_camera_3D:set") { - bool enable = cmd[1]; - - if (scene_tree) { - scene_tree->get_root()->enable_camera_override(enable); - } - } else if (command == "override_camera_3D:transform") { - Transform transform = cmd[1]; - bool is_perspective = cmd[2]; - float size_or_fov = cmd[3]; - float near = cmd[4]; - float far = cmd[5]; - - if (scene_tree) { - if (is_perspective) { - scene_tree->get_root()->set_camera_override_perspective(size_or_fov, near, far); - } else { - scene_tree->get_root()->set_camera_override_orthogonal(size_or_fov, near, far); - } - scene_tree->get_root()->set_camera_override_transform(transform); - } - - } else if (command == "reload_scripts") { - reload_all_scripts = true; - } else if (command == "breakpoint") { - - bool set = cmd[3]; - if (set) - insert_breakpoint(cmd[2], cmd[1]); - else - remove_breakpoint(cmd[2], cmd[1]); - - } else if (command == "save_node") { - _save_node(cmd[1], cmd[2]); - } else if (command == "set_skip_breakpoints") { - skip_breakpoints = cmd[1]; - } else { - _parse_live_edit(cmd); - } - - } else { - OS::get_singleton()->delay_usec(10000); - OS::get_singleton()->process_and_drop_events(); - } - - // This is for the camera override to stay live even when the game is paused from the editor - loop_time_sec = (OS::get_singleton()->get_ticks_usec() - loop_begin_usec) / 1000000.0f; - VisualServer::get_singleton()->sync(); - if (VisualServer::get_singleton()->has_changed()) { - VisualServer::get_singleton()->draw(true, loop_time_sec * Engine::get_singleton()->get_time_scale()); - } - } - - packet_peer_stream->put_var("debug_exit"); - packet_peer_stream->put_var(0); - - if (mouse_mode != Input::MOUSE_MODE_VISIBLE) - Input::get_singleton()->set_mouse_mode(mouse_mode); -} - -void ScriptDebuggerRemote::_get_output() { - - mutex->lock(); - if (output_strings.size()) { - - locking = true; - packet_peer_stream->put_var("output"); - packet_peer_stream->put_var(output_strings.size()); - - while (output_strings.size()) { - - packet_peer_stream->put_var(output_strings.front()->get()); - output_strings.pop_front(); - } - locking = false; - } - - if (n_messages_dropped > 0) { - Message msg; - msg.message = "Too many messages! " + String::num_int64(n_messages_dropped) + " messages were dropped."; - messages.push_back(msg); - n_messages_dropped = 0; - } - - while (messages.size()) { - locking = true; - packet_peer_stream->put_var("message:" + messages.front()->get().message); - packet_peer_stream->put_var(messages.front()->get().data.size()); - for (int i = 0; i < messages.front()->get().data.size(); i++) { - packet_peer_stream->put_var(messages.front()->get().data[i]); - } - messages.pop_front(); - locking = false; - } - - if (n_errors_dropped == 1) { - // Only print one message about dropping per second - OutputError oe; - oe.error = "TOO_MANY_ERRORS"; - oe.error_descr = "Too many errors! Ignoring errors for up to 1 second."; - oe.warning = false; - uint64_t time = OS::get_singleton()->get_ticks_msec(); - oe.hr = time / 3600000; - oe.min = (time / 60000) % 60; - oe.sec = (time / 1000) % 60; - oe.msec = time % 1000; - errors.push_back(oe); - } - - if (n_warnings_dropped == 1) { - // Only print one message about dropping per second - OutputError oe; - oe.error = "TOO_MANY_WARNINGS"; - oe.error_descr = "Too many warnings! Ignoring warnings for up to 1 second."; - oe.warning = true; - uint64_t time = OS::get_singleton()->get_ticks_msec(); - oe.hr = time / 3600000; - oe.min = (time / 60000) % 60; - oe.sec = (time / 1000) % 60; - oe.msec = time % 1000; - errors.push_back(oe); - } - - while (errors.size()) { - locking = true; - packet_peer_stream->put_var("error"); - OutputError oe = errors.front()->get(); - - packet_peer_stream->put_var(oe.callstack.size() + 2); - - Array error_data; - - error_data.push_back(oe.hr); - error_data.push_back(oe.min); - error_data.push_back(oe.sec); - error_data.push_back(oe.msec); - error_data.push_back(oe.source_func); - error_data.push_back(oe.source_file); - error_data.push_back(oe.source_line); - error_data.push_back(oe.error); - error_data.push_back(oe.error_descr); - error_data.push_back(oe.warning); - packet_peer_stream->put_var(error_data); - packet_peer_stream->put_var(oe.callstack.size()); - for (int i = 0; i < oe.callstack.size(); i++) { - packet_peer_stream->put_var(oe.callstack[i]); - } - - errors.pop_front(); - locking = false; - } - mutex->unlock(); -} - -void ScriptDebuggerRemote::line_poll() { - - //the purpose of this is just processing events every now and then when the script might get too busy - //otherwise bugs like infinite loops can't be caught - if (poll_every % 2048 == 0) - _poll_events(); - poll_every++; -} - -void ScriptDebuggerRemote::_err_handler(void *ud, const char *p_func, const char *p_file, int p_line, const char *p_err, const char *p_descr, ErrorHandlerType p_type) { - - if (p_type == ERR_HANDLER_SCRIPT) - return; //ignore script errors, those go through debugger - - Vector<ScriptLanguage::StackInfo> si; - - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - si = ScriptServer::get_language(i)->debug_get_current_stack_info(); - if (si.size()) - break; - } - - ScriptDebuggerRemote *sdr = (ScriptDebuggerRemote *)ud; - sdr->send_error(p_func, p_file, p_line, p_err, p_descr, p_type, si); -} - -bool ScriptDebuggerRemote::_parse_live_edit(const Array &p_command) { - -#ifdef DEBUG_ENABLED - - String cmdstr = p_command[0]; - if (!scene_tree || !cmdstr.begins_with("live_")) - return false; - - if (cmdstr == "live_set_root") { - - scene_tree->_live_edit_root_func(p_command[1], p_command[2]); - - } else if (cmdstr == "live_node_path") { - - scene_tree->_live_edit_node_path_func(p_command[1], p_command[2]); - - } else if (cmdstr == "live_res_path") { - - scene_tree->_live_edit_res_path_func(p_command[1], p_command[2]); - - } else if (cmdstr == "live_node_prop_res") { - - scene_tree->_live_edit_node_set_res_func(p_command[1], p_command[2], p_command[3]); - - } else if (cmdstr == "live_node_prop") { - - scene_tree->_live_edit_node_set_func(p_command[1], p_command[2], p_command[3]); - - } else if (cmdstr == "live_res_prop_res") { - - scene_tree->_live_edit_res_set_res_func(p_command[1], p_command[2], p_command[3]); - - } else if (cmdstr == "live_res_prop") { - - scene_tree->_live_edit_res_set_func(p_command[1], p_command[2], p_command[3]); - - } else if (cmdstr == "live_node_call") { - - scene_tree->_live_edit_node_call_func(p_command[1], p_command[2], p_command[3], p_command[4], p_command[5], p_command[6], p_command[7]); - - } else if (cmdstr == "live_res_call") { - - scene_tree->_live_edit_res_call_func(p_command[1], p_command[2], p_command[3], p_command[4], p_command[5], p_command[6], p_command[7]); - - } else if (cmdstr == "live_create_node") { - - scene_tree->_live_edit_create_node_func(p_command[1], p_command[2], p_command[3]); - - } else if (cmdstr == "live_instance_node") { - - scene_tree->_live_edit_instance_node_func(p_command[1], p_command[2], p_command[3]); - - } else if (cmdstr == "live_remove_node") { - - scene_tree->_live_edit_remove_node_func(p_command[1]); - - } else if (cmdstr == "live_remove_and_keep_node") { - - scene_tree->_live_edit_remove_and_keep_node_func(p_command[1], p_command[2]); - - } else if (cmdstr == "live_restore_node") { - - scene_tree->_live_edit_restore_node_func(p_command[1], p_command[2], p_command[3]); - - } else if (cmdstr == "live_duplicate_node") { - - scene_tree->_live_edit_duplicate_node_func(p_command[1], p_command[2]); - - } else if (cmdstr == "live_reparent_node") { - - scene_tree->_live_edit_reparent_node_func(p_command[1], p_command[2], p_command[3], p_command[4]); - - } else { - - return false; - } - - return true; -#else - - return false; -#endif -} - -void ScriptDebuggerRemote::_send_object_id(ObjectID p_id) { - - Object *obj = ObjectDB::get_instance(p_id); - if (!obj) - return; - - typedef Pair<PropertyInfo, Variant> PropertyDesc; - List<PropertyDesc> properties; - - if (ScriptInstance *si = obj->get_script_instance()) { - if (!si->get_script().is_null()) { - - typedef Map<const Script *, Set<StringName> > ScriptMemberMap; - typedef Map<const Script *, Map<StringName, Variant> > ScriptConstantsMap; - - ScriptMemberMap members; - members[si->get_script().ptr()] = Set<StringName>(); - si->get_script()->get_members(&(members[si->get_script().ptr()])); - - ScriptConstantsMap constants; - constants[si->get_script().ptr()] = Map<StringName, Variant>(); - si->get_script()->get_constants(&(constants[si->get_script().ptr()])); - - Ref<Script> base = si->get_script()->get_base_script(); - while (base.is_valid()) { - - members[base.ptr()] = Set<StringName>(); - base->get_members(&(members[base.ptr()])); - - constants[base.ptr()] = Map<StringName, Variant>(); - base->get_constants(&(constants[base.ptr()])); - - base = base->get_base_script(); - } - - for (ScriptMemberMap::Element *sm = members.front(); sm; sm = sm->next()) { - for (Set<StringName>::Element *E = sm->get().front(); E; E = E->next()) { - Variant m; - if (si->get(E->get(), m)) { - String script_path = sm->key() == si->get_script().ptr() ? "" : sm->key()->get_path().get_file() + "/"; - PropertyInfo pi(m.get_type(), "Members/" + script_path + E->get()); - properties.push_back(PropertyDesc(pi, m)); - } - } - } - - for (ScriptConstantsMap::Element *sc = constants.front(); sc; sc = sc->next()) { - for (Map<StringName, Variant>::Element *E = sc->get().front(); E; E = E->next()) { - String script_path = sc->key() == si->get_script().ptr() ? "" : sc->key()->get_path().get_file() + "/"; - if (E->value().get_type() == Variant::OBJECT) { - Variant id = ((Object *)E->value())->get_instance_id(); - PropertyInfo pi(id.get_type(), "Constants/" + E->key(), PROPERTY_HINT_OBJECT_ID, "Object"); - properties.push_back(PropertyDesc(pi, id)); - } else { - PropertyInfo pi(E->value().get_type(), "Constants/" + script_path + E->key()); - properties.push_back(PropertyDesc(pi, E->value())); - } - } - } - } - } - - if (Node *node = Object::cast_to<Node>(obj)) { - // in some cases node will not be in tree here - // for instance where it created as variable and not yet added to tree - // in such cases we can't ask for it's path - if (node->is_inside_tree()) { - PropertyInfo pi(Variant::NODE_PATH, String("Node/path")); - properties.push_front(PropertyDesc(pi, node->get_path())); - } else { - PropertyInfo pi(Variant::STRING, String("Node/path")); - properties.push_front(PropertyDesc(pi, "[Orphan]")); - } - - } else if (Resource *res = Object::cast_to<Resource>(obj)) { - if (Script *s = Object::cast_to<Script>(res)) { - Map<StringName, Variant> constants; - s->get_constants(&constants); - for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { - if (E->value().get_type() == Variant::OBJECT) { - Variant id = ((Object *)E->value())->get_instance_id(); - PropertyInfo pi(id.get_type(), "Constants/" + E->key(), PROPERTY_HINT_OBJECT_ID, "Object"); - properties.push_front(PropertyDesc(pi, E->value())); - } else { - PropertyInfo pi(E->value().get_type(), String("Constants/") + E->key()); - properties.push_front(PropertyDesc(pi, E->value())); - } - } - } - } - - List<PropertyInfo> pinfo; - obj->get_property_list(&pinfo, true); - for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { - if (E->get().usage & (PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CATEGORY)) { - properties.push_back(PropertyDesc(E->get(), obj->get(E->get().name))); - } - } - - Array send_props; - for (int i = 0; i < properties.size(); i++) { - const PropertyInfo &pi = properties[i].first; - Variant &var = properties[i].second; - - WeakRef *ref = Object::cast_to<WeakRef>(var); - if (ref) { - var = ref->get_ref(); - } - - RES res = var; - - Array prop; - prop.push_back(pi.name); - prop.push_back(pi.type); - - //only send information that can be sent.. - int len = 0; //test how big is this to encode - encode_variant(var, NULL, len); - if (len > packet_peer_stream->get_output_buffer_max_size()) { //limit to max size - prop.push_back(PROPERTY_HINT_OBJECT_TOO_BIG); - prop.push_back(""); - prop.push_back(pi.usage); - prop.push_back(Variant()); - } else { - prop.push_back(pi.hint); - prop.push_back(pi.hint_string); - prop.push_back(pi.usage); - - if (!res.is_null()) { - var = res->get_path(); - } - - prop.push_back(var); - } - send_props.push_back(prop); - } - - packet_peer_stream->put_var("message:inspect_object"); - packet_peer_stream->put_var(3); - packet_peer_stream->put_var(p_id); - packet_peer_stream->put_var(obj->get_class()); - packet_peer_stream->put_var(send_props); -} - -void ScriptDebuggerRemote::_set_object_property(ObjectID p_id, const String &p_property, const Variant &p_value) { - - Object *obj = ObjectDB::get_instance(p_id); - if (!obj) - return; - - String prop_name = p_property; - if (p_property.begins_with("Members/")) { - Vector<String> ss = p_property.split("/"); - prop_name = ss[ss.size() - 1]; - } - - obj->set(prop_name, p_value); -} - -void ScriptDebuggerRemote::_poll_events() { - - //this si called from ::idle_poll, happens only when running the game, - //does not get called while on debug break - - while (packet_peer_stream->get_available_packet_count() > 0) { - - _get_output(); - - //send over output_strings - - Variant var; - Error err = packet_peer_stream->get_var(var); - - ERR_CONTINUE(err != OK); - ERR_CONTINUE(var.get_type() != Variant::ARRAY); - - Array cmd = var; - - ERR_CONTINUE(cmd.size() == 0); - ERR_CONTINUE(cmd[0].get_type() != Variant::STRING); - - String command = cmd[0]; - //cmd.remove(0); - - if (command == "break") { - - if (get_break_language()) - debug(get_break_language()); - } else if (command == "request_scene_tree") { - -#ifdef DEBUG_ENABLED - if (scene_tree) - scene_tree->_debugger_request_tree(); -#endif - } else if (command == "request_video_mem") { - - _send_video_memory(); - } else if (command == "inspect_object") { - - ObjectID id = cmd[1]; - _send_object_id(id); - } else if (command == "set_object_property") { - - _set_object_property(cmd[1], cmd[2], cmd[3]); - - } else if (command == "start_profiling") { - - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ScriptServer::get_language(i)->profiling_start(); - } - - max_frame_functions = cmd[1]; - profiler_function_signature_map.clear(); - profiling = true; - frame_time = 0; - idle_time = 0; - physics_time = 0; - physics_frame_time = 0; - - print_line("PROFILING ALRIGHT!"); - - } else if (command == "stop_profiling") { - - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ScriptServer::get_language(i)->profiling_stop(); - } - profiling = false; - _send_profiling_data(false); - print_line("PROFILING END!"); - } else if (command == "start_visual_profiling") { - - visual_profiling = true; - VS::get_singleton()->set_frame_profiling_enabled(true); - } else if (command == "stop_visual_profiling") { - - visual_profiling = false; - VS::get_singleton()->set_frame_profiling_enabled(false); - } else if (command == "start_network_profiling") { - - network_profiling = true; - multiplayer->profiling_start(); - } else if (command == "stop_network_profiling") { - - network_profiling = false; - multiplayer->profiling_end(); - } else if (command == "override_camera_2D:set") { - bool enforce = cmd[1]; - - if (scene_tree) { - scene_tree->get_root()->enable_canvas_transform_override(enforce); - } - } else if (command == "override_camera_2D:transform") { - Transform2D transform = cmd[1]; - - if (scene_tree) { - scene_tree->get_root()->set_canvas_transform_override(transform); - } - } else if (command == "override_camera_3D:set") { - bool enable = cmd[1]; - - if (scene_tree) { - scene_tree->get_root()->enable_camera_override(enable); - } - } else if (command == "override_camera_3D:transform") { - Transform transform = cmd[1]; - bool is_perspective = cmd[2]; - float size_or_fov = cmd[3]; - float near = cmd[4]; - float far = cmd[5]; - - if (scene_tree) { - if (is_perspective) { - scene_tree->get_root()->set_camera_override_perspective(size_or_fov, near, far); - } else { - scene_tree->get_root()->set_camera_override_orthogonal(size_or_fov, near, far); - } - scene_tree->get_root()->set_camera_override_transform(transform); - } - - } else if (command == "reload_scripts") { - reload_all_scripts = true; - } else if (command == "breakpoint") { - - bool set = cmd[3]; - if (set) - insert_breakpoint(cmd[2], cmd[1]); - else - remove_breakpoint(cmd[2], cmd[1]); - } else if (command == "set_skip_breakpoints") { - skip_breakpoints = cmd[1]; - } else { - _parse_live_edit(cmd); - } - } -} - -void ScriptDebuggerRemote::_send_profiling_data(bool p_for_frame) { - - int ofs = 0; - - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - if (p_for_frame) - ofs += ScriptServer::get_language(i)->profiling_get_frame_data(&profile_info.write[ofs], profile_info.size() - ofs); - else - ofs += ScriptServer::get_language(i)->profiling_get_accumulated_data(&profile_info.write[ofs], profile_info.size() - ofs); - } - - for (int i = 0; i < ofs; i++) { - profile_info_ptrs.write[i] = &profile_info.write[i]; - } - - SortArray<ScriptLanguage::ProfilingInfo *, ProfileInfoSort> sa; - sa.sort(profile_info_ptrs.ptrw(), ofs); - - int to_send = MIN(ofs, max_frame_functions); - - //check signatures first - uint64_t total_script_time = 0; - - for (int i = 0; i < to_send; i++) { - - if (!profiler_function_signature_map.has(profile_info_ptrs[i]->signature)) { - - int idx = profiler_function_signature_map.size(); - packet_peer_stream->put_var("profile_sig"); - packet_peer_stream->put_var(2); - packet_peer_stream->put_var(profile_info_ptrs[i]->signature); - packet_peer_stream->put_var(idx); - - profiler_function_signature_map[profile_info_ptrs[i]->signature] = idx; - } - - total_script_time += profile_info_ptrs[i]->self_time; - } - - //send frames then - - if (p_for_frame) { - packet_peer_stream->put_var("profile_frame"); - packet_peer_stream->put_var(8 + profile_frame_data.size() * 2 + to_send * 4); - } else { - packet_peer_stream->put_var("profile_total"); - packet_peer_stream->put_var(8 + to_send * 4); - } - - packet_peer_stream->put_var(Engine::get_singleton()->get_frames_drawn()); //total frame time - packet_peer_stream->put_var(frame_time); //total frame time - packet_peer_stream->put_var(idle_time); //idle frame time - packet_peer_stream->put_var(physics_time); //fixed frame time - packet_peer_stream->put_var(physics_frame_time); //fixed frame time - - packet_peer_stream->put_var(USEC_TO_SEC(total_script_time)); //total script execution time - - if (p_for_frame) { - - packet_peer_stream->put_var(profile_frame_data.size()); //how many profile framedatas to send - packet_peer_stream->put_var(to_send); //how many script functions to send - for (int i = 0; i < profile_frame_data.size(); i++) { - - packet_peer_stream->put_var(profile_frame_data[i].name); - packet_peer_stream->put_var(profile_frame_data[i].data); - } - } else { - packet_peer_stream->put_var(0); //how many script functions to send - packet_peer_stream->put_var(to_send); //how many script functions to send - } - - for (int i = 0; i < to_send; i++) { - - int sig_id = -1; - - if (profiler_function_signature_map.has(profile_info_ptrs[i]->signature)) { - sig_id = profiler_function_signature_map[profile_info_ptrs[i]->signature]; - } - - packet_peer_stream->put_var(sig_id); - packet_peer_stream->put_var(profile_info_ptrs[i]->call_count); - packet_peer_stream->put_var(profile_info_ptrs[i]->total_time / 1000000.0); - packet_peer_stream->put_var(profile_info_ptrs[i]->self_time / 1000000.0); - } - - if (p_for_frame) { - profile_frame_data.clear(); - } -} - -void ScriptDebuggerRemote::idle_poll() { - - // this function is called every frame, except when there is a debugger break (::debug() in this class) - // execution stops and remains in the ::debug function - - _get_output(); - - if (requested_quit) { - - packet_peer_stream->put_var("kill_me"); - packet_peer_stream->put_var(0); - requested_quit = false; - } - - if (performance) { - - uint64_t pt = OS::get_singleton()->get_ticks_msec(); - if (pt - last_perf_time > 1000) { - - last_perf_time = pt; - int max = performance->get("MONITOR_MAX"); - Array arr; - arr.resize(max); - for (int i = 0; i < max; i++) { - arr[i] = performance->call("get_monitor", i); - } - packet_peer_stream->put_var("performance"); - packet_peer_stream->put_var(1); - packet_peer_stream->put_var(arr); - } - } - if (visual_profiling) { - Vector<VS::FrameProfileArea> profile_areas = VS::get_singleton()->get_frame_profile(); - if (profile_areas.size()) { - Vector<String> area_names; - Vector<real_t> area_times; - area_names.resize(profile_areas.size()); - area_times.resize(profile_areas.size() * 2); - { - String *area_namesw = area_names.ptrw(); - real_t *area_timesw = area_times.ptrw(); - - for (int i = 0; i < profile_areas.size(); i++) { - area_namesw[i] = profile_areas[i].name; - area_timesw[i * 2 + 0] = profile_areas[i].cpu_msec; - area_timesw[i * 2 + 1] = profile_areas[i].gpu_msec; - } - } - packet_peer_stream->put_var("visual_profile"); - packet_peer_stream->put_var(3); - packet_peer_stream->put_var(VS::get_singleton()->get_frame_profile_frame()); - packet_peer_stream->put_var(area_names); - packet_peer_stream->put_var(area_times); - } - } - - if (profiling) { - - if (skip_profile_frame) { - skip_profile_frame = false; - } else { - //send profiling info normally - _send_profiling_data(true); - } - } - - if (network_profiling) { - uint64_t pt = OS::get_singleton()->get_ticks_msec(); - if (pt - last_net_bandwidth_time > 200) { - last_net_bandwidth_time = pt; - _send_network_bandwidth_usage(); - } - if (pt - last_net_prof_time > 100) { - last_net_prof_time = pt; - _send_network_profiling_data(); - } - } - - if (reload_all_scripts) { - - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ScriptServer::get_language(i)->reload_all_scripts(); - } - reload_all_scripts = false; - } - - _poll_events(); -} - -void ScriptDebuggerRemote::_send_network_profiling_data() { - ERR_FAIL_COND(multiplayer.is_null()); - - int n_nodes = multiplayer->get_profiling_frame(&network_profile_info.write[0]); - - packet_peer_stream->put_var("network_profile"); - packet_peer_stream->put_var(n_nodes * 6); - for (int i = 0; i < n_nodes; ++i) { - packet_peer_stream->put_var(network_profile_info[i].node); - packet_peer_stream->put_var(network_profile_info[i].node_path); - packet_peer_stream->put_var(network_profile_info[i].incoming_rpc); - packet_peer_stream->put_var(network_profile_info[i].incoming_rset); - packet_peer_stream->put_var(network_profile_info[i].outgoing_rpc); - packet_peer_stream->put_var(network_profile_info[i].outgoing_rset); - } -} - -void ScriptDebuggerRemote::_send_network_bandwidth_usage() { - ERR_FAIL_COND(multiplayer.is_null()); - - int incoming_bandwidth = multiplayer->get_incoming_bandwidth_usage(); - int outgoing_bandwidth = multiplayer->get_outgoing_bandwidth_usage(); - - packet_peer_stream->put_var("network_bandwidth"); - packet_peer_stream->put_var(2); - packet_peer_stream->put_var(incoming_bandwidth); - packet_peer_stream->put_var(outgoing_bandwidth); -} - -void ScriptDebuggerRemote::send_message(const String &p_message, const Array &p_args) { - - mutex->lock(); - if (!locking && tcp_client->is_connected_to_host()) { - - if (messages.size() >= max_messages_per_frame) { - n_messages_dropped++; - } else { - Message msg; - msg.message = p_message; - msg.data = p_args; - messages.push_back(msg); - } - } - mutex->unlock(); -} - -void ScriptDebuggerRemote::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type, const Vector<ScriptLanguage::StackInfo> &p_stack_info) { - - OutputError oe; - oe.error = p_err; - oe.error_descr = p_descr; - oe.source_file = p_file; - oe.source_line = p_line; - oe.source_func = p_func; - oe.warning = p_type == ERR_HANDLER_WARNING; - uint64_t time = OS::get_singleton()->get_ticks_msec(); - oe.hr = time / 3600000; - oe.min = (time / 60000) % 60; - oe.sec = (time / 1000) % 60; - oe.msec = time % 1000; - Array cstack; - - uint64_t ticks = OS::get_singleton()->get_ticks_usec() / 1000; - msec_count += ticks - last_msec; - last_msec = ticks; - - if (msec_count > 1000) { - msec_count = 0; - - err_count = 0; - n_errors_dropped = 0; - warn_count = 0; - n_warnings_dropped = 0; - } - - cstack.resize(p_stack_info.size() * 3); - for (int i = 0; i < p_stack_info.size(); i++) { - cstack[i * 3 + 0] = p_stack_info[i].file; - cstack[i * 3 + 1] = p_stack_info[i].func; - cstack[i * 3 + 2] = p_stack_info[i].line; - } - - oe.callstack = cstack; - if (oe.warning) { - warn_count++; - } else { - err_count++; - } - - mutex->lock(); - - if (!locking && tcp_client->is_connected_to_host()) { - - if (oe.warning) { - if (warn_count > max_warnings_per_second) { - n_warnings_dropped++; - } else { - errors.push_back(oe); - } - } else { - if (err_count > max_errors_per_second) { - n_errors_dropped++; - } else { - errors.push_back(oe); - } - } - } - - mutex->unlock(); -} - -void ScriptDebuggerRemote::_print_handler(void *p_this, const String &p_string, bool p_error) { - - ScriptDebuggerRemote *sdr = (ScriptDebuggerRemote *)p_this; - - uint64_t ticks = OS::get_singleton()->get_ticks_usec() / 1000; - sdr->msec_count += ticks - sdr->last_msec; - sdr->last_msec = ticks; - - if (sdr->msec_count > 1000) { - sdr->char_count = 0; - sdr->msec_count = 0; - } - - String s = p_string; - int allowed_chars = MIN(MAX(sdr->max_cps - sdr->char_count, 0), s.length()); - - if (allowed_chars == 0) - return; - - if (allowed_chars < s.length()) { - s = s.substr(0, allowed_chars); - } - - sdr->char_count += allowed_chars; - bool overflowed = sdr->char_count >= sdr->max_cps; - - sdr->mutex->lock(); - if (!sdr->locking && sdr->tcp_client->is_connected_to_host()) { - - if (overflowed) - s += "[...]"; - - sdr->output_strings.push_back(s); - - if (overflowed) { - sdr->output_strings.push_back("[output overflow, print less text!]"); - } - } - sdr->mutex->unlock(); -} - -void ScriptDebuggerRemote::request_quit() { - - requested_quit = true; -} - -void ScriptDebuggerRemote::set_multiplayer(Ref<MultiplayerAPI> p_multiplayer) { - multiplayer = p_multiplayer; -} - -bool ScriptDebuggerRemote::is_profiling() const { - - return profiling; -} -void ScriptDebuggerRemote::add_profiling_frame_data(const StringName &p_name, const Array &p_data) { - - int idx = -1; - for (int i = 0; i < profile_frame_data.size(); i++) { - if (profile_frame_data[i].name == p_name) { - idx = i; - break; - } - } - - FrameData fd; - fd.name = p_name; - fd.data = p_data; - - if (idx == -1) { - profile_frame_data.push_back(fd); - } else { - profile_frame_data.write[idx] = fd; - } -} - -void ScriptDebuggerRemote::profiling_start() { - //ignores this, uses it via connection -} - -void ScriptDebuggerRemote::profiling_end() { - //ignores this, uses it via connection -} - -void ScriptDebuggerRemote::profiling_set_frame_times(float p_frame_time, float p_idle_time, float p_physics_time, float p_physics_frame_time) { - - frame_time = p_frame_time; - idle_time = p_idle_time; - physics_time = p_physics_time; - physics_frame_time = p_physics_frame_time; -} - -void ScriptDebuggerRemote::set_skip_breakpoints(bool p_skip_breakpoints) { - skip_breakpoints = p_skip_breakpoints; -} - -ScriptDebuggerRemote::ResourceUsageFunc ScriptDebuggerRemote::resource_usage_func = NULL; - -ScriptDebuggerRemote::ScriptDebuggerRemote() : - profiling(false), - visual_profiling(false), - network_profiling(false), - max_frame_functions(16), - skip_profile_frame(false), - reload_all_scripts(false), - tcp_client(Ref<StreamPeerTCP>(memnew(StreamPeerTCP))), - packet_peer_stream(Ref<PacketPeerStream>(memnew(PacketPeerStream))), - last_perf_time(0), - last_net_prof_time(0), - last_net_bandwidth_time(0), - performance(Engine::get_singleton()->get_singleton_object("Performance")), - requested_quit(false), - mutex(Mutex::create()), - max_messages_per_frame(GLOBAL_GET("network/limits/debugger_stdout/max_messages_per_frame")), - n_messages_dropped(0), - max_errors_per_second(GLOBAL_GET("network/limits/debugger_stdout/max_errors_per_second")), - max_warnings_per_second(GLOBAL_GET("network/limits/debugger_stdout/max_warnings_per_second")), - n_errors_dropped(0), - max_cps(GLOBAL_GET("network/limits/debugger_stdout/max_chars_per_second")), - char_count(0), - err_count(0), - warn_count(0), - last_msec(0), - msec_count(0), - locking(false), - poll_every(0), - scene_tree(NULL) { - - packet_peer_stream->set_stream_peer(tcp_client); - packet_peer_stream->set_output_buffer_max_size((1024 * 1024 * 8) - 4); // 8 MiB should be way more than enough, minus 4 bytes for separator. - - phl.printfunc = _print_handler; - phl.userdata = this; - add_print_handler(&phl); - - eh.errfunc = _err_handler; - eh.userdata = this; - add_error_handler(&eh); - - profile_info.resize(GLOBAL_GET("debug/settings/profiler/max_functions")); - network_profile_info.resize(GLOBAL_GET("debug/settings/profiler/max_functions")); - profile_info_ptrs.resize(profile_info.size()); -} - -ScriptDebuggerRemote::~ScriptDebuggerRemote() { - - remove_print_handler(&phl); - remove_error_handler(&eh); - memdelete(mutex); -} diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 2c15ac6aae..6556dd6d6c 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -37,6 +37,7 @@ #include "core/message_queue.h" #include "core/print_string.h" #include "instance_placeholder.h" +#include "scene/debugger/scene_debugger.h" #include "scene/resources/packed_scene.h" #include "scene/scene_string_names.h" #include "viewport.h" @@ -244,11 +245,7 @@ void Node::_propagate_enter_tree() { data.blocked--; #ifdef DEBUG_ENABLED - - if (ScriptDebugger::get_singleton() && data.filename != String()) { - //used for live edit - data.tree->live_scene_edit_cache[data.filename].insert(this); - } + SceneDebugger::add_to_cache(data.filename, this); #endif // enter groups } @@ -268,26 +265,7 @@ void Node::_propagate_exit_tree() { //block while removing children #ifdef DEBUG_ENABLED - - if (ScriptDebugger::get_singleton() && data.filename != String()) { - //used for live edit - Map<String, Set<Node *> >::Element *E = data.tree->live_scene_edit_cache.find(data.filename); - if (E) { - E->get().erase(this); - if (E->get().size() == 0) { - data.tree->live_scene_edit_cache.erase(E); - } - } - - Map<Node *, Map<ObjectID, Node *> >::Element *F = data.tree->live_edit_remove_list.find(this); - if (F) { - for (Map<ObjectID, Node *>::Element *G = F->get().front(); G; G = G->next()) { - - memdelete(G->get()); - } - data.tree->live_edit_remove_list.erase(F); - } - } + SceneDebugger::remove_from_cache(data.filename, this); #endif data.blocked++; diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index f558670693..9f9dace9f9 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -38,9 +38,10 @@ #include "core/os/os.h" #include "core/print_string.h" #include "core/project_settings.h" +#include "core/script_debugger_remote.h" #include "main/input_default.h" #include "node.h" -#include "scene/debugger/script_debugger_remote.h" +#include "scene/debugger/scene_debugger.h" #include "scene/resources/dynamic_font.h" #include "scene/resources/material.h" #include "scene/resources/mesh.h" @@ -1329,380 +1330,6 @@ void SceneTree::add_current_scene(Node *p_current) { root->add_child(p_current); } -#ifdef DEBUG_ENABLED - -static void _fill_array(Node *p_node, Array &array, int p_level) { - - array.push_back(p_node->get_child_count()); - array.push_back(p_node->get_name()); - array.push_back(p_node->get_class()); - array.push_back(p_node->get_instance_id()); - for (int i = 0; i < p_node->get_child_count(); i++) { - - _fill_array(p_node->get_child(i), array, p_level + 1); - } -} - -void SceneTree::_debugger_request_tree() { - - Array arr; - _fill_array(root, arr, 0); - ScriptDebugger::get_singleton()->send_message("scene_tree", arr); -} - -void SceneTree::_live_edit_node_path_func(const NodePath &p_path, int p_id) { - - live_edit_node_path_cache[p_id] = p_path; -} - -void SceneTree::_live_edit_res_path_func(const String &p_path, int p_id) { - - live_edit_resource_cache[p_id] = p_path; -} - -void SceneTree::_live_edit_node_set_func(int p_id, const StringName &p_prop, const Variant &p_value) { - - if (!live_edit_node_path_cache.has(p_id)) - return; - - NodePath np = live_edit_node_path_cache[p_id]; - Node *base = NULL; - if (root->has_node(live_edit_root)) - base = root->get_node(live_edit_root); - - Map<String, Set<Node *> >::Element *E = live_scene_edit_cache.find(live_edit_scene); - if (!E) - return; //scene not editable - - for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) { - - Node *n = F->get(); - - if (base && !base->is_a_parent_of(n)) - continue; - - if (!n->has_node(np)) - continue; - Node *n2 = n->get_node(np); - - n2->set(p_prop, p_value); - } -} - -void SceneTree::_live_edit_node_set_res_func(int p_id, const StringName &p_prop, const String &p_value) { - - RES r = ResourceLoader::load(p_value); - if (!r.is_valid()) - return; - _live_edit_node_set_func(p_id, p_prop, r); -} -void SceneTree::_live_edit_node_call_func(int p_id, const StringName &p_method, VARIANT_ARG_DECLARE) { - - if (!live_edit_node_path_cache.has(p_id)) - return; - - NodePath np = live_edit_node_path_cache[p_id]; - Node *base = NULL; - if (root->has_node(live_edit_root)) - base = root->get_node(live_edit_root); - - Map<String, Set<Node *> >::Element *E = live_scene_edit_cache.find(live_edit_scene); - if (!E) - return; //scene not editable - - for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) { - - Node *n = F->get(); - - if (base && !base->is_a_parent_of(n)) - continue; - - if (!n->has_node(np)) - continue; - Node *n2 = n->get_node(np); - - n2->call(p_method, VARIANT_ARG_PASS); - } -} -void SceneTree::_live_edit_res_set_func(int p_id, const StringName &p_prop, const Variant &p_value) { - - if (!live_edit_resource_cache.has(p_id)) - return; - - String resp = live_edit_resource_cache[p_id]; - - if (!ResourceCache::has(resp)) - return; - - RES r = ResourceCache::get(resp); - if (!r.is_valid()) - return; - - r->set(p_prop, p_value); -} -void SceneTree::_live_edit_res_set_res_func(int p_id, const StringName &p_prop, const String &p_value) { - - RES r = ResourceLoader::load(p_value); - if (!r.is_valid()) - return; - _live_edit_res_set_func(p_id, p_prop, r); -} -void SceneTree::_live_edit_res_call_func(int p_id, const StringName &p_method, VARIANT_ARG_DECLARE) { - - if (!live_edit_resource_cache.has(p_id)) - return; - - String resp = live_edit_resource_cache[p_id]; - - if (!ResourceCache::has(resp)) - return; - - RES r = ResourceCache::get(resp); - if (!r.is_valid()) - return; - - r->call(p_method, VARIANT_ARG_PASS); -} - -void SceneTree::_live_edit_root_func(const NodePath &p_scene_path, const String &p_scene_from) { - - live_edit_root = p_scene_path; - live_edit_scene = p_scene_from; -} - -void SceneTree::_live_edit_create_node_func(const NodePath &p_parent, const String &p_type, const String &p_name) { - - Node *base = NULL; - if (root->has_node(live_edit_root)) - base = root->get_node(live_edit_root); - - Map<String, Set<Node *> >::Element *E = live_scene_edit_cache.find(live_edit_scene); - if (!E) - return; //scene not editable - - for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) { - - Node *n = F->get(); - - if (base && !base->is_a_parent_of(n)) - continue; - - if (!n->has_node(p_parent)) - continue; - Node *n2 = n->get_node(p_parent); - - Node *no = Object::cast_to<Node>(ClassDB::instance(p_type)); - if (!no) { - continue; - } - - no->set_name(p_name); - n2->add_child(no); - } -} -void SceneTree::_live_edit_instance_node_func(const NodePath &p_parent, const String &p_path, const String &p_name) { - - Ref<PackedScene> ps = ResourceLoader::load(p_path); - - if (!ps.is_valid()) - return; - - Node *base = NULL; - if (root->has_node(live_edit_root)) - base = root->get_node(live_edit_root); - - Map<String, Set<Node *> >::Element *E = live_scene_edit_cache.find(live_edit_scene); - if (!E) - return; //scene not editable - - for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) { - - Node *n = F->get(); - - if (base && !base->is_a_parent_of(n)) - continue; - - if (!n->has_node(p_parent)) - continue; - Node *n2 = n->get_node(p_parent); - - Node *no = ps->instance(); - if (!no) { - continue; - } - - no->set_name(p_name); - n2->add_child(no); - } -} -void SceneTree::_live_edit_remove_node_func(const NodePath &p_at) { - - Node *base = NULL; - if (root->has_node(live_edit_root)) - base = root->get_node(live_edit_root); - - Map<String, Set<Node *> >::Element *E = live_scene_edit_cache.find(live_edit_scene); - if (!E) - return; //scene not editable - - for (Set<Node *>::Element *F = E->get().front(); F;) { - - Set<Node *>::Element *N = F->next(); - - Node *n = F->get(); - - if (base && !base->is_a_parent_of(n)) - continue; - - if (!n->has_node(p_at)) - continue; - Node *n2 = n->get_node(p_at); - - memdelete(n2); - - F = N; - } -} -void SceneTree::_live_edit_remove_and_keep_node_func(const NodePath &p_at, ObjectID p_keep_id) { - - Node *base = NULL; - if (root->has_node(live_edit_root)) - base = root->get_node(live_edit_root); - - Map<String, Set<Node *> >::Element *E = live_scene_edit_cache.find(live_edit_scene); - if (!E) - return; //scene not editable - - for (Set<Node *>::Element *F = E->get().front(); F;) { - - Set<Node *>::Element *N = F->next(); - - Node *n = F->get(); - - if (base && !base->is_a_parent_of(n)) - continue; - - if (!n->has_node(p_at)) - continue; - - Node *n2 = n->get_node(p_at); - - n2->get_parent()->remove_child(n2); - - live_edit_remove_list[n][p_keep_id] = n2; - - F = N; - } -} -void SceneTree::_live_edit_restore_node_func(ObjectID p_id, const NodePath &p_at, int p_at_pos) { - - Node *base = NULL; - if (root->has_node(live_edit_root)) - base = root->get_node(live_edit_root); - - Map<String, Set<Node *> >::Element *E = live_scene_edit_cache.find(live_edit_scene); - if (!E) - return; //scene not editable - - for (Set<Node *>::Element *F = E->get().front(); F;) { - - Set<Node *>::Element *N = F->next(); - - Node *n = F->get(); - - if (base && !base->is_a_parent_of(n)) - continue; - - if (!n->has_node(p_at)) - continue; - Node *n2 = n->get_node(p_at); - - Map<Node *, Map<ObjectID, Node *> >::Element *EN = live_edit_remove_list.find(n); - - if (!EN) - continue; - - Map<ObjectID, Node *>::Element *FN = EN->get().find(p_id); - - if (!FN) - continue; - n2->add_child(FN->get()); - - EN->get().erase(FN); - - if (EN->get().size() == 0) { - live_edit_remove_list.erase(EN); - } - - F = N; - } -} -void SceneTree::_live_edit_duplicate_node_func(const NodePath &p_at, const String &p_new_name) { - - Node *base = NULL; - if (root->has_node(live_edit_root)) - base = root->get_node(live_edit_root); - - Map<String, Set<Node *> >::Element *E = live_scene_edit_cache.find(live_edit_scene); - if (!E) - return; //scene not editable - - for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) { - - Node *n = F->get(); - - if (base && !base->is_a_parent_of(n)) - continue; - - if (!n->has_node(p_at)) - continue; - Node *n2 = n->get_node(p_at); - - Node *dup = n2->duplicate(Node::DUPLICATE_SIGNALS | Node::DUPLICATE_GROUPS | Node::DUPLICATE_SCRIPTS); - - if (!dup) - continue; - - dup->set_name(p_new_name); - n2->get_parent()->add_child(dup); - } -} -void SceneTree::_live_edit_reparent_node_func(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos) { - - Node *base = NULL; - if (root->has_node(live_edit_root)) - base = root->get_node(live_edit_root); - - Map<String, Set<Node *> >::Element *E = live_scene_edit_cache.find(live_edit_scene); - if (!E) - return; //scene not editable - - for (Set<Node *>::Element *F = E->get().front(); F; F = F->next()) { - - Node *n = F->get(); - - if (base && !base->is_a_parent_of(n)) - continue; - - if (!n->has_node(p_at)) - continue; - Node *nfrom = n->get_node(p_at); - - if (!n->has_node(p_new_place)) - continue; - Node *nto = n->get_node(p_new_place); - - nfrom->get_parent()->remove_child(nfrom); - nfrom->set_name(p_new_name); - - nto->add_child(nfrom); - if (p_at_pos >= 0) - nto->move_child(nfrom, p_at_pos); - } -} - -#endif - void SceneTree::drop_files(const Vector<String> &p_files, int p_from_screen) { emit_signal("files_dropped", p_files, p_from_screen); @@ -2116,11 +1743,6 @@ SceneTree::SceneTree() { _update_root_rect(); if (ScriptDebugger::get_singleton()) { - if (ScriptDebugger::get_singleton()->is_remote()) { - ScriptDebuggerRemote *remote_debugger = static_cast<ScriptDebuggerRemote *>(ScriptDebugger::get_singleton()); - - remote_debugger->set_scene_tree(this); - } ScriptDebugger::get_singleton()->set_multiplayer(multiplayer); } @@ -2129,12 +1751,6 @@ SceneTree::SceneTree() { #ifdef TOOLS_ENABLED edited_scene_root = NULL; #endif - -#ifdef DEBUG_ENABLED - - live_edit_root = NodePath("/root"); - -#endif } SceneTree::~SceneTree() { diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index 80f0da66e2..1bef0d3131 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -44,6 +44,7 @@ class Node; class Viewport; class Material; class Mesh; +class SceneDebugger; class SceneTreeTimer : public Reference { GDCLASS(SceneTreeTimer, Reference); @@ -219,39 +220,8 @@ private: SelfList<Node>::List xform_change_list; - friend class ScriptDebuggerRemote; -#ifdef DEBUG_ENABLED - - Map<int, NodePath> live_edit_node_path_cache; - Map<int, String> live_edit_resource_cache; - - NodePath live_edit_root; - String live_edit_scene; - - Map<String, Set<Node *> > live_scene_edit_cache; - Map<Node *, Map<ObjectID, Node *> > live_edit_remove_list; - - void _debugger_request_tree(); - - void _live_edit_node_path_func(const NodePath &p_path, int p_id); - void _live_edit_res_path_func(const String &p_path, int p_id); - - void _live_edit_node_set_func(int p_id, const StringName &p_prop, const Variant &p_value); - void _live_edit_node_set_res_func(int p_id, const StringName &p_prop, const String &p_value); - void _live_edit_node_call_func(int p_id, const StringName &p_method, VARIANT_ARG_DECLARE); - void _live_edit_res_set_func(int p_id, const StringName &p_prop, const Variant &p_value); - void _live_edit_res_set_res_func(int p_id, const StringName &p_prop, const String &p_value); - void _live_edit_res_call_func(int p_id, const StringName &p_method, VARIANT_ARG_DECLARE); - void _live_edit_root_func(const NodePath &p_scene_path, const String &p_scene_from); - - void _live_edit_create_node_func(const NodePath &p_parent, const String &p_type, const String &p_name); - void _live_edit_instance_node_func(const NodePath &p_parent, const String &p_path, const String &p_name); - void _live_edit_remove_node_func(const NodePath &p_at); - void _live_edit_remove_and_keep_node_func(const NodePath &p_at, ObjectID p_keep_id); - void _live_edit_restore_node_func(ObjectID p_id, const NodePath &p_at, int p_at_pos); - void _live_edit_duplicate_node_func(const NodePath &p_at, const String &p_new_name); - void _live_edit_reparent_node_func(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos); - +#ifdef DEBUG_ENABLED // No live editor in release build. + friend class LiveEditor; #endif enum { diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 40f24ece87..7ffead9b86 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -76,6 +76,7 @@ #include "scene/animation/root_motion_view.h" #include "scene/animation/tween.h" #include "scene/audio/audio_stream_player.h" +#include "scene/debugger/scene_debugger.h" #include "scene/gui/box_container.h" #include "scene/gui/button.h" #include "scene/gui/center_container.h" @@ -777,10 +778,12 @@ void register_scene_types() { ERR_PRINT("Error loading custom theme '" + theme_path + "'"); } } + SceneDebugger::initialize(); } void unregister_scene_types() { + SceneDebugger::deinitialize(); clear_default_theme(); ResourceLoader::remove_resource_format_loader(resource_loader_dynamic_font); diff --git a/servers/register_server_types.cpp b/servers/register_server_types.cpp index 25d122604a..c9f5277a4d 100644 --- a/servers/register_server_types.cpp +++ b/servers/register_server_types.cpp @@ -56,6 +56,7 @@ #include "audio_server.h" #include "camera/camera_feed.h" #include "camera_server.h" +#include "core/script_debugger_remote.h" #include "navigation_2d_server.h" #include "navigation_server.h" #include "physics/physics_server_sw.h" @@ -63,18 +64,17 @@ #include "physics_2d/physics_2d_server_wrap_mt.h" #include "physics_2d_server.h" #include "physics_server.h" -#include "scene/debugger/script_debugger_remote.h" #include "visual/shader_types.h" #include "visual_server.h" -static void _debugger_get_resource_usage(List<ScriptDebuggerRemote::ResourceUsage> *r_usage) { +static void _debugger_get_resource_usage(ScriptDebuggerRemote::ResourceUsage *r_usage) { List<VS::TextureInfo> tinfo; VS::get_singleton()->texture_debug_usage(&tinfo); for (List<VS::TextureInfo>::Element *E = tinfo.front(); E; E = E->next()) { - ScriptDebuggerRemote::ResourceUsage usage; + ScriptDebuggerRemote::ResourceInfo usage; usage.path = E->get().path; usage.vram = E->get().bytes; usage.id = E->get().texture; @@ -84,7 +84,7 @@ static void _debugger_get_resource_usage(List<ScriptDebuggerRemote::ResourceUsag } else { usage.format = itos(E->get().width) + "x" + itos(E->get().height) + "x" + itos(E->get().depth) + " " + Image::get_format_name(E->get().format); } - r_usage->push_back(usage); + r_usage->infos.push_back(usage); } } |