Relay Server for Unity Bridge Protocol. Handles connections from: - Unity Editor instances (register, status updates, command results) - CLI clients (requests, instance queries)
| 89 | |
| 90 | |
| 91 | class RelayServer: |
| 92 | """ |
| 93 | Relay Server for Unity Bridge Protocol. |
| 94 | |
| 95 | Handles connections from: |
| 96 | - Unity Editor instances (register, status updates, command results) |
| 97 | - CLI clients (requests, instance queries) |
| 98 | """ |
| 99 | |
| 100 | def __init__( |
| 101 | self, |
| 102 | host: str = DEFAULT_HOST, |
| 103 | port: int = DEFAULT_PORT, |
| 104 | reload_grace_period_ms: int = RELOAD_GRACE_PERIOD_MS, |
| 105 | ) -> None: |
| 106 | self.host = host |
| 107 | self.port = port |
| 108 | self.reload_grace_period_ms = reload_grace_period_ms |
| 109 | self.registry = InstanceRegistry() |
| 110 | self.request_cache = RequestCache(ttl_seconds=60.0) |
| 111 | try: |
| 112 | self._relay_version = importlib.metadata.version("unity-cli") |
| 113 | except importlib.metadata.PackageNotFoundError: |
| 114 | self._relay_version = "" |
| 115 | self._server: asyncio.Server | None = None |
| 116 | self._running = False |
| 117 | self._stop_lock = asyncio.Lock() |
| 118 | self._stopped = False |
| 119 | self._pending_commands: dict[str, asyncio.Future[dict[str, Any]]] = {} |
| 120 | self._heartbeat_tasks: dict[str, asyncio.Task] = {} |
| 121 | # Single Outstanding PING: track pending PONG per instance |
| 122 | self._pending_pongs: dict[str, asyncio.Event] = {} |
| 123 | |
| 124 | async def start(self) -> None: |
| 125 | """Start the relay server""" |
| 126 | await self.request_cache.start() |
| 127 | |
| 128 | self._server = await asyncio.start_server( |
| 129 | self._handle_connection, |
| 130 | self.host, |
| 131 | self.port, |
| 132 | ) |
| 133 | self._running = True |
| 134 | |
| 135 | addrs = ", ".join(str(sock.getsockname()) for sock in self._server.sockets) |
| 136 | logger.info(f"Relay Server listening on {addrs}") |
| 137 | |
| 138 | async with self._server: |
| 139 | await self._server.serve_forever() |
| 140 | |
| 141 | async def stop(self) -> None: |
| 142 | """Stop the relay server (idempotent, concurrency-safe). |
| 143 | |
| 144 | Uses ``_stop_lock`` to serialize concurrent calls (e.g. signal handler |
| 145 | and ``finally`` block). Each cleanup step is individually guarded so |
| 146 | that a partial failure does not prevent subsequent resources from being |
| 147 | released. |
| 148 | """ |