Sync from GitHub main #1

Open
nomad wants to merge 145 commits from sync/from-github into main
Showing only changes of commit 86ec79eeb9 - Show all commits
+58 -9
View File
@@ -15,7 +15,10 @@ from pathlib import Path
from typing import Any, Callable from typing import Any, Callable
from admin_server import AdminServer, DEFAULT_ADMIN_PORT 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 server_core import FalloutTogetherServer
from transport_server import TransportAwareFalloutTogetherServer
_IPV4_RE = re.compile(r"^\d{1,3}(?:\.\d{1,3}){3}$") _IPV4_RE = re.compile(r"^\d{1,3}(?:\.\d{1,3}){3}$")
@@ -32,6 +35,8 @@ class ServerConfig:
log_verbosity: str = "info" log_verbosity: str = "info"
admin_port: int = DEFAULT_ADMIN_PORT admin_port: int = DEFAULT_ADMIN_PORT
bans_path: str | None = None bans_path: str | None = None
enable_gns_transport: bool = False
gns_bridge_path: str | None = None
@dataclass @dataclass
@@ -76,11 +81,13 @@ class ServerService:
self.config = config or ServerConfig() self.config = config or ServerConfig()
self._server: FalloutTogetherServer | None = None self._server: FalloutTogetherServer | None = None
self._admin: AdminServer | None = None self._admin: AdminServer | None = None
self._gns_adapter: GnsGameplayAdapter | None = None
self._log_listeners: list[Callable[..., None]] = [] self._log_listeners: list[Callable[..., None]] = []
self._log_lock = threading.RLock() self._log_lock = threading.RLock()
self._serve_thread: threading.Thread | None = None self._serve_thread: threading.Thread | None = None
self._running = False self._running = False
self._stop_requested = False self._stop_requested = False
self._stop_event = threading.Event()
def add_log_listener(self, callback: Callable[..., None]) -> None: def add_log_listener(self, callback: Callable[..., None]) -> None:
"""Register a callback for server log messages.""" """Register a callback for server log messages."""
@@ -114,7 +121,7 @@ class ServerService:
return str(Path(__file__).resolve().parent / "bans.json") return str(Path(__file__).resolve().parent / "bans.json")
def _create_server(self) -> FalloutTogetherServer: def _create_server(self) -> FalloutTogetherServer:
return FalloutTogetherServer( return TransportAwareFalloutTogetherServer(
host=self.config.host, host=self.config.host,
port=self.config.port, port=self.config.port,
server_name=self.config.server_name, server_name=self.config.server_name,
@@ -137,6 +144,41 @@ class ServerService:
self._admin.stop() self._admin.stop()
self._admin = None 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: def start(self) -> None:
"""Start the server in a background thread.""" """Start the server in a background thread."""
if self._running: if self._running:
@@ -144,6 +186,7 @@ class ServerService:
return return
self._stop_requested = False self._stop_requested = False
self._stop_event.clear()
self._server = self._create_server() self._server = self._create_server()
self._server.add_log_listener(self._dispatch_log) self._server.add_log_listener(self._dispatch_log)
self._running = True self._running = True
@@ -159,25 +202,29 @@ class ServerService:
raise RuntimeError("Server is already running.") raise RuntimeError("Server is already running.")
self._stop_requested = False self._stop_requested = False
self._stop_event.clear()
self._server = self._create_server() self._server = self._create_server()
self._server.add_log_listener(self._dispatch_log) self._server.add_log_listener(self._dispatch_log)
self._running = True self._running = True
self._start_admin() self._start_admin()
self._run_server_loop()
def _run_server_loop(self) -> None:
try: 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: finally:
self._stop_gns()
self._stop_admin() self._stop_admin()
self._running = False self._running = False
def _serve_forever(self) -> None: def _serve_forever(self) -> None:
"""Internal serve_forever for background thread.""" """Internal server loop for background thread."""
try: self._run_server_loop()
if self._server:
self._server.serve_forever()
finally:
self._stop_admin()
self._running = False
def stop(self) -> None: def stop(self) -> None:
"""Stop the server.""" """Stop the server."""
@@ -185,11 +232,13 @@ class ServerService:
return return
self._stop_requested = True self._stop_requested = True
self._stop_event.set()
if not self._running and self._server is None: if not self._running and self._server is None:
self._dispatch_log("Server is not running.") self._dispatch_log("Server is not running.")
return return
self._stop_admin() self._stop_admin()
self._stop_gns()
if self._server is not None: if self._server is not None:
self._server.stop() self._server.stop()