diff --git a/MCPForUnity/Editor/Services/EditorStateCache.cs b/MCPForUnity/Editor/Services/EditorStateCache.cs index 54625c2e4..d02b26528 100644 --- a/MCPForUnity/Editor/Services/EditorStateCache.cs +++ b/MCPForUnity/Editor/Services/EditorStateCache.cs @@ -260,8 +260,8 @@ static EditorStateCache() EditorApplication.playModeStateChanged += _ => ForceUpdate("playmode"); // Tracks whether an assembly compilation is actually running, for - // GetActualIsCompiling's Play-mode check. Statics reset on domain reload - // and this [InitializeOnLoad] ctor re-subscribes, so the flag is per-domain. + // GetActualIsCompiling. Statics reset on domain reload and this + // [InitializeOnLoad] ctor re-subscribes, so the flag is per-domain. UnityEditor.Compilation.CompilationPipeline.compilationStarted += _ => _pipelineCompilationRunning = true; UnityEditor.Compilation.CompilationPipeline.compilationFinished += _ => _pipelineCompilationRunning = false; @@ -288,7 +288,7 @@ private static void OnUpdate() { // Throttle to reduce overhead while keeping the snapshot fresh enough for polling clients. double now = EditorApplication.timeSinceStartup; - // Use GetActualIsCompiling() to avoid Play mode false positives (issue #582) + // Use GetActualIsCompiling() to avoid isCompiling false positives (issues #549, #1276) bool isCompiling = GetActualIsCompiling(); // Check for compilation edge transitions (always update on these) @@ -543,10 +543,12 @@ public static JObject GetSnapshot() private static bool _pipelineCompilationRunning; /// - /// Returns the actual compilation state, working around a known Unity quirk where - /// EditorApplication.isCompiling can return false positives in Play mode (e.g. a - /// recompile deferred by Recompile-After-Finished-Playing keeps it true for the - /// whole play session). See: https://github.com/CoplayDev/unity-mcp/issues/549 + /// Returns the actual compilation state, working around known Unity quirks where + /// EditorApplication.isCompiling reports false positives while no compilation is + /// running: a recompile deferred by Recompile-After-Finished-Playing keeps it true + /// for the whole play session (issue #549), and a project holding + /// EditorApplication.LockReloadAssemblies keeps it true until the lock is released + /// (issue #1276). In both cases the event-tracked pipeline flag is authoritative. /// internal static bool GetActualIsCompiling() { @@ -556,16 +558,9 @@ internal static bool GetActualIsCompiling() return false; } - // In Play mode, trust the event-tracked pipeline state instead: a deferred - // recompile keeps EditorApplication.isCompiling true without any compilation - // actually running. - if (EditorApplication.isPlaying) - { - return _pipelineCompilationRunning; - } - - // Outside Play mode the raw signal is reliable. - return true; + // Otherwise trust the event-tracked pipeline state: isCompiling stays true for as + // long as an assembly reload is deferred, with no compilation actually running. + return _pipelineCompilationRunning; } } } diff --git a/MCPForUnity/Editor/Services/StdioBridgeReloadHandler.cs b/MCPForUnity/Editor/Services/StdioBridgeReloadHandler.cs index cfa4d7286..4d9bb1ab1 100644 --- a/MCPForUnity/Editor/Services/StdioBridgeReloadHandler.cs +++ b/MCPForUnity/Editor/Services/StdioBridgeReloadHandler.cs @@ -114,14 +114,8 @@ private static void OnAfterAssemblyReload() } // If the editor is not compiling, attempt an immediate restart without relying on editor focus. - bool isCompiling = EditorApplication.isCompiling; - try - { - var pipeline = Type.GetType("UnityEditor.Compilation.CompilationPipeline, UnityEditor"); - var prop = pipeline?.GetProperty("isCompiling", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static); - if (prop != null) isCompiling |= (bool)prop.GetValue(null); - } - catch { } + // Routed through EditorStateCache so a deferred reload (issue #1276) does not block resume. + bool isCompiling = EditorStateCache.GetActualIsCompiling(); if (!isCompiling) { diff --git a/MCPForUnity/Editor/Services/TestJobManager.cs b/MCPForUnity/Editor/Services/TestJobManager.cs index d162476e8..bdf626036 100644 --- a/MCPForUnity/Editor/Services/TestJobManager.cs +++ b/MCPForUnity/Editor/Services/TestJobManager.cs @@ -502,7 +502,7 @@ internal static TestJob GetJob(string jobId) { long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); long initTimeout = job.InitTimeoutMs > 0 ? job.InitTimeoutMs : DefaultInitializationTimeoutMs; - if (!EditorApplication.isCompiling && !EditorApplication.isUpdating && now - job.StartedUnixMs > initTimeout) + if (!EditorStateCache.GetActualIsCompiling() && !EditorApplication.isUpdating && now - job.StartedUnixMs > initTimeout) { McpLog.Warn($"[TestJobManager] Job {jobId} failed to initialize within {initTimeout}ms, auto-failing"); job.Status = TestJobStatus.Failed; @@ -589,7 +589,7 @@ private static string GetBlockedReason(TestJob job) return "editor_unfocused"; } - if (EditorApplication.isCompiling) + if (EditorStateCache.GetActualIsCompiling()) { return "compiling"; } diff --git a/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs b/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs index c4fb2438d..a6a0559d0 100644 --- a/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs +++ b/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs @@ -237,24 +237,10 @@ private static void EnsureStartedOnEditorIdle() } } - private static bool IsCompiling() - { - if (EditorApplication.isCompiling) - { - return true; - } - try - { - Type pipeline = Type.GetType("UnityEditor.Compilation.CompilationPipeline, UnityEditor"); - var prop = pipeline?.GetProperty("isCompiling", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static); - if (prop != null) - { - return (bool)prop.GetValue(null); - } - } - catch { } - return false; - } + // Routed through EditorStateCache so a deferred domain reload (issue #1276) does not + // pin the bridge off: raw EditorApplication.isCompiling stays true for as long as the + // reload is held, and this gates bridge startup. + private static bool IsCompiling() => EditorStateCache.GetActualIsCompiling(); public static void Start() { diff --git a/MCPForUnity/Editor/Tools/ManageScriptableObject.cs b/MCPForUnity/Editor/Tools/ManageScriptableObject.cs index c508e9fc2..2ddd021c3 100644 --- a/MCPForUnity/Editor/Tools/ManageScriptableObject.cs +++ b/MCPForUnity/Editor/Tools/ManageScriptableObject.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text.RegularExpressions; using MCPForUnity.Editor.Helpers; +using MCPForUnity.Editor.Services; using Newtonsoft.Json.Linq; using UnityEditor; using UnityEngine; @@ -44,7 +45,7 @@ public static object HandleCommand(JObject @params) return new ErrorResponse(CodeInvalidParams); } - if (EditorApplication.isCompiling || EditorApplication.isUpdating) + if (EditorStateCache.GetActualIsCompiling() || EditorApplication.isUpdating) { // Unity is transient; treat as retryable on the client side. return new ErrorResponse(CodeCompilingOrReloading, new { hint = "retry" }); diff --git a/MCPForUnity/Editor/Tools/RefreshUnity.cs b/MCPForUnity/Editor/Tools/RefreshUnity.cs index 537472ac0..a7736aa7e 100644 --- a/MCPForUnity/Editor/Tools/RefreshUnity.cs +++ b/MCPForUnity/Editor/Tools/RefreshUnity.cs @@ -109,7 +109,7 @@ await WaitForUnityReadyAsync( } } - string resultingState = EditorApplication.isCompiling + string resultingState = EditorStateCache.GetActualIsCompiling() ? "compiling" : (EditorApplication.isUpdating ? "asset_import" : "idle"); @@ -146,7 +146,7 @@ void Tick() return; } - if (!EditorApplication.isCompiling + if (!EditorStateCache.GetActualIsCompiling() && !EditorApplication.isUpdating && !TestRunStatus.IsRunning && !EditorApplication.isPlayingOrWillChangePlaymode) diff --git a/MCPForUnity/Editor/Tools/UnityReflect.cs b/MCPForUnity/Editor/Tools/UnityReflect.cs index 06aee0d7f..236b74342 100644 --- a/MCPForUnity/Editor/Tools/UnityReflect.cs +++ b/MCPForUnity/Editor/Tools/UnityReflect.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using MCPForUnity.Editor.Helpers; +using MCPForUnity.Editor.Services; using MCPForUnity.Runtime.Helpers; using Newtonsoft.Json.Linq; using UnityEditor; @@ -99,7 +100,7 @@ private static Dictionary GetAssemblyTypeCache() public static object HandleCommand(JObject @params) { - if (EditorApplication.isCompiling) + if (EditorStateCache.GetActualIsCompiling()) return new ErrorResponse("Cannot reflect while Unity is compiling. Wait for domain reload to complete."); if (@params == null)