"""Server beacon for multicast discovery.""" import asyncio import socket from typing import Callable from ..shared.discovery import ( ServerInfo, DiscoveryMessage, create_multicast_socket, get_local_ip, ) from ..shared.constants import MULTICAST_GROUP, MULTICAST_PORT, BEACON_INTERVAL class ServerBeacon: """Broadcasts server presence and responds to discovery requests.""" def __init__( self, server_name: str, server_port: int, get_player_count: Callable[[], int], ): """Initialize the server beacon. Args: server_name: Name of the server server_port: TCP port the game server is listening on get_player_count: Callable that returns current player count """ self.server_name = server_name self.server_port = server_port self.get_player_count = get_player_count self.running = False self.sock: socket.socket | None = None self.local_ip = get_local_ip() def create_server_info(self) -> ServerInfo: """Create ServerInfo with current server state. Returns: Current server information """ return ServerInfo( host=self.local_ip, port=self.server_port, server_name=self.server_name, players_count=self.get_player_count(), ) async def start(self) -> None: """Start the beacon service.""" self.running = True self.sock = create_multicast_socket(bind=True) self.sock.setblocking(False) print(f"Server beacon started on {MULTICAST_GROUP}:{MULTICAST_PORT}") print(f"Server IP: {self.local_ip}, Port: {self.server_port}") # Run both listener and broadcaster concurrently await asyncio.gather( self.listen_for_discovery(), self.broadcast_presence(), ) async def listen_for_discovery(self) -> None: """Listen for DISCOVER messages and respond.""" loop = asyncio.get_event_loop() while self.running: try: # Receive data (non-blocking with asyncio) data, addr = await loop.sock_recvfrom(self.sock, 1024) # Parse message msg_type, msg_data = DiscoveryMessage.parse(data) if msg_type == DiscoveryMessage.DISCOVER: # Respond to discovery request await self.send_announce(addr) except Exception as e: # Ignore errors and continue listening await asyncio.sleep(0.1) async def send_announce(self, addr: tuple) -> None: """Send SERVER_ANNOUNCE message to a specific address. Args: addr: (host, port) tuple to send to """ try: server_info = self.create_server_info() message = DiscoveryMessage.create_announce(server_info) # Send directly to the requester self.sock.sendto(message, addr) print(f"Sent announcement to {addr}") except Exception as e: print(f"Error sending announcement: {e}") async def broadcast_presence(self) -> None: """Periodically broadcast server presence to multicast group.""" while self.running: try: server_info = self.create_server_info() message = DiscoveryMessage.create_announce(server_info) # Broadcast to multicast group self.sock.sendto(message, (MULTICAST_GROUP, MULTICAST_PORT)) except Exception as e: print(f"Error broadcasting presence: {e}") # Wait before next broadcast await asyncio.sleep(BEACON_INTERVAL) async def stop(self) -> None: """Stop the beacon service.""" self.running = False if self.sock: self.sock.close() print("Server beacon stopped")