From 86ec79eeb978d33881a7137a685a4afeb5af68d7 Mon Sep 17 00:00:00 2001 From: Nomads_Reach <144523850+NomadsReach@users.noreply.github.com> Date: Sun, 16 Aug 2026 00:46:15 -0400 Subject: [PATCH] Add opt-in GNS host transport --- server/server_service.py | 67 ++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/server/server_service.py b/server/server_service.py index 2d26662..7924c3c 100644 --- a/server/server_service.py +++ b/server/server_service.py @@ -15,7 +15,10 @@ from pathlib import Path from typing import Any, Callable from admin_server import AdminServer, DEFAULT_ADMIN_PORT +from gns_gameplay_adapter import GnsGameplayAdapter +from gns_transport import GnsServerTransport from server_core import FalloutTogetherServer +from transport_server import TransportAwareFalloutTogetherServer _IPV4_RE = re.compile(r"^\d{1,3}(?:\.\d{1,3}){3}$") @@ -32,6 +35,8 @@ class ServerConfig: log_verbosity: str = "info" admin_port: int = DEFAULT_ADMIN_PORT bans_path: str | None = None + enable_gns_transport: bool = False + gns_bridge_path: str | None = None @dataclass @@ -76,11 +81,13 @@ class ServerService: self.config = config or ServerConfig() self._server: FalloutTogetherServer | None = None self._admin: AdminServer | None = None + self._gns_adapter: GnsGameplayAdapter | None = None self._log_listeners: list[Callable[..., None]] = [] self._log_lock = threading.RLock() self._serve_thread: threading.Thread | None = None self._running = False self._stop_requested = False + self._stop_event = threading.Event() def add_log_listener(self, callback: Callable[..., None]) -> None: """Register a callback for server log messages.""" @@ -114,7 +121,7 @@ class ServerService: return str(Path(__file__).resolve().parent / "bans.json") def _create_server(self) -> FalloutTogetherServer: - return FalloutTogetherServer( + return TransportAwareFalloutTogetherServer( host=self.config.host, port=self.config.port, server_name=self.config.server_name, @@ -137,6 +144,41 @@ class ServerService: self._admin.stop() self._admin = None + def _start_gns(self) -> None: + if not self.config.enable_gns_transport or self._server is None: + return + if self._gns_adapter is not None: + return + + transport = GnsServerTransport( + self.config.host, + self._server.port, + library_path=self.config.gns_bridge_path, + ) + if transport.local_port != self._server.port: + actual_port = transport.local_port + transport.close() + raise RuntimeError( + f"GNS transport bound UDP {actual_port}, expected UDP {self._server.port}." + ) + + adapter = GnsGameplayAdapter(self._server, transport) + try: + adapter.start() + except Exception: + transport.close() + raise + self._gns_adapter = adapter + self._dispatch_log( + f"GameNetworkingSockets gameplay transport listening on UDP {self._server.port}." + ) + + def _stop_gns(self) -> None: + adapter = self._gns_adapter + self._gns_adapter = None + if adapter is not None: + adapter.stop() + def start(self) -> None: """Start the server in a background thread.""" if self._running: @@ -144,6 +186,7 @@ class ServerService: return self._stop_requested = False + self._stop_event.clear() self._server = self._create_server() self._server.add_log_listener(self._dispatch_log) self._running = True @@ -159,25 +202,29 @@ class ServerService: raise RuntimeError("Server is already running.") self._stop_requested = False + self._stop_event.clear() self._server = self._create_server() self._server.add_log_listener(self._dispatch_log) self._running = True self._start_admin() + self._run_server_loop() + def _run_server_loop(self) -> None: try: - self._server.serve_forever() + if self._server is None: + return + self._server.start() + self._start_gns() + while not self._stop_requested and self._server.is_running(): + self._stop_event.wait(0.1) finally: + self._stop_gns() self._stop_admin() self._running = False def _serve_forever(self) -> None: - """Internal serve_forever for background thread.""" - try: - if self._server: - self._server.serve_forever() - finally: - self._stop_admin() - self._running = False + """Internal server loop for background thread.""" + self._run_server_loop() def stop(self) -> None: """Stop the server.""" @@ -185,11 +232,13 @@ class ServerService: return self._stop_requested = True + self._stop_event.set() if not self._running and self._server is None: self._dispatch_log("Server is not running.") return self._stop_admin() + self._stop_gns() if self._server is not None: self._server.stop()