import asyncio import logging import random from yuxi.channel.extensions.minecraft.protocol import write_bool, write_double logger = logging.getLogger(__name__) PLAYER_POSITION_INTERVAL = 60.0 KEEP_ALIVE_TIMEOUT = 30.0 class KeepAliveManager: def __init__(self, client): self.client = client self._position_task: asyncio.Task | None = None self._timeout_task: asyncio.Task | None = None self._last_keep_alive = 0.0 self._running = False async def start(self) -> None: self._running = True self._last_keep_alive = asyncio.get_event_loop().time() self._position_task = asyncio.create_task(self._position_loop()) self._timeout_task = asyncio.create_task(self._timeout_monitor()) logger.info("KeepAliveManager started") async def stop(self) -> None: self._running = False if self._position_task: self._position_task.cancel() self._position_task = None if self._timeout_task: self._timeout_task.cancel() self._timeout_task = None logger.info("KeepAliveManager stopped") async def _position_loop(self) -> None: x, y, z = 0.0, 64.0, 0.0 while self._running: try: await asyncio.sleep(PLAYER_POSITION_INTERVAL + random.uniform(-5, 5)) data = write_double(x) + write_double(y) + write_double(z) + write_bool(True) await self.client.send_packet(0x1A, data) logger.debug("Player position sent: (%.1f, %.1f, %.1f)", x, y, z) except asyncio.CancelledError: break except Exception: logger.exception("Error sending player position") def record_keep_alive(self) -> None: self._last_keep_alive = asyncio.get_event_loop().time() @property def is_alive(self) -> bool: elapsed = asyncio.get_event_loop().time() - self._last_keep_alive return elapsed < KEEP_ALIVE_TIMEOUT async def _timeout_monitor(self) -> None: while self._running: try: await asyncio.sleep(KEEP_ALIVE_TIMEOUT) if not self.is_alive: logger.warning("Keep-Alive timeout detected, connection may be dead") if self.client and self.client.writer: self.client.writer.close() except asyncio.CancelledError: break except Exception: logger.exception("Error in keep-alive timeout monitor")