@tool extends EditorPlugin var _runner_btn: RunnerButton var _play_icon: Texture2D var _stop_icon: Texture2D func _enter_tree() -> void: ProjectSettings.set("application/run/custom_runner", "") ProjectSettings.add_property_info({ "name": "application/run/custom_runner", "type": TYPE_STRING, "hint": PROPERTY_HINT_GLOBAL_FILE, "hint_string": "" }) _runner_btn = RunnerButton.new() add_control_to_container(EditorPlugin.CONTAINER_TOOLBAR, _runner_btn) var cnt := _runner_btn.get_parent() cnt.move_child(_runner_btn, cnt.get_child_count() - 3) func _exit_tree() -> void: remove_control_from_container(EditorPlugin.CONTAINER_TOOLBAR, _runner_btn) _runner_btn.queue_free() _runner_btn = null class RunnerButton: extends Button var _play: Texture2D var _stop: Texture2D var _running_pid: int = -1 var _io: FileAccess = null var _err: FileAccess = null func _init() -> void: _play = EditorInterface.get_editor_theme().get_icon(&"Play", &"EditorIcons") _stop = EditorInterface.get_editor_theme().get_icon(&"Stop", &"EditorIcons") shortcut = Shortcut.new() var key := InputEventKey.new() key.keycode = KEY_F5 key.shift_pressed = true shortcut.events = [key] modulate = Color.GREEN var esb := StyleBoxEmpty.new() add_theme_stylebox_override(&"normal", esb) func _ready() -> void: icon = _play func _process(_d: float) -> void: if _running_pid == -1: return if not OS.is_process_running(_running_pid): _running_pid = -1 icon = _play modulate = Color.GREEN func _pressed() -> void: if _running_pid != -1: OS.kill(_running_pid) _running_pid = -1 icon = _play modulate = Color.GREEN return var runner := ProjectSettings.get_setting("application/run/custom_runner", "") if runner == "": EditorInterface.play_main_scene() return if not FileAccess.file_exists(runner): EditorInterface.get_editor_toaster().push_toast("Custom runner does not exist!", EditorToaster.SEVERITY_ERROR, "Provide the full path to the custom runner!") return var godot = OS.get_executable_path() var project = ProjectSettings.globalize_path("res://").rstrip("/") var host: String = EditorInterface.get_editor_settings().get_setting("network/debug/remote_host") var port: int = EditorInterface.get_editor_settings().get_setting("network/debug/remote_port") var pid: int = OS.get_process_id() var main_scene: String = ProjectSettings.get_setting("application/run/main_scene") var run_arguments := [ godot, "--path", project, "-d" #"--remote-debug", "tcp://%s:%s" % [host, port], #"--editor-pid", "%s" % pid, #"--position", "320,-24", #"--scene", main_scene ] # --remote-debug tcp://127.0.0.1:6007 --editor-pid 3480813 --position 320,-24 --scene uid://2t2hslaaewee print("Executing project with the following Command Line: %s %s" % [runner, run_arguments]) #_running_pid = OS.create_process(runner, run_arguments) var res := OS.execute_with_pipe(runner, run_arguments, false) if res.is_empty(): EditorInterface.get_editor_toaster().push_toast("Failed to Run custom runner.",EditorToaster.SEVERITY_ERROR) _running_pid = res.pid _io = res.stdio _err = res.stderr icon = _stop modulate = Color.RED _monitor_input_output() func _monitor_input_output() -> void: var output: PackedByteArray = [] var err: PackedByteArray = [] while OS.is_process_running(_running_pid): await get_tree().process_frame var res := _read_io(_io, output) if res != "": print_rich(res) res = _read_io(_err, err) if res != "": print_rich(res) await get_tree().process_frame var str := _read_io(_io, output) if str != "": print(str) str = _read_io(_err, err) if str != "": print(str) _io.close() _err.close() _io = null _err = null func _read_io(io: FileAccess, buffer: PackedByteArray) -> String: if io.get_position() < io.get_length(): buffer.append_array(io.get_buffer(io.get_length() - io.get_position())) var nl = buffer.rfind(10) if nl == -1: return "" var part := buffer.slice(0,nl) var leftover := buffer.slice(nl+1) buffer.clear() buffer.append_array(leftover) return part.get_string_from_utf8()