Split the monolithic test server into a reusable server core and a thin terminal launcher, and add a PySide6 developer GUI. Added server_core.py (FalloutTogetherServer) with lifecycle control, thread-safe client snapshots, stats and log listener support; added client_session.py for per-client state; added dev_server_app.py GUI and requirements.txt. Updated server.py to use the new server core, and revised server/README.md and docs/dev-log.md to document the new structure, usage, and validation steps. Protocol behavior (welcome/transform/disconnect handling and newline-separated JSON) remains unchanged.
54 lines
1.7 KiB
Python
54 lines
1.7 KiB
Python
from __future__ import annotations
|
|
|
|
import socket
|
|
import threading
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
|
|
@dataclass
|
|
class ClientSession:
|
|
connection: socket.socket
|
|
address: tuple[str, int]
|
|
player_id: int
|
|
connected_at: float
|
|
last_packet_at: float | None = None
|
|
last_transform: dict[str, Any] | None = None
|
|
packets_received: int = 0
|
|
packets_sent: int = 0
|
|
packets_broadcast: int = 0
|
|
_lock: Any = field(repr=False, compare=False, default_factory=threading.RLock)
|
|
|
|
@property
|
|
def label(self) -> str:
|
|
return f"{self.address[0]}:{self.address[1]}"
|
|
|
|
def record_received(self, received_at: float) -> None:
|
|
with self._lock:
|
|
self.last_packet_at = received_at
|
|
self.packets_received += 1
|
|
|
|
def record_transform(self, packet: dict[str, Any]) -> None:
|
|
with self._lock:
|
|
self.last_transform = dict(packet)
|
|
|
|
def record_sent(self, *, broadcast: bool = False) -> None:
|
|
with self._lock:
|
|
self.packets_sent += 1
|
|
if broadcast:
|
|
self.packets_broadcast += 1
|
|
|
|
def to_snapshot(self) -> dict[str, Any]:
|
|
with self._lock:
|
|
return {
|
|
"playerId": self.player_id,
|
|
"address": self.address[0],
|
|
"port": self.address[1],
|
|
"connectedAt": self.connected_at,
|
|
"lastPacketAt": self.last_packet_at,
|
|
"lastTransform": dict(self.last_transform) if self.last_transform is not None else None,
|
|
"packetsReceived": self.packets_received,
|
|
"packetsSent": self.packets_sent,
|
|
"packetsBroadcast": self.packets_broadcast,
|
|
}
|