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

203 lines
6.4 KiB
C#

using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// Standalone window for server status monitoring and lifecycle control.
/// Accessible from Window > Unity CLI Loop > Server.
/// </summary>
public class ServerEditorWindow : EditorWindow
{
private const string UXML_RELATIVE_PATH = "Editor/UI/Server/ServerEditorWindow.uxml";
private const string USS_RELATIVE_PATH = "Editor/UI/Server/ServerEditorWindow.uss";
private ServerStatusSection _serverStatusSection;
private ServerControlsSection _serverControlsSection;
private volatile bool _needsRepaint;
[MenuItem("Window/Unity CLI Loop/Server", priority = 2)]
public static void ShowWindow()
{
ServerEditorWindow window = GetWindow<ServerEditorWindow>("Server");
window.Show();
}
private void OnEnable()
{
EditorApplication.update += OnEditorUpdate;
McpBridgeServer.OnServerStarted += OnServerStateChanged;
McpBridgeServer.OnServerStopping += OnServerStateChanged;
}
private void OnDisable()
{
EditorApplication.update -= OnEditorUpdate;
McpBridgeServer.OnServerStarted -= OnServerStateChanged;
McpBridgeServer.OnServerStopping -= OnServerStateChanged;
}
private void CreateGUI()
{
LoadLayout();
InitializeSections();
RefreshUI();
}
private void OnFocus()
{
RefreshUI();
}
private void LoadLayout()
{
string uxmlPath = $"{McpConstants.PackageAssetPath}/{UXML_RELATIVE_PATH}";
VisualTreeAsset visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(uxmlPath);
Debug.Assert(visualTree != null, $"UXML not found at {uxmlPath}");
visualTree.CloneTree(rootVisualElement);
string ussPath = $"{McpConstants.PackageAssetPath}/{USS_RELATIVE_PATH}";
StyleSheet styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(ussPath);
Debug.Assert(styleSheet != null, $"USS not found at {ussPath}");
rootVisualElement.styleSheets.Add(styleSheet);
}
private void InitializeSections()
{
_serverStatusSection = new ServerStatusSection(rootVisualElement);
_serverControlsSection = new ServerControlsSection(rootVisualElement);
_serverControlsSection.OnToggleServer += ToggleServer;
_serverControlsSection.OnPortChanged += OnPortChanged;
}
private void RefreshUI()
{
if (_serverStatusSection == null)
{
return;
}
SyncPortIfRunning();
ServerStatusData statusData = CreateServerStatusData();
_serverStatusSection.Update(statusData);
ServerControlsData controlsData = CreateServerControlsData();
_serverControlsSection.Update(controlsData);
}
private void SyncPortIfRunning()
{
if (!McpServerController.IsServerRunning)
{
return;
}
int actualPort = McpServerController.ServerPort;
int savedPort = McpEditorSettings.GetCustomPort();
if (savedPort != actualPort)
{
McpEditorSettings.SetCustomPort(actualPort);
}
}
private ServerStatusData CreateServerStatusData()
{
(bool isRunning, int port, bool _) = McpServerController.GetServerStatus();
string status = isRunning ? "Running" : "Stopped";
Color statusColor = isRunning ? Color.green : Color.red;
return new ServerStatusData(isRunning, port, status, statusColor);
}
private ServerControlsData CreateServerControlsData()
{
bool isRunning = McpServerController.IsServerRunning;
int customPort = McpEditorSettings.GetCustomPort();
bool hasPortWarning = false;
string portWarningMessage = null;
if (!isRunning)
{
if (!McpPortValidator.ValidatePort(customPort))
{
hasPortWarning = true;
portWarningMessage = $"Port {customPort} is invalid. Port must be 1024 or higher and not a reserved system port.";
}
else if (NetworkUtility.IsPortInUse(customPort))
{
hasPortWarning = true;
portWarningMessage = $"Port {customPort} is already in use. Please choose a different port or stop the other process using this port.";
}
}
return new ServerControlsData(customPort, isRunning, !isRunning, hasPortWarning, portWarningMessage);
}
private void ToggleServer()
{
if (McpServerController.IsServerRunning)
{
McpServerController.StopServer();
}
else
{
StartServer();
}
RefreshUI();
}
private void StartServer()
{
int port = McpEditorSettings.GetCustomPort();
if (!McpPortValidator.ValidatePort(port))
{
EditorUtility.DisplayDialog("Port Error",
$"Port must be between {McpServerConfig.MIN_PORT_NUMBER} and {McpServerConfig.MAX_PORT_NUMBER}",
"OK");
return;
}
// Config auto-update is best-effort; do not block server startup on unrelated config-file problems
try
{
McpConfigAutoUpdater.UpdateAllConfiguredEditors(port);
}
catch (System.Exception ex)
{
Debug.LogWarning($"[uLoopMCP] Failed to auto-update configurations: {ex.Message}");
}
McpServerController.StartServer(port);
}
private void OnPortChanged(int port)
{
McpEditorSettings.SetCustomPort(port);
RefreshUI();
}
private void OnServerStateChanged()
{
_needsRepaint = true;
}
private void OnEditorUpdate()
{
if (!_needsRepaint)
{
return;
}
_needsRepaint = false;
RefreshUI();
}
}
}