Optimise so startup is fast again (#494)
* Optimize tool loading so startup is fast again We lazy load tools, remove the expensive AssetPath property, and reflect for only `McpForUnityToolAttribute`, so it's much faster. A 6 second startup is now back to 400ms. Can still be optimised but this is good * Remove .meta file from tests The tests automatically cleans this up, so it likely got pushed by accidentmain
parent
6f080f5390
commit
1a7f4bb4a5
|
|
@ -14,7 +14,6 @@ namespace MCPForUnity.Editor.Services
|
|||
public string ClassName { get; set; }
|
||||
public string Namespace { get; set; }
|
||||
public string AssemblyName { get; set; }
|
||||
public string AssetPath { get; set; }
|
||||
public bool AutoRegister { get; set; } = true;
|
||||
public bool RequiresPolling { get; set; } = false;
|
||||
public string PollAction { get; set; } = "status";
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using MCPForUnity.Editor.Constants;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using MCPForUnity.Editor.Tools;
|
||||
|
|
@ -13,8 +12,7 @@ namespace MCPForUnity.Editor.Services
|
|||
public class ToolDiscoveryService : IToolDiscoveryService
|
||||
{
|
||||
private Dictionary<string, ToolMetadata> _cachedTools;
|
||||
private readonly Dictionary<Type, string> _scriptPathCache = new();
|
||||
private readonly Dictionary<string, string> _summaryCache = new();
|
||||
|
||||
|
||||
public List<ToolMetadata> DiscoverAllTools()
|
||||
{
|
||||
|
|
@ -25,20 +23,24 @@ namespace MCPForUnity.Editor.Services
|
|||
|
||||
_cachedTools = new Dictionary<string, ToolMetadata>();
|
||||
|
||||
// Scan all assemblies for [McpForUnityTool] attributes
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
var toolTypes = TypeCache.GetTypesWithAttribute<McpForUnityToolAttribute>();
|
||||
foreach (var type in toolTypes)
|
||||
{
|
||||
McpForUnityToolAttribute toolAttr;
|
||||
try
|
||||
{
|
||||
var types = assembly.GetTypes();
|
||||
|
||||
foreach (var type in types)
|
||||
toolAttr = type.GetCustomAttribute<McpForUnityToolAttribute>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var toolAttr = type.GetCustomAttribute<McpForUnityToolAttribute>();
|
||||
if (toolAttr == null)
|
||||
McpLog.Warn($"Failed to read [McpForUnityTool] for {type.FullName}: {ex.Message}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (toolAttr == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var metadata = ExtractToolMetadata(type, toolAttr);
|
||||
if (metadata != null)
|
||||
|
|
@ -47,13 +49,6 @@ namespace MCPForUnity.Editor.Services
|
|||
EnsurePreferenceInitialized(metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Skip assemblies that can't be reflected
|
||||
McpLog.Info($"Skipping assembly {assembly.FullName}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
McpLog.Info($"Discovered {_cachedTools.Count} MCP tools via reflection");
|
||||
return _cachedTools.Values.ToList();
|
||||
|
|
@ -131,21 +126,13 @@ namespace MCPForUnity.Editor.Services
|
|||
ClassName = type.Name,
|
||||
Namespace = type.Namespace ?? "",
|
||||
AssemblyName = type.Assembly.GetName().Name,
|
||||
AssetPath = ResolveScriptAssetPath(type),
|
||||
AutoRegister = toolAttr.AutoRegister,
|
||||
RequiresPolling = toolAttr.RequiresPolling,
|
||||
PollAction = string.IsNullOrEmpty(toolAttr.PollAction) ? "status" : toolAttr.PollAction
|
||||
};
|
||||
|
||||
metadata.IsBuiltIn = DetermineIsBuiltIn(type, metadata);
|
||||
if (metadata.IsBuiltIn)
|
||||
{
|
||||
string summaryDescription = ExtractSummaryDescription(type, metadata);
|
||||
if (!string.IsNullOrWhiteSpace(summaryDescription))
|
||||
{
|
||||
metadata.Description = summaryDescription;
|
||||
}
|
||||
}
|
||||
|
||||
return metadata;
|
||||
|
||||
}
|
||||
|
|
@ -265,56 +252,6 @@ namespace MCPForUnity.Editor.Services
|
|||
return EditorPrefKeys.ToolEnabledPrefix + toolName;
|
||||
}
|
||||
|
||||
private string ResolveScriptAssetPath(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_scriptPathCache.TryGetValue(type, out var cachedPath))
|
||||
{
|
||||
return cachedPath;
|
||||
}
|
||||
|
||||
string resolvedPath = null;
|
||||
|
||||
try
|
||||
{
|
||||
string filter = string.IsNullOrEmpty(type.Name) ? "t:MonoScript" : $"{type.Name} t:MonoScript";
|
||||
var guids = AssetDatabase.FindAssets(filter);
|
||||
|
||||
foreach (var guid in guids)
|
||||
{
|
||||
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
|
||||
if (string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var script = AssetDatabase.LoadAssetAtPath<MonoScript>(assetPath);
|
||||
if (script == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var scriptClass = script.GetClass();
|
||||
if (scriptClass == type)
|
||||
{
|
||||
resolvedPath = assetPath.Replace('\\', '/');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
McpLog.Warn($"Failed to resolve asset path for {type.FullName}: {ex.Message}");
|
||||
}
|
||||
|
||||
_scriptPathCache[type] = resolvedPath;
|
||||
return resolvedPath;
|
||||
}
|
||||
|
||||
private bool DetermineIsBuiltIn(Type type, ToolMetadata metadata)
|
||||
{
|
||||
if (metadata == null)
|
||||
|
|
@ -322,26 +259,10 @@ namespace MCPForUnity.Editor.Services
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(metadata.AssetPath))
|
||||
{
|
||||
string normalizedPath = metadata.AssetPath.Replace("\\", "/");
|
||||
string packageRoot = AssetPathUtility.GetMcpPackageRootPath();
|
||||
|
||||
if (!string.IsNullOrEmpty(packageRoot))
|
||||
{
|
||||
string normalizedRoot = packageRoot.Replace("\\", "/");
|
||||
if (!normalizedRoot.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
normalizedRoot += "/";
|
||||
}
|
||||
|
||||
string builtInRoot = normalizedRoot + "Editor/Tools/";
|
||||
if (normalizedPath.StartsWith(builtInRoot, StringComparison.OrdinalIgnoreCase))
|
||||
if (type != null && !string.IsNullOrEmpty(type.Namespace) && type.Namespace.StartsWith("MCPForUnity.Editor.Tools", StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(metadata.AssemblyName) && metadata.AssemblyName.Equals("MCPForUnity.Editor", StringComparison.Ordinal))
|
||||
{
|
||||
|
|
@ -350,57 +271,5 @@ namespace MCPForUnity.Editor.Services
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string ExtractSummaryDescription(Type type, ToolMetadata metadata)
|
||||
{
|
||||
if (metadata == null || string.IsNullOrEmpty(metadata.AssetPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_summaryCache.TryGetValue(metadata.AssetPath, out var cachedSummary))
|
||||
{
|
||||
return cachedSummary;
|
||||
}
|
||||
|
||||
string summary = null;
|
||||
|
||||
try
|
||||
{
|
||||
var monoScript = AssetDatabase.LoadAssetAtPath<MonoScript>(metadata.AssetPath);
|
||||
string scriptText = monoScript?.text;
|
||||
if (string.IsNullOrEmpty(scriptText))
|
||||
{
|
||||
_summaryCache[metadata.AssetPath] = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
string classPattern = $@"///\s*<summary>\s*(?<content>[\s\S]*?)\s*</summary>\s*(?:\[[^\]]*\]\s*)*(?:public\s+)?(?:static\s+)?class\s+{Regex.Escape(type.Name)}";
|
||||
var match = Regex.Match(scriptText, classPattern);
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
match = Regex.Match(scriptText, @"///\s*<summary>\s*(?<content>[\s\S]*?)\s*</summary>");
|
||||
}
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
_summaryCache[metadata.AssetPath] = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
summary = match.Groups["content"].Value;
|
||||
summary = Regex.Replace(summary, @"^\s*///\s?", string.Empty, RegexOptions.Multiline);
|
||||
summary = Regex.Replace(summary, @"<[^>]+>", string.Empty);
|
||||
summary = Regex.Replace(summary, @"\s+", " ").Trim();
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
McpLog.Warn($"Failed to extract summary description for {type?.FullName}: {ex.Message}");
|
||||
}
|
||||
|
||||
_summaryCache[metadata.AssetPath] = summary;
|
||||
return summary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,17 +2,17 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MCPForUnity.Editor.Constants;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using MCPForUnity.Editor.Services;
|
||||
using MCPForUnity.Editor.Windows.Components.ClientConfig;
|
||||
using MCPForUnity.Editor.Windows.Components.Connection;
|
||||
using MCPForUnity.Editor.Windows.Components.Settings;
|
||||
using MCPForUnity.Editor.Windows.Components.Tools;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using MCPForUnity.Editor.Constants;
|
||||
using MCPForUnity.Editor.Windows.Components.Tools;
|
||||
|
||||
namespace MCPForUnity.Editor.Windows
|
||||
{
|
||||
|
|
@ -31,6 +31,7 @@ namespace MCPForUnity.Editor.Windows
|
|||
|
||||
private static readonly HashSet<MCPForUnityEditorWindow> OpenWindows = new();
|
||||
private bool guiCreated = false;
|
||||
private bool toolsLoaded = false;
|
||||
private double lastRefreshTime = 0;
|
||||
private const double RefreshDebounceSeconds = 0.5;
|
||||
|
||||
|
|
@ -196,18 +197,39 @@ namespace MCPForUnity.Editor.Windows
|
|||
var toolsRoot = toolsTree.Instantiate();
|
||||
toolsContainer.Add(toolsRoot);
|
||||
toolsSection = new McpToolsSection(toolsRoot);
|
||||
toolsSection.Refresh();
|
||||
|
||||
if (toolsTabToggle != null && toolsTabToggle.value)
|
||||
{
|
||||
EnsureToolsLoaded();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
McpLog.Warn("Failed to load tools section UXML. Tool configuration will be unavailable.");
|
||||
}
|
||||
|
||||
guiCreated = true;
|
||||
|
||||
// Initial updates
|
||||
RefreshAllData();
|
||||
}
|
||||
|
||||
private void EnsureToolsLoaded()
|
||||
{
|
||||
if (toolsLoaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (toolsSection == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
toolsLoaded = true;
|
||||
toolsSection.Refresh();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
EditorApplication.update += OnEditorUpdate;
|
||||
|
|
@ -219,6 +241,7 @@ namespace MCPForUnity.Editor.Windows
|
|||
EditorApplication.update -= OnEditorUpdate;
|
||||
OpenWindows.Remove(this);
|
||||
guiCreated = false;
|
||||
toolsLoaded = false;
|
||||
}
|
||||
|
||||
private void OnFocus()
|
||||
|
|
@ -327,6 +350,11 @@ namespace MCPForUnity.Editor.Windows
|
|||
settingsTabToggle?.SetValueWithoutNotify(showSettings);
|
||||
toolsTabToggle?.SetValueWithoutNotify(!showSettings);
|
||||
|
||||
if (!showSettings)
|
||||
{
|
||||
EnsureToolsLoaded();
|
||||
}
|
||||
|
||||
EditorPrefs.SetString(EditorPrefKeys.EditorWindowActivePanel, panel.ToString());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 20332651bb6f64cadb92cf3c6d68bed5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Reference in New Issue