Files
ft/Client/Packages/io.github.hatayama.uloopmcp/Editor/UI/McpEditorWindowEventHandler.cs

241 lines
8.0 KiB
C#

using Unity.Profiling;
using UnityEditor;
using UnityEngine;
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// Event handler for McpEditorWindow - Manages Unity and server events
/// Helper class for Presenter layer in MVP architecture
/// Related classes:
/// - McpEditorWindow: Main presenter that owns this handler
/// - McpEditorModel: Model layer for state management
/// - McpBridgeServer: Server that provides events
/// - McpServerController: Server lifecycle management
/// </summary>
internal class McpEditorWindowEventHandler
{
private static readonly ProfilerMarker s_onEditorUpdateMarker =
new ProfilerMarker("McpEditorWindow.OnEditorUpdate");
private static readonly ProfilerMarker s_refreshUiMarker =
new ProfilerMarker("McpEditorWindow.RefreshUI");
private readonly McpEditorModel _model;
private readonly McpEditorWindow _window;
public McpEditorWindowEventHandler(McpEditorModel model, McpEditorWindow window)
{
_model = model;
_window = window;
}
/// <summary>
/// Initialize all event subscriptions
/// </summary>
public void Initialize()
{
SubscribeToUnityEvents();
SubscribeToServerEvents();
}
/// <summary>
/// Cleanup all event subscriptions
/// </summary>
public void Cleanup()
{
UnsubscribeFromUnityEvents();
UnsubscribeFromServerEvents();
}
/// <summary>
/// Subscribe to Unity Editor events
/// </summary>
private void SubscribeToUnityEvents()
{
EditorApplication.update += OnEditorUpdate;
}
/// <summary>
/// Unsubscribe from Unity Editor events
/// </summary>
private void UnsubscribeFromUnityEvents()
{
EditorApplication.update -= OnEditorUpdate;
}
private void SubscribeToServerEvents()
{
UnsubscribeFromServerEvents();
McpBridgeServer.OnServerStarted += OnServerStateChanged;
McpBridgeServer.OnServerStopping += OnServerStateChanged;
ConnectedToolsMonitoringService.OnConnectedToolsChanged += OnConnectedToolsChanged;
CustomToolManager.OnToolsChanged += OnToolsChanged;
McpBridgeServer currentServer = McpServerController.CurrentServer;
if (currentServer != null)
{
currentServer.OnClientConnected += OnClientConnected;
currentServer.OnClientDisconnected += OnClientDisconnected;
}
}
private void UnsubscribeFromServerEvents()
{
McpBridgeServer.OnServerStarted -= OnServerStateChanged;
McpBridgeServer.OnServerStopping -= OnServerStateChanged;
ConnectedToolsMonitoringService.OnConnectedToolsChanged -= OnConnectedToolsChanged;
CustomToolManager.OnToolsChanged -= OnToolsChanged;
McpBridgeServer currentServer = McpServerController.CurrentServer;
if (currentServer != null)
{
currentServer.OnClientConnected -= OnClientConnected;
currentServer.OnClientDisconnected -= OnClientDisconnected;
}
}
private void OnServerStateChanged()
{
_window.InvalidateToolSettingsCatalog();
_model.RequestRepaint();
}
private void OnConnectedToolsChanged()
{
_model.RequestRepaint();
}
private void OnToolsChanged()
{
_window.InvalidateToolSettingsCatalog();
_model.RequestRepaint();
}
/// <summary>
/// Handle client connection event - force UI repaint for immediate update
/// </summary>
private void OnClientConnected(string clientEndpoint)
{
// Enhanced logging for debugging client connection
// Count check for debugging purposes only
// Clear reconnecting flags when client connects
McpServerController.ClearReconnectingFlag();
// Mark that repaint is needed since events are called from background thread
_model.RequestRepaint();
// Exit post-compile mode when client connects
if (_model.Runtime.IsPostCompileMode)
{
_model.DisablePostCompileMode();
}
}
/// <summary>
/// Handle client disconnection event - force UI repaint for immediate update
/// </summary>
private void OnClientDisconnected(string clientEndpoint)
{
// Enhanced logging for debugging client disconnection issues
// Count check for debugging purposes only
// Mark that repaint is needed since events are called from background thread
_model.RequestRepaint();
}
private void OnEditorUpdate()
{
using (s_onEditorUpdateMarker.Auto())
{
if (!McpEditorWindowRefreshPolicy.ShouldRefreshOnEditorUpdate(_model.Runtime))
{
return;
}
_model.ClearRepaintRequest();
using (s_refreshUiMarker.Auto())
{
_window.RefreshAllSections();
}
}
}
/// <summary>
/// Re-subscribe to server events (called after server start)
/// </summary>
public void RefreshServerEventSubscriptions()
{
SubscribeToServerEvents();
}
}
// Post-compile recovery can stay active while UI data is unchanged, so explicit repaint
// requests gate expensive full-section refreshes.
internal static class McpEditorWindowRefreshPolicy
{
public static bool ShouldRefreshOnEditorUpdate(RuntimeState runtimeState)
{
Debug.Assert(runtimeState != null, "runtimeState must not be null");
return runtimeState.NeedsRepaint;
}
public static bool ShouldRunExpensiveChecks(McpEditorWindowRefreshMode refreshMode)
{
return refreshMode == McpEditorWindowRefreshMode.Full;
}
public static bool ShouldRefreshSkillInstallState(
McpEditorWindowRefreshMode refreshMode,
bool refreshRequested)
{
return refreshRequested && ShouldRunExpensiveChecks(refreshMode);
}
public static bool ShouldKeepToolSettingsCatalogDirty(ToolSettingsSectionData toolSettingsData)
{
Debug.Assert(toolSettingsData != null, "toolSettingsData must not be null");
return toolSettingsData.ShowToolSettings && !toolSettingsData.IsRegistryAvailable;
}
public static bool ShouldStartToolSettingsRegistryWarmup(
bool isAlreadyScheduled,
int attemptCount,
int maxAttempts)
{
Debug.Assert(attemptCount >= 0, "attemptCount must not be negative");
Debug.Assert(maxAttempts > 0, "maxAttempts must be positive");
return !isAlreadyScheduled && attemptCount < maxAttempts;
}
public static double CalculateToolSettingsRegistryWarmupDelaySeconds(
double initialDelaySeconds,
double maxDelaySeconds,
int attemptCount)
{
Debug.Assert(initialDelaySeconds > 0.0, "initialDelaySeconds must be positive");
Debug.Assert(maxDelaySeconds >= initialDelaySeconds, "maxDelaySeconds must not be smaller than initialDelaySeconds");
Debug.Assert(attemptCount >= 0, "attemptCount must not be negative");
double delaySeconds = initialDelaySeconds;
for (int i = 0; i < attemptCount; i++)
{
delaySeconds *= 2.0;
}
return System.Math.Min(delaySeconds, maxDelaySeconds);
}
}
internal enum McpEditorWindowRefreshMode
{
InitialPaint,
Full
}
}