From 95e766e360df333b418b51854279c71f2cc030b0 Mon Sep 17 00:00:00 2001 From: hatayama Date: Tue, 21 Jul 2026 05:25:22 +0900 Subject: [PATCH 1/2] Add Warning to compile response when Play Mode compiles with active pause points Compiling while Play Mode is active stops Play Mode and triggers a domain reload, silently discarding both the Play session state and every enabled pause-point Harmony patch. Users had no signal this happened until they noticed their pause points stopped firing. CompileUseCase now captures whether Play Mode was active and how many pause points were enabled before the PlayMode-stop step mutates that state, and surfaces a Warning on the compile response describing the loss. UloopPausePointRegistry gains GetActiveCount(); Compile.Editor is wired to it via the same InternalsVisibleTo + asmdef-reference pattern already used by PausePoint.Editor, RunTests.Editor, and other consumers. The decision logic itself lives in a pure CompilePausePointWarningBuilder, unit-tested directly to avoid needing a real Play Mode session in an EditMode test. Also verified the physical/message-callback pause-point warning (OnCollisionEnter, OnTriggerEnter, etc.) is already fully implemented and test-covered from a prior PR; no new code was needed there. --- .../CompilePausePointWarningBuilderTests.cs | 49 +++++++++++++++++++ ...mpilePausePointWarningBuilderTests.cs.meta | 11 +++++ Assets/Tests/Editor/CompileUseCaseTests.cs | 2 + .../CompilePausePointWarningBuilder.cs | 22 +++++++++ .../CompilePausePointWarningBuilder.cs.meta | 11 +++++ .../Compile/CompileResponse.cs | 6 +++ .../FirstPartyTools/Compile/CompileUseCase.cs | 9 ++++ ...Loop.FirstPartyTools.Compile.Editor.asmdef | 3 +- .../src/Runtime/PausePoints/AssemblyInfo.cs | 1 + .../PausePoints/UloopPausePointRegistry.cs | 17 +++++++ .../compile_status_response_contract.json | 3 +- 11 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 Assets/Tests/Editor/CompilePausePointWarningBuilderTests.cs create mode 100644 Assets/Tests/Editor/CompilePausePointWarningBuilderTests.cs.meta create mode 100644 Packages/src/Editor/FirstPartyTools/Compile/CompilePausePointWarningBuilder.cs create mode 100644 Packages/src/Editor/FirstPartyTools/Compile/CompilePausePointWarningBuilder.cs.meta diff --git a/Assets/Tests/Editor/CompilePausePointWarningBuilderTests.cs b/Assets/Tests/Editor/CompilePausePointWarningBuilderTests.cs new file mode 100644 index 0000000000..02c62a15dd --- /dev/null +++ b/Assets/Tests/Editor/CompilePausePointWarningBuilderTests.cs @@ -0,0 +1,49 @@ +using NUnit.Framework; + +using io.github.hatayama.UnityCliLoop.FirstPartyTools; + +namespace io.github.hatayama.UnityCliLoop.Tests.Editor +{ + /// + /// Verifies the compile Warning text only appears when Play Mode was active with at least + /// one enabled pause point, and states the domain-reload loss when it does. + /// + [TestFixture] + public sealed class CompilePausePointWarningBuilderTests + { + [Test] + public void BuildWarning_WhenNotPlayingAtRequestStart_ReturnsNull() + { + // Verifies no warning is produced when Play Mode was not active, regardless of marker count. + string warning = CompilePausePointWarningBuilder.BuildWarning( + wasPlayingAtRequestStart: false, + activePausePointCount: 3); + + Assert.That(warning, Is.Null); + } + + [Test] + public void BuildWarning_WhenPlayingButNoActivePausePoints_ReturnsNull() + { + // Verifies no warning is produced when Play Mode was active but no pause point is enabled. + string warning = CompilePausePointWarningBuilder.BuildWarning( + wasPlayingAtRequestStart: true, + activePausePointCount: 0); + + Assert.That(warning, Is.Null); + } + + [Test] + public void BuildWarning_WhenPlayingWithActivePausePoints_MentionsCountAndDomainReloadLoss() + { + // Verifies the warning names the active count and explains what the domain reload discards. + string warning = CompilePausePointWarningBuilder.BuildWarning( + wasPlayingAtRequestStart: true, + activePausePointCount: 2); + + Assert.That(warning, Does.Contain("2 enabled pause point")); + Assert.That(warning, Does.Contain("Play session state")); + Assert.That(warning, Does.Contain("pause point patches")); + } + } +} diff --git a/Assets/Tests/Editor/CompilePausePointWarningBuilderTests.cs.meta b/Assets/Tests/Editor/CompilePausePointWarningBuilderTests.cs.meta new file mode 100644 index 0000000000..376f488350 --- /dev/null +++ b/Assets/Tests/Editor/CompilePausePointWarningBuilderTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b619b8f058fc54c9d855b5ae93c5d746 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/Editor/CompileUseCaseTests.cs b/Assets/Tests/Editor/CompileUseCaseTests.cs index 4c7970c3e5..909f41db09 100644 --- a/Assets/Tests/Editor/CompileUseCaseTests.cs +++ b/Assets/Tests/Editor/CompileUseCaseTests.cs @@ -67,6 +67,8 @@ public async Task CompileAsync_WhenExecutionLayerStoresDelayedSuccess_DoesNotSto Assert.That(compileResultSessionRepository.StoreCount, Is.EqualTo(1)); Assert.That(response.Success, Is.True); Assert.That(response.ProjectRoot, Is.Not.Empty); + // Verifies no pause-point Warning appears outside Play Mode (the only state an EditMode test can exercise). + Assert.That(response.Warning, Is.Null); UnityCliLoopStoredCompileResult storedResult = compileResultSessionRepository.GetCompileResult("compile_test_request"); Assert.That(storedResult.ResultJson, Does.Contain("\"ProjectRoot\":")); diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompilePausePointWarningBuilder.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompilePausePointWarningBuilder.cs new file mode 100644 index 0000000000..6825e0e288 --- /dev/null +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompilePausePointWarningBuilder.cs @@ -0,0 +1,22 @@ +namespace io.github.hatayama.UnityCliLoop.FirstPartyTools +{ + /// + /// Builds the compile Warning text for the case where Play Mode was active with enabled + /// pause points at the moment compile was requested: the domain reload that follows discards + /// both the Play session state and every pause-point Harmony patch. + /// + internal static class CompilePausePointWarningBuilder + { + public static string BuildWarning(bool wasPlayingAtRequestStart, int activePausePointCount) + { + if (!wasPlayingAtRequestStart || activePausePointCount <= 0) + { + return null; + } + + return "Play Mode was active with " + activePausePointCount + " enabled pause point(s). " + + "The compile stops Play Mode and the domain reload discards the Play session state " + + "and all pause point patches — re-enable pause points after the compile completes."; + } + } +} diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompilePausePointWarningBuilder.cs.meta b/Packages/src/Editor/FirstPartyTools/Compile/CompilePausePointWarningBuilder.cs.meta new file mode 100644 index 0000000000..075a1fa46b --- /dev/null +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompilePausePointWarningBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0fd68b00b4e134f7bbd52a920ebea900 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileResponse.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileResponse.cs index a3cd64c8e7..9532967598 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompileResponse.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileResponse.cs @@ -73,6 +73,12 @@ public class CompileResponse : UnityCliLoopToolResponse /// public string ProjectRoot { get; set; } + /// + /// Optional warning about a condition compile does not block on, e.g. Play Mode being + /// active with enabled pause points whose patches the domain reload will discard. + /// + public string Warning { get; set; } + /// /// Create a new CompileResponse /// diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs index a864833350..5a9d64c0c9 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs @@ -5,6 +5,7 @@ using UnityEngine; using io.github.hatayama.UnityCliLoop.Domain; +using io.github.hatayama.UnityCliLoop.Runtime; using io.github.hatayama.UnityCliLoop.ToolContracts; namespace io.github.hatayama.UnityCliLoop.FirstPartyTools @@ -68,6 +69,11 @@ public async Task CompileAsync(CompileSchema request, Cancellat string correlationId = ResolveCorrelationId(request); LogCompileRequestReceived(request, correlationId); + // Captured before PlayMode preparation can stop Play Mode, so the warning reflects + // the state compile was actually requested in, not the state after this method mutates it. + bool wasPlayingAtRequestStart = EditorApplication.isPlaying; + int activePausePointCountAtRequestStart = UloopPausePointRegistry.GetActiveCount(); + DateTime utcNow = DateTime.UtcNow; _compileSessionLifecycleService.ClearExpiredCompileResult(utcNow); _compileSessionLifecycleService.ClearExpiredPendingCompileRequest(utcNow); @@ -159,6 +165,9 @@ public async Task CompileAsync(CompileSchema request, Cancellat // 4. Result formatting CompileResponse successResponse = CompileResponseFactory.CreateResponse(result, request.ForceRecompile); + successResponse.Warning = CompilePausePointWarningBuilder.BuildWarning( + wasPlayingAtRequestStart, + activePausePointCountAtRequestStart); StampProjectRootForDelayedResponseIfNeeded(request, successResponse); return successResponse; } diff --git a/Packages/src/Editor/FirstPartyTools/Compile/UnityCLILoop.FirstPartyTools.Compile.Editor.asmdef b/Packages/src/Editor/FirstPartyTools/Compile/UnityCLILoop.FirstPartyTools.Compile.Editor.asmdef index acb1fb1610..a220a2bc0a 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/UnityCLILoop.FirstPartyTools.Compile.Editor.asmdef +++ b/Packages/src/Editor/FirstPartyTools/Compile/UnityCLILoop.FirstPartyTools.Compile.Editor.asmdef @@ -5,7 +5,8 @@ "GUID:5c4588558a3624eacbce0f50007cf1eb", "GUID:e5952ef560e1641f68b2687e6045bf5b", "GUID:d427b32aad9cb44fc8e962437c9dbcd8", - "GUID:fc3fd32eddbee40e39c2d76dc184957b" + "GUID:fc3fd32eddbee40e39c2d76dc184957b", + "GUID:527f26a36b5043c2bd4d4036d04cd76d" ], "includePlatforms": [ "Editor" diff --git a/Packages/src/Runtime/PausePoints/AssemblyInfo.cs b/Packages/src/Runtime/PausePoints/AssemblyInfo.cs index 19745d9168..06ed62d813 100644 --- a/Packages/src/Runtime/PausePoints/AssemblyInfo.cs +++ b/Packages/src/Runtime/PausePoints/AssemblyInfo.cs @@ -7,6 +7,7 @@ [assembly: InternalsVisibleTo("UnityCLILoop.FirstPartyTools.SimulateMouseUi.Editor")] [assembly: InternalsVisibleTo("UnityCLILoop.FirstPartyTools.ExecuteDynamicCode.Editor")] [assembly: InternalsVisibleTo("UnityCLILoop.FirstPartyTools.Common.Preflight.Editor")] +[assembly: InternalsVisibleTo("UnityCLILoop.FirstPartyTools.Compile.Editor")] [assembly: InternalsVisibleTo("UnityCLILoop.Infrastructure")] [assembly: InternalsVisibleTo("UnityCLILoop.Tests.Editor")] [assembly: InternalsVisibleTo("UnityCLILoop.Tests.PlayMode")] diff --git a/Packages/src/Runtime/PausePoints/UloopPausePointRegistry.cs b/Packages/src/Runtime/PausePoints/UloopPausePointRegistry.cs index 21b753a4e8..7a48565d46 100644 --- a/Packages/src/Runtime/PausePoints/UloopPausePointRegistry.cs +++ b/Packages/src/Runtime/PausePoints/UloopPausePointRegistry.cs @@ -224,6 +224,23 @@ public static bool IsArmed(string id) return Entries.TryGetValue(id, out UloopPausePointEntry entry) && entry.IsEnabled; } + /// + /// Counts entries still armed (IsEnabled), i.e. markers whose Harmony patch is currently + /// installed and would be lost on the next domain reload. + /// + public static int GetActiveCount() + { + int count = 0; + foreach (UloopPausePointEntry entry in Entries.Values) + { + if (entry.IsEnabled) + { + count++; + } + } + return count; + } + private static UloopPausePointSnapshot HitCore( string id, UloopPausePointCapturedVariableFrame capturedFrame, diff --git a/tests/contracts/compile_status_response_contract.json b/tests/contracts/compile_status_response_contract.json index e5366231cd..fff442079d 100644 --- a/tests/contracts/compile_status_response_contract.json +++ b/tests/contracts/compile_status_response_contract.json @@ -14,7 +14,8 @@ "Message": "Compile result is available.", "ErrorCode": null, "NextActions": null, - "ProjectRoot": null + "ProjectRoot": null, + "Warning": null }, "Message": "Compile result is available." } From 8f0bdf3dcfbacf97ba1fb0d46ef0d34c62214689 Mon Sep 17 00:00:00 2001 From: hatayama Date: Tue, 21 Jul 2026 06:12:45 +0900 Subject: [PATCH 2/2] Fix pause-point compile Warning being lost on the stored-response CLI polling path CompileUseCase.CompileAsync attached the pause-point Warning only to the CompileResponse it directly returns, but the default `uloop compile` flow (WaitForDomainReload=true) instead polls get-compile-status, which reads a separately constructed CompileResponse stored by CompileController via CompileResultSessionRecorder. Thread the computed warning string through CompilationExecutionService, CompileController, and CompileResultSessionRecorder so both response-construction call sites share the same value, and update the CompileController/ CompileResponseFactory call sites broken by the new required parameter. --- .../CompileCheckerExample.cs | 2 ++ .../CompileCheckWindow/CompileEditorWindow.cs | 2 +- .../Editor/CompileResponseFactoryTests.cs | 18 +++++++++--------- .../Editor/CompileSessionResultStoreTests.cs | 4 ++-- Assets/Tests/Editor/CompileUseCaseTests.cs | 2 +- .../Compile/CompilationExecutionService.cs | 5 +++-- .../Compile/CompileController.cs | 17 ++++++++++++----- .../Compile/CompileResponseFactory.cs | 13 +++++++++---- .../Compile/CompileResultSessionRecorder.cs | 5 +++-- .../FirstPartyTools/Compile/CompileUseCase.cs | 18 +++++++++--------- 10 files changed, 51 insertions(+), 35 deletions(-) diff --git a/Assets/Editor/CompileCheckWindow/CompileCheckerExample.cs b/Assets/Editor/CompileCheckWindow/CompileCheckerExample.cs index 6513290c47..c30e590bdf 100644 --- a/Assets/Editor/CompileCheckWindow/CompileCheckerExample.cs +++ b/Assets/Editor/CompileCheckWindow/CompileCheckerExample.cs @@ -30,6 +30,7 @@ public static async void TestCompileChecker() CompileResult result = await compileController.TryCompileAsync( forceRecompile: false, + pausePointWarning: null, ct: CancellationToken.None); CompilerMessage[] err = result.Errors; CompilerMessage[] warning = result.Warnings; @@ -71,6 +72,7 @@ public static async void TestForceCompileChecker() // Example of forced re-compilation CompileResult result = await compileController.TryCompileAsync( forceRecompile: true, + pausePointWarning: null, ct: CancellationToken.None); CompilerMessage[] err = result.Errors; CompilerMessage[] warning = result.Warnings; diff --git a/Assets/Editor/CompileCheckWindow/CompileEditorWindow.cs b/Assets/Editor/CompileCheckWindow/CompileEditorWindow.cs index dcece94e45..db97f157a4 100644 --- a/Assets/Editor/CompileCheckWindow/CompileEditorWindow.cs +++ b/Assets/Editor/CompileCheckWindow/CompileEditorWindow.cs @@ -135,7 +135,7 @@ private async Task ExecuteCompileAsync() return; } - CompileResult result = await _compileController.TryCompileAsync(_forceRecompile, CancellationToken.None); + CompileResult result = await _compileController.TryCompileAsync(_forceRecompile, pausePointWarning: null, CancellationToken.None); if (ShouldRunExecuteDynamicCodeReadinessAfterCompile(result)) { _isPostCompileReadinessRunning = true; diff --git a/Assets/Tests/Editor/CompileResponseFactoryTests.cs b/Assets/Tests/Editor/CompileResponseFactoryTests.cs index fde03ad9bf..01d2677bac 100644 --- a/Assets/Tests/Editor/CompileResponseFactoryTests.cs +++ b/Assets/Tests/Editor/CompileResponseFactoryTests.cs @@ -42,7 +42,7 @@ public void CreateResponse_WhenNormalCompileCompletes_MapsDetailedIssues() warnings: new[] { warning }); CompileResponse response = - CompileResponseFactory.CreateResponse(result, forceRecompile: false); + CompileResponseFactory.CreateResponse(result, forceRecompile: false, pausePointWarning: null); Assert.That(response.Success, Is.False); Assert.That(response.ErrorCount, Is.EqualTo(1)); @@ -75,7 +75,7 @@ public void CreateResponse_WhenForceCompileIsUnknown_ExplainsNullDetails() message: null); CompileResponse response = - CompileResponseFactory.CreateResponse(result, forceRecompile: true); + CompileResponseFactory.CreateResponse(result, forceRecompile: true, pausePointWarning: null); Assert.That(response.Success, Is.False); Assert.That(response.ErrorCount, Is.Null); @@ -102,7 +102,7 @@ public void CreateResponse_WhenForceCompileHasOutcome_ExplainsNullDetails() message: "Internal force compile status message."); CompileResponse response = - CompileResponseFactory.CreateResponse(result, forceRecompile: true); + CompileResponseFactory.CreateResponse(result, forceRecompile: true, pausePointWarning: null); Assert.That(response.Success, Is.False); Assert.That(response.ErrorCount, Is.Null); @@ -144,7 +144,7 @@ public void CreateResponse_WhenIndeterminateNonForceCompileHasCounts_PreservesCo message: null); CompileResponse response = - CompileResponseFactory.CreateResponse(result, forceRecompile: false); + CompileResponseFactory.CreateResponse(result, forceRecompile: false, pausePointWarning: null); Assert.That(response.Success, Is.False); Assert.That(response.ErrorCount, Is.EqualTo(1)); @@ -179,7 +179,7 @@ public void CreateResponse_WhenForceCompileHasPreservedFailure_MapsDetailedIssue preserveDetailsWhenForceRecompile: true); CompileResponse response = - CompileResponseFactory.CreateResponse(result, forceRecompile: true); + CompileResponseFactory.CreateResponse(result, forceRecompile: true, pausePointWarning: null); Assert.That(response.Success, Is.False); Assert.That(response.ErrorCount, Is.EqualTo(1)); @@ -210,7 +210,7 @@ public void CreateResponse_WhenExternalSceneCannotBeResolved_AddsNextActions() message: error.message); CompileResponse response = - CompileResponseFactory.CreateResponse(result, forceRecompile: false); + CompileResponseFactory.CreateResponse(result, forceRecompile: false, pausePointWarning: null); Assert.That(response.NextActions, Is.Not.Null); Assert.That(response.NextActions, Has.Length.EqualTo(2)); @@ -245,7 +245,7 @@ public void CreateResponse_WhenExternalSceneCannotBeReloaded_AddsNextActions() message: error.message); CompileResponse response = - CompileResponseFactory.CreateResponse(result, forceRecompile: false); + CompileResponseFactory.CreateResponse(result, forceRecompile: false, pausePointWarning: null); Assert.That(response.NextActions, Is.Not.Null); Assert.That(response.NextActions, Has.Length.EqualTo(2)); @@ -275,7 +275,7 @@ public void CreateResponse_WhenExternalSceneStopMessageUsesDifferentWording_Does message: error.message); CompileResponse response = - CompileResponseFactory.CreateResponse(result, forceRecompile: false); + CompileResponseFactory.CreateResponse(result, forceRecompile: false, pausePointWarning: null); Assert.That(response.NextActions, Is.Null); } @@ -301,7 +301,7 @@ public void CreateResponse_WhenUnityTestFrameworkSymbolIsMissing_AddsTestAsmdefH warnings: Array.Empty()); CompileResponse response = - CompileResponseFactory.CreateResponse(result, forceRecompile: false); + CompileResponseFactory.CreateResponse(result, forceRecompile: false, pausePointWarning: null); Assert.That(response.Message, Does.Contain("TestAssemblies")); Assert.That(response.Message, Does.Contain("com.unity.test-framework")); diff --git a/Assets/Tests/Editor/CompileSessionResultStoreTests.cs b/Assets/Tests/Editor/CompileSessionResultStoreTests.cs index ef0938d60a..e3c7750320 100644 --- a/Assets/Tests/Editor/CompileSessionResultStoreTests.cs +++ b/Assets/Tests/Editor/CompileSessionResultStoreTests.cs @@ -97,9 +97,9 @@ public void StoreCompileResult_WhenEquivalentNormalResponsesAreStoredTwice_Write errors: Array.Empty(), warnings: new[] { warning }); CompileResponse firstResponse = - CompileResponseFactory.CreateResponse(result, forceRecompile: false); + CompileResponseFactory.CreateResponse(result, forceRecompile: false, pausePointWarning: null); CompileResponse secondResponse = - CompileResponseFactory.CreateResponse(result, forceRecompile: false); + CompileResponseFactory.CreateResponse(result, forceRecompile: false, pausePointWarning: null); CompileSessionResultStore.StoreCompileResult( compileResultSessionRepository, diff --git a/Assets/Tests/Editor/CompileUseCaseTests.cs b/Assets/Tests/Editor/CompileUseCaseTests.cs index 909f41db09..73f81e6910 100644 --- a/Assets/Tests/Editor/CompileUseCaseTests.cs +++ b/Assets/Tests/Editor/CompileUseCaseTests.cs @@ -49,7 +49,7 @@ public async Task CompileAsync_WhenExecutionLayerStoresDelayedSuccess_DoesNotSto compileSessionLifecycleService, compileResultSessionRepository, pendingCompileSessionRepository); - useCase.SetCompilationExecutionForTesting((compileRequest, ct) => + useCase.SetCompilationExecutionForTesting((compileRequest, pausePointWarning, ct) => { ct.ThrowIfCancellationRequested(); CompileResultSessionRecorder.RecordCompileResult( diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompilationExecutionService.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompilationExecutionService.cs index d89e208d62..ada3400089 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompilationExecutionService.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompilationExecutionService.cs @@ -34,8 +34,9 @@ public CompilationExecutionService( /// Execute compilation asynchronously /// /// Compile request with force and delayed-result settings. + /// Optional Warning to carry onto the shaped response, e.g. when Play Mode was active with enabled pause points. /// Compilation result - public async Task ExecuteCompilationAsync(CompileSchema request, CancellationToken ct) + public async Task ExecuteCompilationAsync(CompileSchema request, string pausePointWarning, CancellationToken ct) { if (request == null) { @@ -47,7 +48,7 @@ public async Task ExecuteCompilationAsync(CompileSchema request, _pendingCompileSessionRepository); compileController.SetResultRecordingContext(CompileResultRecordingContext.Create(request)); compileController.SetExternalSceneChangePolicy(request.ReloadExternalSceneChanges); - return await compileController.TryCompileAsync(request.ForceRecompile, ct).ConfigureAwait(false); + return await compileController.TryCompileAsync(request.ForceRecompile, pausePointWarning, ct).ConfigureAwait(false); } } } diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileController.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileController.cs index 15b06c64e9..71b6091282 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompileController.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileController.cs @@ -22,6 +22,7 @@ public class CompileController : IDisposable private List _compileMessages = new(); private TaskCompletionSource _currentCompileTask; private bool _isForceCompile = false; + private string _pendingPausePointWarning; private bool _reloadExternalSceneChanges = true; private CompileResultRecordingContext _resultRecordingContext = CompileResultRecordingContext.Disabled(); private DateTime _compileStartedAtUtc = DateTime.MinValue; @@ -96,6 +97,7 @@ internal void SetExternalSceneChangePolicy(bool reloadExternalSceneChanges) /// Executes compilation asynchronously. /// /// Whether to force a recompile. + /// Optional Warning to carry onto the shaped response, e.g. when Play Mode was active with enabled pause points. /// Cancellation token for the compile execution. /// The compilation result. /// Thrown when the task is not found during compilation. @@ -103,8 +105,10 @@ internal void SetExternalSceneChangePolicy(bool reloadExternalSceneChanges) /// Callers must validate editor compilation state before invoking compile execution; /// the production pipeline does this in CompileUseCase. /// - public async Task TryCompileAsync(bool forceRecompile, CancellationToken ct) + public async Task TryCompileAsync(bool forceRecompile, string pausePointWarning, CancellationToken ct) { + _pendingPausePointWarning = pausePointWarning; + if (_isCompiling) { // If compilation is already in progress, wait for the current task. @@ -136,7 +140,7 @@ public async Task TryCompileAsync(bool forceRecompile, Cancellati }); CompileResult result = CompileResultFactory.CreateExternalSceneChangeFailureResult(sceneChangeResult); - RecordCompileResultIfNeeded(result); + RecordCompileResultIfNeeded(result, pausePointWarning: null); return result; } @@ -321,7 +325,7 @@ private void CompleteCompileRequest(CompileResult result, bool unregisterEvents) // Completion subscribers are outside this controller, so state cleanup cannot depend on them returning. try { - RecordCompileResultIfNeeded(result); + RecordCompileResultIfNeeded(result, _pendingPausePointWarning); if (unregisterEvents) { @@ -341,13 +345,14 @@ private void CompleteCompileRequest(CompileResult result, bool unregisterEvents) _isForceCompile = false; _resultRecordingContext = CompileResultRecordingContext.Disabled(); _compileStartedAtUtc = DateTime.MinValue; + _pendingPausePointWarning = null; } task?.TrySetResult(result); } } - private void RecordCompileResultIfNeeded(CompileResult result) + private void RecordCompileResultIfNeeded(CompileResult result, string pausePointWarning) { UnityEngine.Debug.Assert(result != null, "result must not be null"); @@ -362,7 +367,8 @@ private void RecordCompileResultIfNeeded(CompileResult result) _resultRecordingContext.RequestId, _resultRecordingContext.ForceRecompile, result, - _resultRecordingContext.RequestId); + _resultRecordingContext.RequestId, + pausePointWarning); } /// @@ -462,6 +468,7 @@ public void Cleanup() _reloadExternalSceneChanges = true; _resultRecordingContext = CompileResultRecordingContext.Disabled(); _compileStartedAtUtc = DateTime.MinValue; + _pendingPausePointWarning = null; } /// diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileResponseFactory.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileResponseFactory.cs index 03816741c8..cf051a27a5 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompileResponseFactory.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileResponseFactory.cs @@ -27,24 +27,27 @@ internal static class CompileResponseFactory internal static CompileResponse CreateResponse( CompileResult result, - bool forceRecompile) + bool forceRecompile, + string pausePointWarning) { Debug.Assert(result != null, "result must not be null"); if (forceRecompile && !result.PreserveDetailsWhenForceRecompile) { - return CreateForceCompileResult(result); + return CreateForceCompileResult(result, pausePointWarning); } if (result.IsIndeterminate) { - return new CompileResponse( + CompileResponse indeterminateResponse = new CompileResponse( success: result.Success == true, errorCount: result.ErrorCount, warningCount: result.WarningCount, errors: null, warnings: null, message: result.Message ?? "Compilation status is unknown. Use get-logs to inspect the compiler output."); + indeterminateResponse.Warning = pausePointWarning; + return indeterminateResponse; } CompileResponse response = new CompileResponse( @@ -55,6 +58,7 @@ internal static CompileResponse CreateResponse( warnings: ToIssues(result.Warnings), message: AddMissingTestFrameworkReferenceHint(result.Message, result.Errors)); response.NextActions = CreateExternalSceneChangeNextActions(result.Message); + response.Warning = pausePointWarning; return response; } @@ -73,7 +77,7 @@ private static string[] CreateExternalSceneChangeNextActions(string message) return ExternalSceneChangeNextActions; } - private static CompileResponse CreateForceCompileResult(CompileResult result) + private static CompileResponse CreateForceCompileResult(CompileResult result, string pausePointWarning) { ForceCompileUnknownResult unknownResult = ForceCompileUnknownResult.Create(); CompileResponse response = new CompileResponse( @@ -85,6 +89,7 @@ private static CompileResponse CreateForceCompileResult(CompileResult result) message: unknownResult.Message); response.ErrorCode = ForceCompileUnknownResult.ErrorCodeText; response.NextActions = new[] { ForceCompileUnknownResult.NextActionText }; + response.Warning = pausePointWarning; return response; } diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileResultSessionRecorder.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileResultSessionRecorder.cs index de6034ccf6..4b6c4a4470 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompileResultSessionRecorder.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileResultSessionRecorder.cs @@ -16,14 +16,15 @@ internal static CompileResponse RecordCompileResult( string requestId, bool forceRecompile, CompileResult result, - string correlationId) + string correlationId, + string pausePointWarning = null) { Debug.Assert(compileResultSessionRepository != null, "compileResultSessionRepository must not be null"); Debug.Assert(pendingCompileSessionRepository != null, "pendingCompileSessionRepository must not be null"); Debug.Assert(!string.IsNullOrWhiteSpace(requestId), "requestId must not be null or whitespace"); Debug.Assert(result != null, "result must not be null"); - CompileResponse response = CompileResponseFactory.CreateResponse(result, forceRecompile); + CompileResponse response = CompileResponseFactory.CreateResponse(result, forceRecompile, pausePointWarning); return RecordCompileResponse( compileResultSessionRepository, pendingCompileSessionRepository, diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs index 5a9d64c0c9..35c9edd120 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs @@ -22,7 +22,7 @@ public class CompileUseCase private readonly UnityCliLoopCompileSessionLifecycleService _compileSessionLifecycleService; private readonly ICompileResultSessionRepository _compileResultSessionRepository; private readonly IPendingCompileSessionRepository _pendingCompileSessionRepository; - private Func> _executeCompilationAsync; + private Func> _executeCompilationAsync; public CompileUseCase( UnityCliLoopCompileSessionLifecycleService compileSessionLifecycleService, @@ -45,7 +45,7 @@ public CompileUseCase( /// /// Replaces compilation execution for tests that must not start Unity's real compilation pipeline. /// - internal void SetCompilationExecutionForTesting(Func> executeCompilationAsync) + internal void SetCompilationExecutionForTesting(Func> executeCompilationAsync) { Debug.Assert(executeCompilationAsync != null, "executeCompilationAsync must not be null"); _executeCompilationAsync = executeCompilationAsync ?? @@ -159,15 +159,15 @@ public async Task CompileAsync(CompileSchema request, Cancellat } // 3. Compilation execution + string pausePointWarning = CompilePausePointWarningBuilder.BuildWarning( + wasPlayingAtRequestStart, + activePausePointCountAtRequestStart); ct.ThrowIfCancellationRequested(); - CompileResult result = await _executeCompilationAsync(request, ct).ConfigureAwait(false); + CompileResult result = await _executeCompilationAsync(request, pausePointWarning, ct).ConfigureAwait(false); // 4. Result formatting CompileResponse successResponse = - CompileResponseFactory.CreateResponse(result, request.ForceRecompile); - successResponse.Warning = CompilePausePointWarningBuilder.BuildWarning( - wasPlayingAtRequestStart, - activePausePointCountAtRequestStart); + CompileResponseFactory.CreateResponse(result, request.ForceRecompile, pausePointWarning); StampProjectRootForDelayedResponseIfNeeded(request, successResponse); return successResponse; } @@ -356,14 +356,14 @@ private static string CreateRequestId() return $"compile_{unixTimeMilliseconds}_{correlationId}"; } - private Task ExecuteCompilationWithDefaultServiceAsync(CompileSchema request, CancellationToken ct) + private Task ExecuteCompilationWithDefaultServiceAsync(CompileSchema request, string pausePointWarning, CancellationToken ct) { Debug.Assert(request != null, "request must not be null"); CompilationExecutionService executionService = new( _compileResultSessionRepository, _pendingCompileSessionRepository); - return executionService.ExecuteCompilationAsync(request, ct); + return executionService.ExecuteCompilationAsync(request, pausePointWarning, ct); } } }