From e73d6273e969f9720b9ce730c3c5825126ae892b Mon Sep 17 00:00:00 2001 From: metamuffin Date: Thu, 26 Dec 2024 13:43:03 +0100 Subject: generalize services and add editor to play menu --- client/menu/play.gd | 58 ++++++++++++++++---- client/menu/play.tscn | 17 +++++- client/project.godot | 3 +- client/server.gd | 133 ---------------------------------------------- client/service/editor.gd | 31 +++++++++++ client/service/server.gd | 44 +++++++++++++++ client/service/service.gd | 122 ++++++++++++++++++++++++++++++++++++++++++ client/settings.gd | 1 + locale/en.ini | 8 ++- server/editor/src/main.rs | 16 +++--- 10 files changed, 280 insertions(+), 153 deletions(-) delete mode 100644 client/server.gd create mode 100644 client/service/editor.gd create mode 100644 client/service/server.gd create mode 100644 client/service/service.gd diff --git a/client/menu/play.gd b/client/menu/play.gd index 50e8a633..894ea4bc 100644 --- a/client/menu/play.gd +++ b/client/menu/play.gd @@ -26,6 +26,8 @@ var url_regex: RegEx = RegEx.new() @onready var server = $side/margin/options/second/server @onready var server_control = $side/margin/options/second/server/control @onready var server_connect = $side/margin/options/second/server/connect +@onready var editor_control = $side/margin/options/second/editor/control +@onready var editor_connect = $side/margin/options/second/editor/connect func _ready(): url_regex.compile("^(?:(ws|wss)://)?([^:]+)(?::([0-9]+))?$") @@ -127,40 +129,74 @@ func connect_to(url: String): print("Connecting to %s" % url) get_parent().replace_menu("res://menu/game.tscn", url) -func _on_server_pressed(): +func _on_server_control_pressed(): match Server.state: - Server.State.RUNNING: Server.stop() - Server.State.STOPPED: Server.start() - Server.State.FAILED: Server.start() + Service.State.RUNNING: Server.stop() + Service.State.STOPPED: Server.start() + Service.State.FAILED: Server.start() + +func _on_editor_control_pressed(): + match Editor.state: + Service.State.RUNNING: Editor.stop() + Service.State.STOPPED: Editor.start(); Server.start() + Service.State.FAILED: Editor.start() func _on_server_connect_pressed(): connect_to("ws://127.0.0.1:27032/") +func _on_editor_connect_pressed(): + connect_to("ws://127.0.0.1:27035/") + func _process(_delta): server_control.disabled = false - server_connect.visible = Server.state == Server.State.RUNNING + server_connect.visible = Server.state == Service.State.RUNNING server_control.modulate = Color.WHITE match Server.state: - Server.State.RUNNING: + Service.State.RUNNING: server_control.text = tr("c.menu.play.server_stop") server_control.modulate = Color.AQUAMARINE - Server.State.TESTING: + Service.State.TESTING: server_control.text = tr("c.menu.play.server_testing") server_control.disabled = true - Server.State.STARTING: + Service.State.STARTING: server_control.text = tr("c.menu.play.server_starting") server_control.disabled = true - Server.State.STOPPED: + Service.State.STOPPED: server_control.text = tr("c.menu.play.server_start") - Server.State.FAILED: + Service.State.FAILED: server_control.text = tr("c.menu.play.server_failed") server_control.modulate = Color(1, 0.4, 0.5) server_control.tooltip_text = tr("c.menu.play.server_failed_tooltip") - Server.State.UNAVAILABLE: + Service.State.UNAVAILABLE: server_control.text = tr("c.menu.play.server_unavailable") server_control.disabled = true server_control.tooltip_text = tr("c.menu.play.server_binary_not_found") + editor_control.disabled = false + editor_connect.visible = Editor.state == Service.State.RUNNING + editor_control.modulate = Color.WHITE + match Editor.state: + Service.State.RUNNING: + editor_control.text = tr("c.menu.play.editor_stop") + editor_control.modulate = Color.AQUAMARINE + Service.State.TESTING: + editor_control.text = tr("c.menu.play.editor_testing") + editor_control.disabled = true + Service.State.STARTING: + editor_control.text = tr("c.menu.play.editor_starting") + editor_control.disabled = true + Service.State.STOPPED: + editor_control.text = tr("c.menu.play.editor_start") + Service.State.FAILED: + editor_control.text = tr("c.menu.play.editor_failed") + editor_control.modulate = Color(1, 0.4, 0.5) + editor_control.tooltip_text = tr("c.menu.play.server_failed_tooltip") + Service.State.UNAVAILABLE: + editor_control.text = tr("c.menu.play.editor_unavailable") + editor_control.disabled = true + editor_control.tooltip_text = tr("c.menu.play.server_binary_not_found") + + func _on_uri_text_changed(new_text): connect_uri.modulate = Color.WHITE if url_regex.search(new_text) else Color.RED diff --git a/client/menu/play.tscn b/client/menu/play.tscn index 43a3414f..47b45d69 100644 --- a/client/menu/play.tscn +++ b/client/menu/play.tscn @@ -113,6 +113,19 @@ alignment = 0 layout_mode = 2 text = "c.menu.play.connect" +[node name="editor" type="HBoxContainer" parent="side/margin/options/second"] +layout_mode = 2 + +[node name="control" type="Button" parent="side/margin/options/second/editor"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "c.menu.play.editor" +alignment = 0 + +[node name="connect" type="Button" parent="side/margin/options/second/editor"] +layout_mode = 2 +text = "c.menu.play.connect" + [node name="spacer2" type="Control" parent="side/margin/options/second"] custom_minimum_size = Vector2(0, 10) layout_mode = 2 @@ -127,6 +140,8 @@ layout_mode = 2 [connection signal="text_changed" from="side/margin/options/second/connect/uri" to="." method="_on_uri_text_changed"] [connection signal="pressed" from="side/margin/options/second/connect/connect" to="." method="_on_connect_pressed"] -[connection signal="pressed" from="side/margin/options/second/server/control" to="." method="_on_server_pressed"] +[connection signal="pressed" from="side/margin/options/second/server/control" to="." method="_on_server_control_pressed"] [connection signal="pressed" from="side/margin/options/second/server/connect" to="." method="_on_server_connect_pressed"] +[connection signal="pressed" from="side/margin/options/second/editor/control" to="." method="_on_editor_control_pressed"] +[connection signal="pressed" from="side/margin/options/second/editor/connect" to="." method="_on_editor_connect_pressed"] [connection signal="pressed" from="side/margin/options/second/back" to="." method="_on_back_pressed"] diff --git a/client/project.godot b/client/project.godot index 2239120b..3d45924f 100644 --- a/client/project.godot +++ b/client/project.godot @@ -20,11 +20,12 @@ config/icon="res://icons/main.png" [autoload] Global="*res://global.gd" -Server="*res://server.gd" Sound="*res://audio/sound.tscn" DisableWrongJoypads="*res://disable_wrong_joypads.gd" InputManager="*res://menu/settings/input/input_manager.gd" ServerList="*res://server_list.gd" +Server="*res://service/server.gd" +Editor="*res://service/editor.gd" [debug] diff --git a/client/server.gd b/client/server.gd deleted file mode 100644 index 4378c02d..00000000 --- a/client/server.gd +++ /dev/null @@ -1,133 +0,0 @@ -# Hurry Curry! - a game about cooking -# Copyright 2024 metamuffin -# Copyright 2024 nokoe -# Copyright 2024 tpart -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, version 3 of the License only. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -class_name GameServer -extends Node - -var thread = null -var pid = null - -var state = State.TESTING -enum State { - TESTING, - UNAVAILABLE, - FAILED, - STOPPED, - STARTING, - RUNNING, -} - -var sem = Semaphore.new() -var thread_result = null; - -func _ready(): - state = State.TESTING - thread = Thread.new() - thread.start(_test_server) - -func test(): - pass - -func start(): - if state != State.STOPPED and state != State.FAILED: - push_error("server cant be started") - return - state = State.STARTING - thread = Thread.new() - thread.start(_server_exec) - -func stop(): - if state != State.RUNNING: - push_error("server cant be stopped") - return - OS.kill(pid) - -func _test_server(): - var output = [] - thread_result = OS.execute(get_server_path(), ["-v"], output, true, false) - sem.post() - -func _server_exec(): - var args = prepare_args() - thread_result = OS.create_process(get_server_path(), args, false) - if thread_result >= 0: - var ok = false - while not ok: - var conn = StreamPeerTCP.new() - if conn.connect_to_host("127.0.0.1", 27032) == OK: - while conn.poll() == OK: - if conn.get_status() == StreamPeerTCP.STATUS_ERROR: break - elif conn.get_status() == StreamPeerTCP.STATUS_CONNECTED: ok = true; break - OS.delay_msec(10) - OS.delay_msec(500 if not ok else 50) - if !OS.is_process_running(thread_result): - thread_result = -1 - break - sem.post() - -func prepare_args(): - var args = [] - if Global.get_setting("server.data_path") != "": - args.push_back("--data-dir") - args.push_back(Global.get_setting("server.data_path")) - if Global.get_setting("server.name") != "": - args.push_back("--server-name") - args.push_back(Global.get_setting("server.name")) - if Global.get_setting("server.mdns"): - args.push_back("--mdns") - if Global.get_setting("server.upnp"): - args.push_back("--upnp") - if Global.get_setting("server.register"): - args.push_back("--register") - return args - -func get_server_path() -> String: - var path: String = Global.get_setting("server.binary_path") - if path != "": - return path - else: - return "hurrycurry-server" - -func _process(_delta): - match state: - State.TESTING: - if sem.try_wait(): - print("Server: Test result=", thread_result) - if thread_result == 0: state = State.STOPPED - else: state = State.UNAVAILABLE - thread.wait_to_finish() - thread = null - State.STARTING: - if sem.try_wait(): - if thread_result >= 0: - state = State.RUNNING - pid = thread_result - print("Server: Started pid=", thread_result) - else: - state = State.FAILED - print("Server: Failed") - thread.wait_to_finish() - thread = null - State.RUNNING: - if not OS.is_process_running(pid): - print("Server: Stopped") - state = State.STOPPED - pid = null - -func _exit_tree(): - if thread != null: thread.wait_to_finish() - if pid != null: OS.kill(pid) diff --git a/client/service/editor.gd b/client/service/editor.gd new file mode 100644 index 00000000..c600b715 --- /dev/null +++ b/client/service/editor.gd @@ -0,0 +1,31 @@ +# Hurry Curry! - a game about cooking +# Copyright 2024 metamuffin +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, version 3 of the License only. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +class_name EditorService +extends Service + +func name(): + return "Editor" + +func arguments(): + return [] + +func exe_path() -> String: + var path: String = Global.get_setting("server.editor_binary_path") + if path != "": return path + else: return "hurrycurry-editor" + +func test_port(): + return 27035 diff --git a/client/service/server.gd b/client/service/server.gd new file mode 100644 index 00000000..b2db04de --- /dev/null +++ b/client/service/server.gd @@ -0,0 +1,44 @@ +# Hurry Curry! - a game about cooking +# Copyright 2024 metamuffin +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, version 3 of the License only. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +class_name ServerService +extends Service + +func name(): + return "Server" + +func arguments(): + var args = [] + if Global.get_setting("server.data_path") != "": + args.push_back("--data-dir") + args.push_back(Global.get_setting("server.data_path")) + if Global.get_setting("server.name") != "": + args.push_back("--server-name") + args.push_back(Global.get_setting("server.name")) + if Global.get_setting("server.mdns"): + args.push_back("--mdns") + if Global.get_setting("server.upnp"): + args.push_back("--upnp") + if Global.get_setting("server.register"): + args.push_back("--register") + return args + +func exe_path() -> String: + var path: String = Global.get_setting("server.binary_path") + if path != "": return path + else: return "hurrycurry-server" + +func test_port(): + return 27032 diff --git a/client/service/service.gd b/client/service/service.gd new file mode 100644 index 00000000..f19364c7 --- /dev/null +++ b/client/service/service.gd @@ -0,0 +1,122 @@ +# Hurry Curry! - a game about cooking +# Copyright 2024 metamuffin +# Copyright 2024 nokoe +# Copyright 2024 tpart +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, version 3 of the License only. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +class_name Service +extends Node + + +func name(): + return "Unknown" +func arguments(): + return [] +func exe_path(): + return "false" +func test_port(): + return 25565 + +var thread = null +var pid = null + +var state = State.TESTING +enum State { + TESTING, + UNAVAILABLE, + FAILED, + STOPPED, + STARTING, + RUNNING, +} + +var sem = Semaphore.new() +var thread_result = null; + +func _ready(): + state = State.TESTING + thread = Thread.new() + thread.start(_test_server) + +func test(): + pass + +func start(): + if state != State.STOPPED and state != State.FAILED: + push_error(name() + " can't be started") + return + state = State.STARTING + thread = Thread.new() + thread.start(_server_exec) + +func stop(): + if state != State.RUNNING: + push_error(name() + " can't be stopped") + return + OS.kill(pid) + +func _test_server(): + var output = [] + print(name() + ": Binary path is " + exe_path()) + thread_result = OS.execute(exe_path(), ["-v"], output, true, false) + print(output) + sem.post() + +func _server_exec(): + var args = arguments() + thread_result = OS.create_process(exe_path(), args, false) + if thread_result >= 0: + var ok = false + while not ok: + var conn = StreamPeerTCP.new() + if conn.connect_to_host("127.0.0.1", test_port()) == OK: + while conn.poll() == OK: + if conn.get_status() == StreamPeerTCP.STATUS_ERROR: break + elif conn.get_status() == StreamPeerTCP.STATUS_CONNECTED: ok = true; break + OS.delay_msec(10) + OS.delay_msec(500 if not ok else 50) + if !OS.is_process_running(thread_result): + thread_result = -1 + break + sem.post() + +func _process(_delta): + match state: + State.TESTING: + if sem.try_wait(): + print(name() + ": Test result=", thread_result) + if thread_result == 0: state = State.STOPPED + else: state = State.UNAVAILABLE + thread.wait_to_finish() + thread = null + State.STARTING: + if sem.try_wait(): + if thread_result >= 0: + state = State.RUNNING + pid = thread_result + print(name() + ": Started pid=", thread_result) + else: + state = State.FAILED + print(name() + ": Failed") + thread.wait_to_finish() + thread = null + State.RUNNING: + if not OS.is_process_running(pid): + print(name() + ": Stopped") + state = State.STOPPED + pid = null + +func _exit_tree(): + if pid != null: OS.kill(pid) + if thread != null: thread.wait_to_finish() diff --git a/client/settings.gd b/client/settings.gd index 80984b22..409e4b6b 100644 --- a/client/settings.gd +++ b/client/settings.gd @@ -66,6 +66,7 @@ static func get_root(): ), SettingsCategory.new("server", [ TextSetting.new("binary_path", ""), + TextSetting.new("editor_binary_path", ""), TextSetting.new("data_path", ""), TextSetting.new("name", "A Hurry Curry! Server"), ToggleSetting.new("upnp", false), diff --git a/locale/en.ini b/locale/en.ini index 74faa4f3..9626499f 100644 --- a/locale/en.ini +++ b/locale/en.ini @@ -64,7 +64,13 @@ c.menu.play.server_starting=Server is starting… c.menu.play.server_stop=Stop server c.menu.play.server_testing=Server (testing) c.menu.play.server_unavailable=Server (unavailable) -c.menu.play.server=Server +c.menu.play.editor_failed=Editor (failed) +c.menu.play.editor_start=Start map editor +c.menu.play.editor_starting=Editor is starting… +c.menu.play.editor_stop=Stop map editor +c.menu.play.editor_testing=Map Editor (testing) +c.menu.play.editor_unavailable=Map Editor (unavailable) +c.menu.play.editor=Map Editor c.menu.play=Play c.menu.quit=Quit c.menu.settings=Settings diff --git a/server/editor/src/main.rs b/server/editor/src/main.rs index cd0bdbec..8577aec1 100644 --- a/server/editor/src/main.rs +++ b/server/editor/src/main.rs @@ -16,6 +16,7 @@ use std::{ fs::{File, read_to_string}, io::Write, net::SocketAddr, + process::exit, thread::{sleep, spawn}, time::{Duration, Instant}, }; @@ -23,6 +24,9 @@ use tokio::net::{TcpListener, TcpStream}; #[derive(Parser)] struct Args { + /// Print version, then exit + #[arg(short, long)] + version: bool, #[arg(short, long, default_value = "127.0.0.1")] bind_addr: String, #[arg(short, long, default_value = "27035")] @@ -36,13 +40,9 @@ pub enum Command { #[clap(alias = "p")] Play, /// Save current map to permanent storage - Save { - name: Option, - }, + Save { name: Option }, /// Load map from storage - Load { - name: Option, - }, + Load { name: Option }, /// Teleport to spawnpoint Spawn { #[arg(short, long)] @@ -54,6 +54,10 @@ pub enum Command { async fn main() -> Result<()> { env_logger::init_from_env("LOG"); let args = Args::parse(); + if args.version { + println!("{}", env!("CARGO_PKG_VERSION")); + exit(0); + } let ws_listener = TcpListener::bind((args.bind_addr, args.port)).await?; let mut ms = None; loop { -- cgit v1.2.3-70-g09d2