Skip to content

Commit 9d967f5

Browse files
committed
Merge on previous PR
Merge and use the previous PR's solution to solve the windows issue.
2 parents 81b0f50 + 84f7b85 commit 9d967f5

File tree

4 files changed

+142
-44
lines changed

4 files changed

+142
-44
lines changed

MCPForUnity/Editor/MenuItems/MCPForUnityMenu.cs

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,25 @@
55

66
namespace MCPForUnity.Editor.MenuItems
77
{
8-
/// <summary>
9-
/// Centralized menu items for MCP For Unity
10-
/// </summary>
118
public static class MCPForUnityMenu
129
{
13-
// ========================================
14-
// Main Menu Items
15-
// ========================================
16-
17-
/// <summary>
18-
/// Show the Setup Window
19-
/// </summary>
2010
[MenuItem("Window/MCP For Unity/Setup Window", priority = 1)]
2111
public static void ShowSetupWindow()
2212
{
2313
SetupWindowService.ShowSetupWindow();
2414
}
2515

26-
/// <summary>
27-
/// Toggle the main MCP For Unity window
28-
/// </summary>
2916
[MenuItem("Window/MCP For Unity/Toggle MCP Window %#m", priority = 2)]
3017
public static void ToggleMCPWindow()
3118
{
32-
if (EditorWindow.HasOpenInstances<MCPForUnityEditorWindow>())
19+
if (MCPForUnityEditorWindow.HasAnyOpenWindow())
3320
{
34-
var window = EditorWindow.GetWindow<MCPForUnityEditorWindow>();
35-
if (window != null)
36-
{
37-
window.Close();
38-
}
21+
MCPForUnityEditorWindow.CloseAllOpenWindows();
3922
}
4023
else
4124
{
4225
MCPForUnityEditorWindow.ShowWindow();
4326
}
4427
}
45-
4628
}
4729
}

MCPForUnity/Editor/Setup/SetupWindowService.cs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ public static class SetupWindowService
1818
{
1919
private const string SETUP_COMPLETED_KEY = EditorPrefKeys.SetupCompleted;
2020
private const string SETUP_DISMISSED_KEY = EditorPrefKeys.SetupDismissed;
21-
private static bool _hasCheckedThisSession = false;
21+
22+
// Use SessionState to persist "checked this editor session" across domain reloads.
23+
// SessionState survives assembly reloads within the same Editor session, which prevents
24+
// the setup window from reappearing after code reloads / playmode transitions.
25+
private const string SessionCheckedKey = "MCPForUnity.SetupWindowCheckedThisEditorSession";
2226

2327
static SetupWindowService()
2428
{
@@ -35,10 +39,12 @@ static SetupWindowService()
3539
/// </summary>
3640
private static void CheckSetupNeeded()
3741
{
38-
if (_hasCheckedThisSession)
42+
// Ensure we only run once per Editor session (survives domain reloads).
43+
// This avoids showing the setup dialog repeatedly when scripts recompile or Play mode toggles.
44+
if (SessionState.GetBool(SessionCheckedKey, false))
3945
return;
4046

41-
_hasCheckedThisSession = true;
47+
SessionState.SetBool(SessionCheckedKey, true);
4248

4349
try
4450
{
@@ -48,11 +54,16 @@ private static void CheckSetupNeeded()
4854
bool userOverrodeHttpUrl = EditorPrefs.HasKey(EditorPrefKeys.HttpBaseUrl);
4955

5056
// In Asset Store builds with a remote default URL (and no user override), skip the local setup wizard.
51-
if (!userOverrodeHttpUrl
57+
if (
58+
!userOverrodeHttpUrl
5259
&& McpDistribution.Settings.skipSetupWindowWhenRemoteDefault
53-
&& McpDistribution.Settings.IsRemoteDefault)
60+
&& McpDistribution.Settings.IsRemoteDefault
61+
)
5462
{
55-
McpLog.Info("Skipping Setup Window because this distribution ships with a hosted MCP URL. Open Window/MCP For Unity/Setup Window if you want to configure a local runtime.", always: false);
63+
McpLog.Info(
64+
"Skipping Setup Window because this distribution ships with a hosted MCP URL. Open Window/MCP For Unity/Setup Window if you want to configure a local runtime.",
65+
always: false
66+
);
5667
return;
5768
}
5869

@@ -66,7 +77,10 @@ private static void CheckSetupNeeded()
6677
}
6778
else
6879
{
69-
McpLog.Info("Setup Window skipped - previously completed or dismissed", always: false);
80+
McpLog.Info(
81+
"Setup Window skipped - previously completed or dismissed",
82+
always: false
83+
);
7084
}
7185
}
7286
catch (Exception ex)
@@ -108,6 +122,5 @@ public static void MarkSetupDismissed()
108122
EditorPrefs.SetBool(SETUP_DISMISSED_KEY, true);
109123
McpLog.Info("Setup marked as dismissed");
110124
}
111-
112125
}
113126
}

MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ private enum TransportProtocol
4444
private Button testConnectionButton;
4545

