Represents a connected Unity Editor instance
| 52 | |
| 53 | @dataclass |
| 54 | class UnityInstance: |
| 55 | """Represents a connected Unity Editor instance""" |
| 56 | |
| 57 | instance_id: str # Project path (e.g., "/Users/dev/MyGame") |
| 58 | project_name: str |
| 59 | unity_version: str |
| 60 | bridge_version: str = "" |
| 61 | ref_id: int = 0 # Stable reference ID assigned by InstanceRegistry |
| 62 | capabilities: list[str] = field(default_factory=list) |
| 63 | status: InstanceStatus = InstanceStatus.DISCONNECTED |
| 64 | status_detail: str | None = None |
| 65 | reader: asyncio.StreamReader | None = None |
| 66 | writer: asyncio.StreamWriter | None = None |
| 67 | registered_at: float = field(default_factory=time.time) |
| 68 | last_heartbeat: float = field(default_factory=time.time) |
| 69 | reloading_since: float | None = None |
| 70 | # Command queue (FIFO) |
| 71 | command_queue: deque[QueuedCommand] = field(default_factory=deque) |
| 72 | queue_enabled: bool = QUEUE_ENABLED |
| 73 | |
| 74 | @property |
| 75 | def is_connected(self) -> bool: |
| 76 | return self.writer is not None and not self.writer.is_closing() and self.status != InstanceStatus.DISCONNECTED |
| 77 | |
| 78 | @property |
| 79 | def is_available(self) -> bool: |
| 80 | """Can accept commands""" |
| 81 | return self.is_connected and self.status == InstanceStatus.READY |
| 82 | |
| 83 | @property |
| 84 | def queue_size(self) -> int: |
| 85 | """Current queue size""" |
| 86 | return len(self.command_queue) |
| 87 | |
| 88 | @property |
| 89 | def is_queue_full(self) -> bool: |
| 90 | """Check if queue is full""" |
| 91 | return len(self.command_queue) >= QUEUE_MAX_SIZE |
| 92 | |
| 93 | def to_dict(self, is_default: bool = False) -> dict: |
| 94 | """Convert to dictionary for API response""" |
| 95 | d = { |
| 96 | "ref_id": self.ref_id, |
| 97 | "instance_id": self.instance_id, |
| 98 | "project_name": self.project_name, |
| 99 | "unity_version": self.unity_version, |
| 100 | "bridge_version": self.bridge_version, |
| 101 | "status": self.status.value, |
| 102 | "is_default": is_default, |
| 103 | "capabilities": self.capabilities, |
| 104 | "queue_size": self.queue_size, |
| 105 | } |
| 106 | if self.status_detail is not None: |
| 107 | d["status_detail"] = self.status_detail |
| 108 | return d |
| 109 | |
| 110 | def update_heartbeat(self) -> None: |
| 111 | """Update last heartbeat timestamp""" |
no outgoing calls