Add opt-in GNS host transport
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user