4646
private bool connectionToggleInProgress;
47+
private Task verificationTask;
48+
private string lastHealthStatus;
49+
50+
// Health status constants
51+
private const string HealthStatusUnknown = "Unknown";
52+
private const string HealthStatusHealthy = "Healthy";
53+
private const string HealthStatusPingFailed = "Ping Failed";
54+
private const string HealthStatusUnhealthy = "Unhealthy";
4755

4856
// Events
4957
public event Action OnManualConfigUpdateRequested;
@@ -173,7 +181,7 @@ public void UpdateConnectionStatus()
173181
statusIndicator.AddToClassList("disconnected");
174182
connectionToggleButton.text = "Start Session";
175183

176-
healthStatusLabel.text = "Unknown";
184+
healthStatusLabel.text = HealthStatusUnknown;
177185
healthIndicator.RemoveFromClassList("healthy");
178186
healthIndicator.RemoveFromClassList("warning");
179187
healthIndicator.AddToClassList("unknown");
@@ -408,15 +416,33 @@ private async void OnTestConnectionClicked()
408416
}
409417

410418
public async Task VerifyBridgeConnectionAsync()
419+
{
420+
// Prevent concurrent verification calls
421+
if (verificationTask != null && !verificationTask.IsCompleted)
422+
{
423+
return;
424+
}
425+
426+
verificationTask = VerifyBridgeConnectionInternalAsync();
427+
await verificationTask;
428+
}
429+
430+
private async Task VerifyBridgeConnectionInternalAsync()
411431
{
412432
var bridgeService = MCPServiceLocator.Bridge;
413433
if (!bridgeService.IsRunning)
414434
{
415-
healthStatusLabel.text = "Disconnected";
435+
healthStatusLabel.text = HealthStatusUnknown;
416436
healthIndicator.RemoveFromClassList("healthy");
417437
healthIndicator.RemoveFromClassList("warning");
418438
healthIndicator.AddToClassList("unknown");
419-
McpLog.Warn("Cannot verify connection: Bridge is not running");
439+
440+
// Only log if state changed
441+
if (lastHealthStatus != HealthStatusUnknown)
442+
{
443+
McpLog.Warn("Cannot verify connection: Bridge is not running");
444+
lastHealthStatus = HealthStatusUnknown;
445+
}
420446
return;
421447
}
422448

@@ -426,23 +452,45 @@ public async Task VerifyBridgeConnectionAsync()
426452
healthIndicator.RemoveFromClassList("warning");
427453
healthIndicator.RemoveFromClassList("unknown");
428454

455+
string newStatus;
429456
if (result.Success && result.PingSucceeded)
430457
{
431-
healthStatusLabel.text = "Healthy";
458+
newStatus = HealthStatusHealthy;
459+
healthStatusLabel.text = newStatus;
432460
healthIndicator.AddToClassList("healthy");
433-
McpLog.Debug($"Connection verification successful: {result.Message}");
461+
462+
// Only log if state changed
463+
if (lastHealthStatus != newStatus)
464+
{
465+
McpLog.Debug($"Connection verification successful: {result.Message}");
466+
lastHealthStatus = newStatus;
467+
}
434468
}
435469
else if (result.HandshakeValid)
436470
{
437-
healthStatusLabel.text = "Ping Failed";
471+
newStatus = HealthStatusPingFailed;
472+
healthStatusLabel.text = newStatus;
438473
healthIndicator.AddToClassList("warning");
439-
McpLog.Warn($"Connection verification warning: {result.Message}");
474+
475+
// Log once per distinct warning state
476+
if (lastHealthStatus != newStatus)
477+
{
478+
McpLog.Warn($"Connection verification warning: {result.Message}");
479+
lastHealthStatus = newStatus;
480+
}
440481
}
441482
else
442483
{
443-
healthStatusLabel.text = "Unhealthy";
484+
newStatus = HealthStatusUnhealthy;
485+
healthStatusLabel.text = newStatus;
444486
healthIndicator.AddToClassList("warning");
445-
McpLog.Error($"Connection verification failed: {result.Message}");
487+
488+
// Log once per distinct error state
489+
if (lastHealthStatus != newStatus)
490+
{
491+
McpLog.Error($"Connection verification failed: {result.Message}");
492+
lastHealthStatus = newStatus;
493+
}
446494
}
447495
}
448496
}

MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Threading.Tasks;
5+
using MCPForUnity.Editor.Helpers;
6+
using MCPForUnity.Editor.Services;
7+
using MCPForUnity.Editor.Windows.Components.ClientConfig;
8+
using MCPForUnity.Editor.Windows.Components.Connection;
9+
using MCPForUnity.Editor.Windows.Components.Settings;
510
using UnityEditor;
611
using UnityEditor.UIElements;
712
using UnityEngine;
@@ -30,6 +35,9 @@ public class MCPForUnityEditorWindow : EditorWindow
3035
private VisualElement toolsPanel;
3136

