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 ClassName { get; set; }
|
||||||
public string Namespace { get; set; }
|
public string Namespace { get; set; }
|
||||||
public string AssemblyName { get; set; }
|
public string AssemblyName { get; set; }
|
||||||
public string AssetPath { get; set; }
|
|
||||||
public bool AutoRegister { get; set; } = true;
|
public bool AutoRegister { get; set; } = true;
|
||||||
public bool RequiresPolling { get; set; } = false;
|
public bool RequiresPolling { get; set; } = false;
|
||||||
public string PollAction { get; set; } = "status";
|
public string PollAction { get; set; } = "status";
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using MCPForUnity.Editor.Constants;
|
using MCPForUnity.Editor.Constants;
|
||||||
using MCPForUnity.Editor.Helpers;
|
using MCPForUnity.Editor.Helpers;
|
||||||
using MCPForUnity.Editor.Tools;
|
using MCPForUnity.Editor.Tools;
|
||||||
|
|
@ -13,8 +12,7 @@ namespace MCPForUnity.Editor.Services
|
||||||
public class ToolDiscoveryService : IToolDiscoveryService
|
public class ToolDiscoveryService : IToolDiscoveryService
|
||||||
{
|
{
|
||||||
private Dictionary<string, ToolMetadata> _cachedTools;
|
private Dictionary<string, ToolMetadata> _cachedTools;
|
||||||
private readonly Dictionary<Type, string> _scriptPathCache = new();
|
|
||||||
private readonly Dictionary<string, string> _summaryCache = new();
|
|
||||||
|
|
||||||
public List<ToolMetadata> DiscoverAllTools()
|
public List<ToolMetadata> DiscoverAllTools()
|
||||||
{
|
{
|
||||||
|
|
@ -25,33 +23,30 @@ namespace MCPForUnity.Editor.Services
|
||||||
|
|
||||||
_cachedTools = new Dictionary<string, ToolMetadata>();
|
_cachedTools = new Dictionary<string, ToolMetadata>();
|
||||||
|
|
||||||
// Scan all assemblies for [McpForUnityTool] attributes
|
var toolTypes = TypeCache.GetTypesWithAttribute<McpForUnityToolAttribute>();
|
||||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
foreach (var type in toolTypes)
|
||||||
|
|
||||||
foreach (var assembly in assemblies)
|
|
||||||
{
|
{
|
||||||
|
McpForUnityToolAttribute toolAttr;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var types = assembly.GetTypes();
|
toolAttr = type.GetCustomAttribute<McpForUnityToolAttribute>();
|
||||||
|
|
||||||
foreach (var type in types)
|
|
||||||
{
|
|
||||||
var toolAttr = type.GetCustomAttribute<McpForUnityToolAttribute>();
|
|
||||||
if (toolAttr == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var metadata = ExtractToolMetadata(type, toolAttr);
|
|
||||||
if (metadata != null)
|
|
||||||
{
|
|
||||||
_cachedTools[metadata.Name] = metadata;
|
|
||||||
EnsurePreferenceInitialized(metadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Skip assemblies that can't be reflected
|
McpLog.Warn($"Failed to read [McpForUnityTool] for {type.FullName}: {ex.Message}");
|
||||||
McpLog.Info($"Skipping assembly {assembly.FullName}: {ex.Message}");
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toolAttr == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata = ExtractToolMetadata(type, toolAttr);
|
||||||
|
if (metadata != null)
|
||||||
|
{
|
||||||
|
_cachedTools[metadata.Name] = metadata;
|
||||||
|
EnsurePreferenceInitialized(metadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,23 +126,15 @@ namespace MCPForUnity.Editor.Services
|
||||||
ClassName = type.Name,
|
ClassName = type.Name,
|
||||||
Namespace = type.Namespace ?? "",
|
Namespace = type.Namespace ?? "",
|
||||||
AssemblyName = type.Assembly.GetName().Name,
|
AssemblyName = type.Assembly.GetName().Name,
|
||||||
AssetPath = ResolveScriptAssetPath(type),
|
|
||||||
AutoRegister = toolAttr.AutoRegister,
|
AutoRegister = toolAttr.AutoRegister,
|
||||||
RequiresPolling = toolAttr.RequiresPolling,
|
RequiresPolling = toolAttr.RequiresPolling,
|
||||||
PollAction = string.IsNullOrEmpty(toolAttr.PollAction) ? "status" : toolAttr.PollAction
|
PollAction = string.IsNullOrEmpty(toolAttr.PollAction) ? "status" : toolAttr.PollAction
|
||||||
};
|
};
|
||||||
|
|
||||||
metadata.IsBuiltIn = DetermineIsBuiltIn(type, metadata);
|
metadata.IsBuiltIn = DetermineIsBuiltIn(type, metadata);
|
||||||
if (metadata.IsBuiltIn)
|
|
||||||
{
|
|
||||||
string summaryDescription = ExtractSummaryDescription(type, metadata);
|
|
||||||
if (!string.IsNullOrWhiteSpace(summaryDescription))
|
|
||||||
{
|
|
||||||
metadata.Description = summaryDescription;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return metadata;
|
return metadata;
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -265,56 +252,6 @@ namespace MCPForUnity.Editor.Services
|
||||||
return EditorPrefKeys.ToolEnabledPrefix + toolName;
|
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)
|
private bool DetermineIsBuiltIn(Type type, ToolMetadata metadata)
|
||||||
{
|
{
|
||||||
if (metadata == null)
|
if (metadata == null)
|
||||||
|
|
@ -322,25 +259,9 @@ namespace MCPForUnity.Editor.Services
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(metadata.AssetPath))
|
if (type != null && !string.IsNullOrEmpty(type.Namespace) && type.Namespace.StartsWith("MCPForUnity.Editor.Tools", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
string normalizedPath = metadata.AssetPath.Replace("\\", "/");
|
return true;
|
||||||
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))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(metadata.AssemblyName) && metadata.AssemblyName.Equals("MCPForUnity.Editor", StringComparison.Ordinal))
|
if (!string.IsNullOrEmpty(metadata.AssemblyName) && metadata.AssemblyName.Equals("MCPForUnity.Editor", StringComparison.Ordinal))
|
||||||
|
|
@ -350,57 +271,5 @@ namespace MCPForUnity.Editor.Services
|
||||||
|
|
||||||
return false;
|
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.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MCPForUnity.Editor.Constants;
|
||||||
using MCPForUnity.Editor.Helpers;
|
using MCPForUnity.Editor.Helpers;
|
||||||
using MCPForUnity.Editor.Services;
|
using MCPForUnity.Editor.Services;
|
||||||
using MCPForUnity.Editor.Windows.Components.ClientConfig;
|
using MCPForUnity.Editor.Windows.Components.ClientConfig;
|
||||||
using MCPForUnity.Editor.Windows.Components.Connection;
|
using MCPForUnity.Editor.Windows.Components.Connection;
|
||||||
using MCPForUnity.Editor.Windows.Components.Settings;
|
using MCPForUnity.Editor.Windows.Components.Settings;
|
||||||
|
using MCPForUnity.Editor.Windows.Components.Tools;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.UIElements;
|
using UnityEditor.UIElements;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UIElements;
|
using UnityEngine.UIElements;
|
||||||
using MCPForUnity.Editor.Constants;
|
|
||||||
using MCPForUnity.Editor.Windows.Components.Tools;
|
|
||||||
|
|
||||||
namespace MCPForUnity.Editor.Windows
|
namespace MCPForUnity.Editor.Windows
|
||||||
{
|
{
|
||||||
|
|
@ -31,6 +31,7 @@ namespace MCPForUnity.Editor.Windows
|
||||||
|
|
||||||
private static readonly HashSet<MCPForUnityEditorWindow> OpenWindows = new();
|
private static readonly HashSet<MCPForUnityEditorWindow> OpenWindows = new();
|
||||||
private bool guiCreated = false;
|
private bool guiCreated = false;
|
||||||
|
private bool toolsLoaded = false;
|
||||||
private double lastRefreshTime = 0;
|
private double lastRefreshTime = 0;
|
||||||
private const double RefreshDebounceSeconds = 0.5;
|
private const double RefreshDebounceSeconds = 0.5;
|
||||||
|
|
||||||
|
|
@ -196,18 +197,39 @@ namespace MCPForUnity.Editor.Windows
|
||||||
var toolsRoot = toolsTree.Instantiate();
|
var toolsRoot = toolsTree.Instantiate();
|
||||||
toolsContainer.Add(toolsRoot);
|
toolsContainer.Add(toolsRoot);
|
||||||
toolsSection = new McpToolsSection(toolsRoot);
|
toolsSection = new McpToolsSection(toolsRoot);
|
||||||
toolsSection.Refresh();
|
|
||||||
|
if (toolsTabToggle != null && toolsTabToggle.value)
|
||||||
|
{
|
||||||
|
EnsureToolsLoaded();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
McpLog.Warn("Failed to load tools section UXML. Tool configuration will be unavailable.");
|
McpLog.Warn("Failed to load tools section UXML. Tool configuration will be unavailable.");
|
||||||
}
|
}
|
||||||
|
|
||||||
guiCreated = true;
|
guiCreated = true;
|
||||||
|
|
||||||
// Initial updates
|
// Initial updates
|
||||||
RefreshAllData();
|
RefreshAllData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void EnsureToolsLoaded()
|
||||||
|
{
|
||||||
|
if (toolsLoaded)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toolsSection == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toolsLoaded = true;
|
||||||
|
toolsSection.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
EditorApplication.update += OnEditorUpdate;
|
EditorApplication.update += OnEditorUpdate;
|
||||||
|
|
@ -219,6 +241,7 @@ namespace MCPForUnity.Editor.Windows
|
||||||
EditorApplication.update -= OnEditorUpdate;
|
EditorApplication.update -= OnEditorUpdate;
|
||||||
OpenWindows.Remove(this);
|
OpenWindows.Remove(this);
|
||||||
guiCreated = false;
|
guiCreated = false;
|
||||||
|
toolsLoaded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnFocus()
|
private void OnFocus()
|
||||||
|
|
@ -327,6 +350,11 @@ namespace MCPForUnity.Editor.Windows
|
||||||
settingsTabToggle?.SetValueWithoutNotify(showSettings);
|
settingsTabToggle?.SetValueWithoutNotify(showSettings);
|
||||||
toolsTabToggle?.SetValueWithoutNotify(!showSettings);
|
toolsTabToggle?.SetValueWithoutNotify(!showSettings);
|
||||||
|
|
||||||
|
if (!showSettings)
|
||||||
|
{
|
||||||
|
EnsureToolsLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
EditorPrefs.SetString(EditorPrefKeys.EditorWindowActivePanel, panel.ToString());
|
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