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, }