From 0bc7e49e6822fad417d92917b303e3b8fcbc102f Mon Sep 17 00:00:00 2001 From: Martin Vagstad Date: Thu, 19 Feb 2026 19:12:22 +0100 Subject: [PATCH] Fix wait command returning before editor is ready to accept commands After WebSocket connection, the bridge now sends editor.ping RPCs to Unity until the main thread responds, proving the editor isn't blocked on asset import or safe mode. The wait command checks editorReady instead of just unityConnected, with intermediate progress messages and distinct timeout errors for each failure mode. Closes #18 --- UnityCtl.Bridge/BridgeEndpoints.cs | 58 ++++++++- UnityCtl.Bridge/BridgeState.cs | 74 +++++++++++ UnityCtl.Cli/WaitCommand.cs | 27 +++- UnityCtl.Protocol/Constants.cs | 3 + UnityCtl.Protocol/DTOs.cs | 3 + .../Integration/EditorReadinessTests.cs | 122 ++++++++++++++++++ .../Integration/HealthEndpointTests.cs | 7 + .../Unit/Protocol/DtoSerializationTests.cs | 2 + .../Editor/UnityCtlClient.cs | 4 + .../Plugins/UnityCtl.Protocol.dll | Bin 34816 -> 35328 bytes 10 files changed, 293 insertions(+), 7 deletions(-) create mode 100644 UnityCtl.Tests/Integration/EditorReadinessTests.cs diff --git a/UnityCtl.Bridge/BridgeEndpoints.cs b/UnityCtl.Bridge/BridgeEndpoints.cs index 95eb418..a57a6f3 100644 --- a/UnityCtl.Bridge/BridgeEndpoints.cs +++ b/UnityCtl.Bridge/BridgeEndpoints.cs @@ -903,6 +903,58 @@ private static async Task HandleGenericCommandAsync( return JsonResponse(response); } + // --- Editor readiness probing --- + + /// + /// Sends editor.ping commands to Unity until one succeeds, proving the main thread + /// is responsive (not blocked on asset import, safe mode dialog, etc.). + /// + private static async Task ProbeEditorReadinessAsync(BridgeState state, CancellationToken ct) + { + var pingInterval = TimeSpan.FromSeconds(1); + var pingTimeout = TimeSpan.FromSeconds(5); + + Console.WriteLine($"[Bridge] Starting editor readiness probe..."); + + while (!ct.IsCancellationRequested && state.IsUnityConnected) + { + try + { + var pingRequest = CreateInternalRequest(null, UnityCtlCommands.EditorPing); + var response = await state.SendCommandToUnityAsync(pingRequest, pingTimeout, ct); + + if (response.Status == ResponseStatus.Ok) + { + state.SetEditorReady(true); + Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Editor ready (ping succeeded)"); + return; + } + } + catch (TimeoutException) + { + // Unity main thread is blocked — retry after interval + } + catch (InvalidOperationException) + { + // Unity disconnected — stop probing + return; + } + catch (OperationCanceledException) + { + return; + } + + try + { + await Task.Delay(pingInterval, ct); + } + catch (OperationCanceledException) + { + return; + } + } + } + // --- Endpoint mapping --- public static void MapEndpoints(WebApplication app) @@ -919,7 +971,8 @@ public static void MapEndpoints(WebApplication app) ProjectId = state.ProjectId, UnityConnected = state.IsUnityConnected, BridgeVersion = VersionInfo.Version, - UnityPluginVersion = unityHello?.PluginVersion + UnityPluginVersion = unityHello?.PluginVersion, + EditorReady = state.IsEditorReady }; }); @@ -1145,6 +1198,9 @@ await webSocket.SendAsync( true, cancellationToken ); + + // Start background readiness probe — pings Unity until its main thread responds + _ = Task.Run(() => ProbeEditorReadinessAsync(state, cancellationToken), cancellationToken); } private static void HandleResponse(string json, BridgeState state) diff --git a/UnityCtl.Bridge/BridgeState.cs b/UnityCtl.Bridge/BridgeState.cs index e6feee9..53f3d89 100644 --- a/UnityCtl.Bridge/BridgeState.cs +++ b/UnityCtl.Bridge/BridgeState.cs @@ -66,12 +66,71 @@ public void SetUnityHelloMessage(HelloMessage? hello) private DateTime _domainReloadGracePeriodEnd = DateTime.MinValue; private static readonly TimeSpan DefaultGracePeriod = TimeSpan.FromSeconds(60); + // Editor readiness tracking (main thread responsive after hello handshake) + private bool _isEditorReady = false; + private TaskCompletionSource _editorReadySignal = new(TaskCreationOptions.RunContinuationsAsynchronously); + // Event-driven signals (replace polling loops) private TaskCompletionSource _connectionSignal = new(TaskCreationOptions.RunContinuationsAsynchronously); private TaskCompletionSource _domainReloadCompleteSignal = new(TaskCreationOptions.RunContinuationsAsynchronously); public bool IsUnityConnected => UnityConnection?.State == WebSocketState.Open; + public bool IsEditorReady + { + get + { + lock (_lock) + { + return _isEditorReady && IsUnityConnected; + } + } + } + + public void SetEditorReady(bool ready) + { + lock (_lock) + { + _isEditorReady = ready; + if (ready) + { + _editorReadySignal.TrySetResult(); + } + else + { + if (_editorReadySignal.Task.IsCompleted) + _editorReadySignal = new(TaskCreationOptions.RunContinuationsAsynchronously); + } + } + } + + /// + /// Wait for the editor main thread to become responsive. + /// Returns true if ready within timeout, false if timeout expired. + /// + public async Task WaitForEditorReadyAsync(TimeSpan timeout, CancellationToken cancellationToken = default) + { + Task signal; + lock (_lock) + { + if (_isEditorReady && IsUnityConnected) return true; + signal = _editorReadySignal.Task; + } + + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + cts.CancelAfter(timeout); + + try + { + await signal.WaitAsync(cts.Token); + return true; + } + catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) + { + return false; + } + } + /// /// Wait for Unity to connect (or reconnect after domain reload). /// Returns true if connected within timeout, false if timeout expired. @@ -183,6 +242,11 @@ public void SetUnityConnection(WebSocket? connection) // Clear hello message when Unity disconnects _unityHelloMessage = null; + // Reset editor readiness — must re-probe after reconnect + _isEditorReady = false; + if (_editorReadySignal.Task.IsCompleted) + _editorReadySignal = new(TaskCreationOptions.RunContinuationsAsynchronously); + // Reset connection signal so future waiters block until next connection if (_connectionSignal.Task.IsCompleted) _connectionSignal = new(TaskCreationOptions.RunContinuationsAsynchronously); @@ -237,6 +301,11 @@ public bool ClearUnityConnectionIfCurrent(WebSocket expected) UnityConnection = null; _unityHelloMessage = null; + // Reset editor readiness — must re-probe after reconnect + _isEditorReady = false; + if (_editorReadySignal.Task.IsCompleted) + _editorReadySignal = new(TaskCreationOptions.RunContinuationsAsynchronously); + if (_connectionSignal.Task.IsCompleted) _connectionSignal = new(TaskCreationOptions.RunContinuationsAsynchronously); @@ -312,6 +381,11 @@ public void OnDomainReloadStarting() { lock (_lock) { + // Reset editor readiness — domain reload means main thread is blocked + _isEditorReady = false; + if (_editorReadySignal.Task.IsCompleted) + _editorReadySignal = new(TaskCreationOptions.RunContinuationsAsynchronously); + _isDomainReloadInProgress = true; _domainReloadGracePeriodEnd = DateTime.UtcNow.Add(DefaultGracePeriod); diff --git a/UnityCtl.Cli/WaitCommand.cs b/UnityCtl.Cli/WaitCommand.cs index 505b7df..15e80a4 100644 --- a/UnityCtl.Cli/WaitCommand.cs +++ b/UnityCtl.Cli/WaitCommand.cs @@ -15,7 +15,7 @@ public static class WaitCommand public static Command CreateCommand() { - var waitCommand = new Command("wait", "Wait until Unity is connected to the bridge"); + var waitCommand = new Command("wait", "Wait until Unity Editor is connected and ready to accept commands"); var timeoutOption = new Option( "--timeout", @@ -57,10 +57,11 @@ public static Command CreateCommand() if (!json) { - Console.WriteLine("Waiting for Unity to connect..."); + Console.WriteLine("Waiting for Unity Editor to be ready..."); } var bridgeFound = false; + var unityConnected = false; var elapsed = 0; BridgeClient? client = null; int? lastPort = null; @@ -86,19 +87,28 @@ public static Command CreateCommand() if (health != null) { bridgeFound = true; - if (health.UnityConnected) + + // Log transition to connected (once) + if (health.UnityConnected && !unityConnected && !json) + { + Console.WriteLine("Unity connected, waiting for editor to be ready..."); + unityConnected = true; + } + + if (health.EditorReady) { if (json) { Console.WriteLine(JsonHelper.Serialize(new { unityConnected = true, + editorReady = true, bridgeRunning = true })); } else { - Console.WriteLine("Connected!"); + Console.WriteLine("Editor ready!"); } return; } @@ -127,7 +137,8 @@ public static Command CreateCommand() { Console.WriteLine(JsonHelper.Serialize(new { - unityConnected = false, + unityConnected, + editorReady = false, bridgeRunning = bridgeFound })); } @@ -142,10 +153,14 @@ public static Command CreateCommand() Console.Error.WriteLine($"Timed out after {timeout}s. Bridge not found."); Console.Error.WriteLine(" Run 'unityctl bridge start' first."); } - else + else if (!unityConnected) { Console.Error.WriteLine($"Timed out after {timeout}s. Unity is not connected to the bridge."); } + else + { + Console.Error.WriteLine($"Timed out after {timeout}s. Unity is connected but the editor is not ready (may still be importing assets)."); + } } context.ExitCode = 1; }); diff --git a/UnityCtl.Protocol/Constants.cs b/UnityCtl.Protocol/Constants.cs index 080b4ef..9672836 100644 --- a/UnityCtl.Protocol/Constants.cs +++ b/UnityCtl.Protocol/Constants.cs @@ -33,6 +33,9 @@ public static class UnityCtlCommands public const string RecordStart = "record.start"; public const string RecordStop = "record.stop"; public const string RecordStatus = "record.status"; + + // Editor readiness + public const string EditorPing = "editor.ping"; } public static class UnityCtlEvents diff --git a/UnityCtl.Protocol/DTOs.cs b/UnityCtl.Protocol/DTOs.cs index cb0407e..5a1e630 100644 --- a/UnityCtl.Protocol/DTOs.cs +++ b/UnityCtl.Protocol/DTOs.cs @@ -19,6 +19,9 @@ public class HealthResult [JsonProperty("unityPluginVersion")] public string? UnityPluginVersion { get; init; } + + [JsonProperty("editorReady")] + public required bool EditorReady { get; init; } } public class LogEntry diff --git a/UnityCtl.Tests/Integration/EditorReadinessTests.cs b/UnityCtl.Tests/Integration/EditorReadinessTests.cs new file mode 100644 index 0000000..4cd0206 --- /dev/null +++ b/UnityCtl.Tests/Integration/EditorReadinessTests.cs @@ -0,0 +1,122 @@ +using UnityCtl.Protocol; +using UnityCtl.Tests.Fakes; +using UnityCtl.Tests.Helpers; +using Xunit; + +namespace UnityCtl.Tests.Integration; + +/// +/// Tests for the editor readiness probe (editor.ping) that verifies Unity's main thread +/// is responsive before reporting the editor as ready. +/// +public class EditorReadinessTests : IAsyncLifetime +{ + private readonly BridgeTestFixture _fixture = new(); + + public Task InitializeAsync() => _fixture.InitializeAsync(); + public Task DisposeAsync() => _fixture.DisposeAsync(); + + [Fact] + public async Task EditorReady_BecomesTrue_AfterPingSucceeds() + { + // FakeUnity's default handler responds OK to editor.ping, + // so the readiness probe should succeed shortly after connection. + await AssertExtensions.WaitUntilAsync( + () => _fixture.BridgeState.IsEditorReady, + timeout: TimeSpan.FromSeconds(10), + message: "Editor should become ready after successful ping"); + } + + [Fact] + public async Task HealthEndpoint_IncludesEditorReady() + { + // Wait for readiness probe to complete + await AssertExtensions.WaitUntilAsync( + () => _fixture.BridgeState.IsEditorReady, + timeout: TimeSpan.FromSeconds(10)); + + var response = await _fixture.HttpClient.GetAsync("/health"); + response.EnsureSuccessStatusCode(); + + var json = await response.Content.ReadAsStringAsync(); + var health = JsonHelper.Deserialize(json); + + Assert.NotNull(health); + Assert.True(health.UnityConnected); + Assert.True(health.EditorReady); + } + + [Fact] + public async Task EditorReady_ResetToFalse_AfterDisconnect() + { + // Wait for readiness + await AssertExtensions.WaitUntilAsync( + () => _fixture.BridgeState.IsEditorReady, + timeout: TimeSpan.FromSeconds(10)); + + // Disconnect + await _fixture.FakeUnity.DisconnectAsync(); + await AssertExtensions.WaitUntilAsync( + () => !_fixture.BridgeState.IsUnityConnected); + + Assert.False(_fixture.BridgeState.IsEditorReady); + } + + [Fact] + public async Task EditorReady_RestoredAfterReconnect() + { + // Wait for initial readiness + await AssertExtensions.WaitUntilAsync( + () => _fixture.BridgeState.IsEditorReady, + timeout: TimeSpan.FromSeconds(10)); + + // Disconnect + await _fixture.FakeUnity.DisconnectAsync(); + await AssertExtensions.WaitUntilAsync( + () => !_fixture.BridgeState.IsUnityConnected); + Assert.False(_fixture.BridgeState.IsEditorReady); + + // Reconnect with new client + var newFake = _fixture.CreateFakeUnity(); + await newFake.ConnectAsync(_fixture.BaseUri); + + // Should become ready again after ping succeeds + await AssertExtensions.WaitUntilAsync( + () => _fixture.BridgeState.IsEditorReady, + timeout: TimeSpan.FromSeconds(10), + message: "Editor should become ready again after reconnection"); + + await newFake.DisposeAsync(); + } + + [Fact] + public async Task EditorReady_ResetDuringDomainReload() + { + // Wait for initial readiness + await AssertExtensions.WaitUntilAsync( + () => _fixture.BridgeState.IsEditorReady, + timeout: TimeSpan.FromSeconds(10)); + + // Simulate domain reload starting + await _fixture.FakeUnity.SendEventAsync( + UnityCtlEvents.DomainReloadStarting, new { }); + await Task.Delay(50); + + Assert.False(_fixture.BridgeState.IsEditorReady); + } + + [Fact] + public async Task HealthEndpoint_ShowsNotReady_WhenDisconnected() + { + await _fixture.FakeUnity.DisconnectAsync(); + await Task.Delay(100); + + var response = await _fixture.HttpClient.GetAsync("/health"); + var json = await response.Content.ReadAsStringAsync(); + var health = JsonHelper.Deserialize(json); + + Assert.NotNull(health); + Assert.False(health.UnityConnected); + Assert.False(health.EditorReady); + } +} diff --git a/UnityCtl.Tests/Integration/HealthEndpointTests.cs b/UnityCtl.Tests/Integration/HealthEndpointTests.cs index ccf3275..0d04320 100644 --- a/UnityCtl.Tests/Integration/HealthEndpointTests.cs +++ b/UnityCtl.Tests/Integration/HealthEndpointTests.cs @@ -17,6 +17,12 @@ public class HealthEndpointTests : IAsyncLifetime [Fact] public async Task Health_ReturnsOk_WhenUnityConnected() { + // EditorReady is set asynchronously after hello handshake via editor.ping probe. + // FakeUnity auto-responds OK to unknown commands, so the probe completes quickly. + await AssertExtensions.WaitUntilAsync( + () => _fixture.BridgeState.IsEditorReady, + timeout: TimeSpan.FromSeconds(10)); + var response = await _fixture.HttpClient.GetAsync("/health"); response.EnsureSuccessStatusCode(); @@ -27,6 +33,7 @@ public async Task Health_ReturnsOk_WhenUnityConnected() Assert.Equal("ok", health.Status); Assert.Equal(_fixture.ProjectId, health.ProjectId); Assert.True(health.UnityConnected); + Assert.True(health.EditorReady); Assert.NotNull(health.BridgeVersion); Assert.Equal("0.3.6", health.UnityPluginVersion); } diff --git a/UnityCtl.Tests/Unit/Protocol/DtoSerializationTests.cs b/UnityCtl.Tests/Unit/Protocol/DtoSerializationTests.cs index 4413bb9..5c6ec58 100644 --- a/UnityCtl.Tests/Unit/Protocol/DtoSerializationTests.cs +++ b/UnityCtl.Tests/Unit/Protocol/DtoSerializationTests.cs @@ -14,6 +14,7 @@ public void HealthResult_Serializes_CamelCase() Status = "ok", ProjectId = "proj-abc", UnityConnected = true, + EditorReady = true, BridgeVersion = "0.3.6", UnityPluginVersion = "0.3.6" }; @@ -24,6 +25,7 @@ public void HealthResult_Serializes_CamelCase() Assert.Equal("ok", jObj["status"]?.ToString()); Assert.Equal("proj-abc", jObj["projectId"]?.ToString()); Assert.True(jObj["unityConnected"]?.Value()); + Assert.True(jObj["editorReady"]?.Value()); Assert.Equal("0.3.6", jObj["bridgeVersion"]?.ToString()); } diff --git a/UnityCtl.UnityPackage/Editor/UnityCtlClient.cs b/UnityCtl.UnityPackage/Editor/UnityCtlClient.cs index 3e6d57e..80b14f2 100644 --- a/UnityCtl.UnityPackage/Editor/UnityCtlClient.cs +++ b/UnityCtl.UnityPackage/Editor/UnityCtlClient.cs @@ -481,6 +481,10 @@ private void HandleCommand(RequestMessage request) result = Editor.RecordingManager.Instance.GetStatus(); break; + case UnityCtlCommands.EditorPing: + result = new { status = "pong" }; + break; + default: SendResponseError(request.RequestId, "unknown_command", $"Unknown command: {request.Command}"); return; diff --git a/UnityCtl.UnityPackage/Plugins/UnityCtl.Protocol.dll b/UnityCtl.UnityPackage/Plugins/UnityCtl.Protocol.dll index d04742ae9383019378be0fae571e7789f3424a96..7d07cef5ebc1620250a8b7435d708c9ac8614a8d 100644 GIT binary patch literal 35328 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 literal 34816 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 ObC3Vu!~a7y@V@}Ni!ZhS