Kaynağa Gözat

Huge Debugger/EditorDebugger refactor.

Fabio Alessandrelli 5 yıl önce
ebeveyn
işleme
cbc450c0e5
41 değiştirilmiş dosya ile 4264 ekleme ve 2715 silme
  1. 1145 0
      core/script_debugger_remote.cpp
  2. 163 44
      core/script_debugger_remote.h
  3. 1 0
      editor/SCsub
  4. 5 0
      editor/debugger/SCsub
  5. 277 0
      editor/debugger/editor_debugger_inspector.cpp
  6. 98 0
      editor/debugger/editor_debugger_inspector.h
  7. 564 0
      editor/debugger/editor_debugger_node.cpp
  8. 176 0
      editor/debugger/editor_debugger_node.h
  9. 274 0
      editor/debugger/editor_debugger_tree.cpp
  10. 74 0
      editor/debugger/editor_debugger_tree.h
  11. 167 629
      editor/debugger/script_editor_debugger.cpp
  12. 43 69
      editor/debugger/script_editor_debugger.h
  13. 38 12
      editor/editor_node.cpp
  14. 5 3
      editor/editor_node.h
  15. 1 1
      editor/editor_path.cpp
  16. 26 6
      editor/editor_run.cpp
  17. 5 3
      editor/editor_run.h
  18. 1 1
      editor/inspector_dock.cpp
  19. 9 9
      editor/plugins/canvas_item_editor_plugin.cpp
  20. 51 0
      editor/plugins/debugger_editor_plugin.cpp
  21. 50 0
      editor/plugins/debugger_editor_plugin.h
  22. 14 115
      editor/plugins/script_editor_plugin.cpp
  23. 1 13
      editor/plugins/script_editor_plugin.h
  24. 4 4
      editor/plugins/script_text_editor.cpp
  25. 7 7
      editor/plugins/spatial_editor_plugin.cpp
  26. 16 16
      editor/scene_tree_dock.cpp
  27. 3 4
      editor/settings_config_dialog.cpp
  28. 1 7
      main/main.cpp
  29. 1 1
      modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs
  30. 2 2
      modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
  31. 7 7
      modules/mono/editor/editor_internal_calls.cpp
  32. 1 1
      modules/mono/mono_gd/gd_mono_utils.cpp
  33. 1 0
      platform/windows/os_windows.cpp
  34. 867 0
      scene/debugger/scene_debugger.cpp
  35. 151 0
      scene/debugger/scene_debugger.h
  36. 0 1313
      scene/debugger/script_debugger_remote.cpp
  37. 3 25
      scene/main/node.cpp
  38. 2 386
      scene/main/scene_tree.cpp
  39. 3 33
      scene/main/scene_tree.h
  40. 3 0
      scene/register_scene_types.cpp
  41. 4 4
      servers/register_server_types.cpp

+ 1145 - 0
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);
+}

+ 163 - 44
scene/debugger/script_debugger_remote.h → 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();
 };

+ 1 - 0
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')

+ 5 - 0
editor/debugger/SCsub

@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+Import('env')
+
+env.add_source_files(env.editor_sources, "*.cpp")

+ 277 - 0
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);
+}

+ 98 - 0
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

+ 564 - 0
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);
+	});
+}

+ 176 - 0
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

+ 274 - 0
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);
+}

+ 74 - 0
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

Dosya farkı çok büyük olduğundan ihmal edildi
+ 167 - 629
editor/debugger/script_editor_debugger.cpp


+ 43 - 69
editor/script_editor_debugger.h → 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();

+ 38 - 12
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)));

+ 5 - 3
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;

+ 1 - 1
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();

+ 26 - 6
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;

+ 5 - 3
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;

+ 1 - 1
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();

+ 9 - 9
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) {

+ 51 - 0
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?
+}

+ 50 - 0
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

+ 14 - 115
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");

+ 1 - 13
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);

+ 4 - 4
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: {

+ 7 - 7
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);

+ 16 - 16
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 {
 

+ 3 - 4
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: {

+ 1 - 7
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);

+ 1 - 1
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();

+ 2 - 2
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);

+ 7 - 7
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);

+ 1 - 1
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"

+ 1 - 0
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)

+ 867 - 0
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

+ 151 - 0
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

+ 0 - 1313
scene/debugger/script_debugger_remote.cpp

@@ -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);
-}

+ 3 - 25
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++;
 

+ 2 - 386
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() {

+ 3 - 33
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 {

+ 3 - 0
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);

+ 4 - 4
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);
 	}
 }
 

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor