@@ -66,12 +66,71 @@ public void SetUnityHelloMessage(HelloMessage? hello)
6666 private DateTime _domainReloadGracePeriodEnd = DateTime . MinValue ;
6767 private static readonly TimeSpan DefaultGracePeriod = TimeSpan . FromSeconds ( 60 ) ;
6868
69+ // Editor readiness tracking (main thread responsive after hello handshake)
70+ private bool _isEditorReady = false ;
71+ private TaskCompletionSource _editorReadySignal = new ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
72+
6973 // Event-driven signals (replace polling loops)
7074 private TaskCompletionSource _connectionSignal = new ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
7175 private TaskCompletionSource _domainReloadCompleteSignal = new ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
7276
7377 public bool IsUnityConnected => UnityConnection ? . State == WebSocketState . Open ;
7478
79+ public bool IsEditorReady
80+ {
81+ get
82+ {
83+ lock ( _lock )
84+ {
85+ return _isEditorReady && IsUnityConnected ;
86+ }
87+ }
88+ }
89+
90+ public void SetEditorReady ( bool ready )
91+ {
92+ lock ( _lock )
93+ {
94+ _isEditorReady = ready ;
95+ if ( ready )
96+ {
97+ _editorReadySignal . TrySetResult ( ) ;
98+ }
99+ else
100+ {
101+ if ( _editorReadySignal . Task . IsCompleted )
102+ _editorReadySignal = new ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
103+ }
104+ }
105+ }
106+
107+ /// <summary>
108+ /// Wait for the editor main thread to become responsive.
109+ /// Returns true if ready within timeout, false if timeout expired.
110+ /// </summary>
111+ public async Task < bool > WaitForEditorReadyAsync ( TimeSpan timeout , CancellationToken cancellationToken = default )
112+ {
113+ Task signal ;
114+ lock ( _lock )
115+ {
116+ if ( _isEditorReady && IsUnityConnected ) return true ;
117+ signal = _editorReadySignal . Task ;
118+ }
119+
120+ using var cts = CancellationTokenSource . CreateLinkedTokenSource ( cancellationToken ) ;
121+ cts . CancelAfter ( timeout ) ;
122+
123+ try
124+ {
125+ await signal . WaitAsync ( cts . Token ) ;
126+ return true ;
127+ }
128+ catch ( OperationCanceledException ) when ( ! cancellationToken . IsCancellationRequested )
129+ {
130+ return false ;
131+ }
132+ }
133+
75134 /// <summary>
76135 /// Wait for Unity to connect (or reconnect after domain reload).
77136 /// Returns true if connected within timeout, false if timeout expired.
@@ -183,6 +242,11 @@ public void SetUnityConnection(WebSocket? connection)
183242 // Clear hello message when Unity disconnects
184243 _unityHelloMessage = null ;
185244
245+ // Reset editor readiness — must re-probe after reconnect
246+ _isEditorReady = false ;
247+ if ( _editorReadySignal . Task . IsCompleted )
248+ _editorReadySignal = new ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
249+
186250 // Reset connection signal so future waiters block until next connection
187251 if ( _connectionSignal . Task . IsCompleted )
188252 _connectionSignal = new ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
@@ -237,6 +301,11 @@ public bool ClearUnityConnectionIfCurrent(WebSocket expected)
237301 UnityConnection = null ;
238302 _unityHelloMessage = null ;
239303
304+ // Reset editor readiness — must re-probe after reconnect
305+ _isEditorReady = false ;
306+ if ( _editorReadySignal . Task . IsCompleted )
307+ _editorReadySignal = new ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
308+
240309 if ( _connectionSignal . Task . IsCompleted )
241310 _connectionSignal = new ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
242311
@@ -312,6 +381,11 @@ public void OnDomainReloadStarting()
312381 {
313382 lock ( _lock )
314383 {
384+ // Reset editor readiness — domain reload means main thread is blocked
385+ _isEditorReady = false ;
386+ if ( _editorReadySignal . Task . IsCompleted )
387+ _editorReadySignal = new ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
388+
315389 _isDomainReloadInProgress = true ;
316390 _domainReloadGracePeriodEnd = DateTime . UtcNow . Add ( DefaultGracePeriod ) ;
317391
0 commit comments