feat: add allocated server readiness control

This commit is contained in:
Josh Creek
2026-09-01 09:12:10 +01:00
parent 5b80c97337
commit 6caf719158
8 changed files with 218 additions and 4 deletions
+24
View File
@@ -1,6 +1,7 @@
extends Node
const NetCodec = preload("res://scripts/net_codec.gd")
const ServerControlScript = preload("res://scripts/server_control.gd")
# Headless dedicated server entry point (task 1.6). Parses CLI args, hosts
# via NetworkManager, logs structured lines, and watches for physics-tick
@@ -26,6 +27,8 @@ const NetCodec = preload("res://scripts/net_codec.gd")
var _last_physics_frame := 0
var config: ServerConfig = null
var _watchdog_armed := false # skip the first _process(): engine startup scheduling can batch several physics frames before the first idle frame runs, which isn't a real overrun
var _control: ServerControl = null
var _drain_requested := false
func _ready() -> void:
@@ -77,6 +80,15 @@ func _ready() -> void:
printerr("cosmic-clash-server: refusing to start with invalid join-authorisations-file")
get_tree().quit(1)
return
_control = ServerControlScript.new()
_control.name = "ServerControl"
_control.drain_requested.connect(_on_drain_requested)
get_tree().root.add_child.call_deferred(_control)
var control_err := _control.start(int(config.get_value("readiness-port")), OS.get_environment(String(config.get_value("drain-token-env"))))
if control_err != OK:
printerr("cosmic-clash-server: refusing to start with invalid readiness control port")
get_tree().quit(1)
return
NetworkManager.client_connected.connect(_on_client_connected)
NetworkManager.client_disconnected.connect(_on_client_disconnected)
@@ -88,6 +100,8 @@ func _ready() -> void:
ServerLog.error("server_boot_failed", {"port": port, "error": error_string(err)})
get_tree().quit(1)
return
if _control != null:
_control.set_process_ready(true)
_install_match_loop()
ServerLog.info("server_started", {
"port": port, "max_clients": max_clients, "log_level": ServerLog.level_name(),
@@ -119,6 +133,10 @@ func _install_match_loop() -> void:
func _process(_delta: float) -> void:
NetworkManager.poll()
if _drain_requested:
var scene := get_tree().current_scene
if not (is_instance_valid(scene) and scene.is_in_group("game")) and MatchNet.roster.is_empty():
get_tree().quit(0)
var current := Engine.get_physics_frames()
var steps := current - _last_physics_frame
_last_physics_frame = current
@@ -149,3 +167,9 @@ func _on_player_joined(peer_id: int, player_name: String) -> void:
func _on_player_left(peer_id: int) -> void:
ServerLog.info("player_left", {"peer_id": peer_id, "roster": MatchNet.roster.size()})
func _on_drain_requested() -> void:
_drain_requested = true
MatchNet.admissions_open = false
ServerLog.info("server_draining", {"reason": "control_request"})