using System;
using System.Threading.Tasks;
using MCPForUnity.Editor.Constants;
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Services.Transport;
using MCPForUnity.Editor.Windows;
using UnityEditor;
namespace MCPForUnity.Editor.Services
{
/// <summary>
/// Ensures HTTP transports resume after domain reloads similar to the legacy stdio bridge.
/// </summary>
[InitializeOnLoad]
internal static class HttpBridgeReloadHandler
{
private static readonly TimeSpan[] ResumeRetrySchedule =
{
TimeSpan.Zero,
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(3),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(10),
TimeSpan.FromSeconds(30)
};
static HttpBridgeReloadHandler()
{
AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload;
AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload;
}
private static void OnBeforeAssemblyReload()
{
try
{
var transport = MCPServiceLocator.TransportManager;
bool shouldResume = transport.IsRunning(TransportMode.Http);
if (shouldResume)
{
EditorPrefs.SetBool(EditorPrefKeys.ResumeHttpAfterReload, true);
}
else
{
EditorPrefs.DeleteKey(EditorPrefKeys.ResumeHttpAfterReload);
}
if (shouldResume)
{
// beforeAssemblyReload is synchronous; force a synchronous teardown so we do not
// leave an orphaned socket due to an unfinished async close handshake.
transport.ForceStop(TransportMode.Http);
}
}
catch (Exception ex)
{
McpLog.Warn($"Failed to evaluate HTTP bridge reload state: {ex.Message}");
}
}
private static void OnAfterAssemblyReload()
{
bool resume = false;
try
{
// Only resume HTTP if it is still the selected transport.
bool useHttp = EditorConfigurationCache.Instance.UseHttpTransport;
resume = useHttp && EditorPrefs.GetBool(EditorPrefKeys.ResumeHttpAfterReload, false);
if (resume)
{
EditorPrefs.DeleteKey(EditorPrefKeys.ResumeHttpAfterReload);
}
}
catch (Exception ex)
{
McpLog.Warn($"Failed to read HTTP bridge reload flag: {ex.Message}");
resume = false;
}
if (!resume)
{
return;
}
// 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 { }
if (!isCompiling)
{
_ = ResumeHttpWithRetriesAsync();
return;
}
// Fallback when compiling: schedule on the editor loop
EditorApplication.delayCall += () =>
{
_ = ResumeHttpWithRetriesAsync();
};
}
private static async Task ResumeHttpWithRetriesAsync()
{
Exception lastException = null;
for (int i = 0; i < ResumeRetrySchedule.Length; i++)
{
int attempt = i + 1;
McpLog.Debug($"[HTTP Reload] Resume attempt {attempt}/{ResumeRetrySchedule.Length}");
TimeSpan delay = ResumeRetrySchedule[i];
if (delay > TimeSpan.Zero)
{
McpLog.Debug($"[HTTP Reload] Waiting {delay.TotalSeconds:0.#}s before resume attempt {attempt}");
try { await Task.Delay(delay); }
catch { return; }
}
// Abort retries if the user switched transports while we were waiting.
if (!EditorConfigurationCache.Instance.UseHttpTransport)
{
return;
}
try
{
bool started = await MCPServiceLocator.TransportManager.StartAsync(TransportMode.Http);
if (started)
{
McpLog.Debug($"[HTTP Reload] Resume succeeded on attempt {attempt}");
MCPForUnityEditorWindow.RequestHealthVerification();
return;
}
var state = MCPServiceLocator.TransportManager.GetState(TransportMode.Http);
string reason = string.IsNullOrWhiteSpace(state?.Error) ? "no error detail" : state.Error;
McpLog.Debug($"[HTTP Reload] Resume attempt {attempt} failed: {reason}");
}
catch (Exception ex)
{
lastException = ex;
McpLog.Debug($"[HTTP Reload] Resume attempt {attempt} threw: {ex.Message}");
}
}
if (lastException != null)
{
McpLog.Warn($"Failed to resume HTTP MCP bridge after domain reload: {lastException.Message}");
}
else
{
McpLog.Warn("Failed to resume HTTP MCP bridge after domain reload");
}
}
}
}