Critical fixes from Hoshe review: 1. Race condition: defer TCP connect to _process() with retry logic (MAX_CONNECT_RETRIES=20, 100ms interval) so server has time to bind. 2. Buffer corruption: disconnect on oversized message instead of clearing valid buffered data (_corrupt flag, fail-safe). 3. Silent input drop: send_input() returns Error so callers can detect encode/validation failures. Warnings addressed: - ServerProcess validates server_path exists before spawning - SIGKILL and health check TODOs documented for future work - Diagonal keybindings documented as intentional deferral - send_message uses single put_data() call (no partial write risk) - Static frame helpers documented as D-030 Layer 2 test-only New tests (36 total, up from 33): - Partial read scenario (chunked TCP delivery) - Multi-message sequential decode (exercises buffer corruption fix) - send_input error return on invalid action Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
48 lines
1.4 KiB
GDScript
48 lines
1.4 KiB
GDScript
class_name ServerProcess
|
|
## Manages the Rust simulation server as a subprocess (D-020).
|
|
|
|
var _pid: int = -1
|
|
|
|
|
|
## Start the server process. Returns the PID, or -1 on failure.
|
|
## Validates that server_path exists and is accessible before spawning.
|
|
func start(server_path: String, args: Array = []) -> int:
|
|
if _pid > 0 and is_alive():
|
|
push_warning("ServerProcess: server already running (pid %d)" % _pid)
|
|
return _pid
|
|
if not FileAccess.file_exists(server_path):
|
|
push_error("ServerProcess: server binary not found at %s" % server_path)
|
|
return -1
|
|
_pid = OS.create_process(server_path, args)
|
|
if _pid <= 0:
|
|
push_error("ServerProcess: failed to start server at %s" % server_path)
|
|
return _pid
|
|
|
|
|
|
## Check if the server process is still running.
|
|
## NOTE: OS.is_process_running() doesn't distinguish starting/healthy/zombie.
|
|
## TODO: Add health check (e.g. TCP readiness probe) when server supports it.
|
|
func is_alive() -> bool:
|
|
if _pid <= 0:
|
|
return false
|
|
return OS.is_process_running(_pid)
|
|
|
|
|
|
## Stop the server process.
|
|
## TODO: Send graceful shutdown signal before SIGKILL when server supports it.
|
|
func stop() -> void:
|
|
if _pid > 0 and is_alive():
|
|
OS.kill(_pid)
|
|
_pid = -1
|
|
|
|
|
|
## Get the server PID (-1 if not started).
|
|
func get_pid() -> int:
|
|
return _pid
|
|
|
|
|
|
## Clean up on destruction — kill the server if still running.
|
|
func _notification(what: int) -> void:
|
|
if what == NOTIFICATION_PREDELETE:
|
|
stop()
|