3237
private static readonly HashSet<MCPForUnityEditorWindow> OpenWindows = new();
38+
private bool guiCreated = false;
39+
private double lastRefreshTime = 0;
40+
private const double RefreshDebounceSeconds = 0.5;
3341

3442
private enum ActivePanel
3543
{
@@ -51,8 +59,40 @@ public static void ShowWindow()
5159
var window = GetWindow<MCPForUnityEditorWindow>("MCP For Unity");
5260
window.minSize = new Vector2(500, 600);
5361
}
62+
63+
// Helper to check and manage open windows from other classes
64+
public static bool HasAnyOpenWindow()
65+
{
66+
return OpenWindows.Count > 0;
67+
}
68+
69+
public static void CloseAllOpenWindows()
70+
{
71+
if (OpenWindows.Count == 0)
72+
return;
73+
74+
// Copy to array to avoid modifying the collection while iterating
75+
var arr = new MCPForUnityEditorWindow[OpenWindows.Count];
76+
OpenWindows.CopyTo(arr);
77+
foreach (var window in arr)
78+
{
79+
try
80+
{
81+
window?.Close();
82+
}
83+
catch (Exception ex)
84+
{
85+
McpLog.Warn($"Error closing MCP window: {ex.Message}");
86+
}
87+
}
88+
}
89+
5490
public void CreateGUI()
5591
{
92+
// Guard against repeated CreateGUI calls (e.g., domain reloads)
93+
if (guiCreated)
94+
return;
95+
5696
string basePath = AssetPathUtility.GetMcpPackageRootPath();
5797

5898
// Load main window UXML
@@ -62,7 +102,9 @@ public void CreateGUI()
62102

63103
if (visualTree == null)
64104
{
65-
McpLog.Error($"Failed to load UXML at: {basePath}/Editor/Windows/MCPForUnityEditorWindow.uxml");
105+
McpLog.Error(
106+
$"Failed to load UXML at: {basePath}/Editor/Windows/MCPForUnityEditorWindow.uxml"
107+
);
66108
return;
67109
}
68110

@@ -120,8 +162,10 @@ public void CreateGUI()
120162
var settingsRoot = settingsTree.Instantiate();
121163
settingsContainer.Add(settingsRoot);
122164
settingsSection = new McpSettingsSection(settingsRoot);
123-
settingsSection.OnGitUrlChanged += () => clientConfigSection?.UpdateManualConfiguration();
124-
settingsSection.OnHttpServerCommandUpdateRequested += () => connectionSection?.UpdateHttpServerCommandDisplay();
165+
settingsSection.OnGitUrlChanged += () =>
166+
clientConfigSection?.UpdateManualConfiguration();
167+
settingsSection.OnHttpServerCommandUpdateRequested += () =>
168+
connectionSection?.UpdateHttpServerCommandDisplay();
125169
}
126170

127171
// Load and initialize Connection section
@@ -133,7 +177,8 @@ public void CreateGUI()
133177
var connectionRoot = connectionTree.Instantiate();
134178
settingsContainer.Add(connectionRoot);
135179
connectionSection = new McpConnectionSection(connectionRoot);
136-
connectionSection.OnManualConfigUpdateRequested += () => clientConfigSection?.UpdateManualConfiguration();
180+
connectionSection.OnManualConfigUpdateRequested += () =>
181+
clientConfigSection?.UpdateManualConfiguration();
137182
}
138183

139184
// Load and initialize Client Configuration section
@@ -162,6 +207,7 @@ public void CreateGUI()
162207
{
163208
McpLog.Warn("Failed to load tools section UXML. Tool configuration will be unavailable.");
164209
}
210+
guiCreated = true;
165211

166212
// Initial updates
167213
RefreshAllData();
@@ -176,7 +222,8 @@ private void OnEnable()
176222
private void OnDisable()
177223
{
178224
EditorApplication.update -= OnEditorUpdate;
179-
OpenWindows.RemoveWhere(window => window == null || window == this);
225+
OpenWindows.Remove(this);
226+
guiCreated = false;
180227
}
181228

182229
private void OnFocus()
@@ -198,6 +245,14 @@ private void OnEditorUpdate()
198245

199246
private void RefreshAllData()
200247
{
248+
// Debounce rapid successive calls (e.g., from OnFocus being called multiple times)
249+
double currentTime = EditorApplication.timeSinceStartup;
250+
if (currentTime - lastRefreshTime < RefreshDebounceSeconds)
251+
{
252+
return;
253+
}
254+
lastRefreshTime = currentTime;
255+
201256
connectionSection?.UpdateConnectionStatus();
202257

203258
if (MCPServiceLocator.Bridge.IsRunning)
@@ -292,11 +347,11 @@ private void ScheduleHealthCheck()
292347
{
293348
EditorApplication.delayCall += async () =>
294349
{
295-
if (this == null)
350+
if (this == null || connectionSection == null)
296351
{
297352
return;
298353
}
299-
await connectionSection?.VerifyBridgeConnectionAsync();
354+
await connectionSection.VerifyBridgeConnectionAsync();
300355
};
301356
}
302357
}

0 commit comments

Comments
 (0)