using System; using System.Collections.Generic; using UnityEditor; using UnityEditor.PackageManager; using UnityEditor.PackageManager.Requests; using UnityEngine; namespace FlowScope.Editor { public sealed class FlowScopeDiagnosticsWindow : EditorWindow { private const string PackageRuntimeAsmdefPath = "Packages/com.flowscope.gamecore/Runtime/FlowScope.Runtime.asmdef"; private const string PackageAddressablesAsmdefPath = "Packages/com.flowscope.gamecore/Addressables/FlowScope.Addressables.asmdef"; private const string AssetsRuntimeAsmdefPath = "Assets/FlowScope/Runtime/FlowScope.Runtime.asmdef"; private const string AssetsAddressablesAsmdefPath = "Assets/FlowScope/Addressables/FlowScope.Addressables.asmdef"; private const string AssetsMainMenuScenePath = "Assets/FlowScope/Samples/MainMenuP0/Scenes/MainMenuP0.unity"; private const string AssetsMainMenuPanelPrefabPath = "Assets/FlowScope/Samples/MainMenuP0/Prefabs/MainMenuPanel.prefab"; private const string AddressablesPackageName = "com.unity.addressables"; private static readonly IReadOnlyList AssetChecks = new[] { new AssetCheck("Runtime asmdef", PackageRuntimeAsmdefPath, AssetsRuntimeAsmdefPath), new AssetCheck("Addressables asmdef", PackageAddressablesAsmdefPath, AssetsAddressablesAsmdefPath), new AssetCheck("MainMenuP0 scene", AssetsMainMenuScenePath), new AssetCheck("MainMenuPanel prefab", AssetsMainMenuPanelPrefabPath), }; private readonly List _assetResults = new List(); private ListRequest _packageListRequest; private DiagnosticResult _addressablesPackageResult = DiagnosticResult.Pending( "Addressables package", "Package status has not been refreshed."); private Vector2 _scrollPosition; [MenuItem("Tools/FlowScope/Diagnostics")] public static void Open() { FlowScopeDiagnosticsWindow window = GetWindow("FlowScope Diagnostics"); window.minSize = new Vector2(520f, 320f); window.RefreshAll(); window.Show(); } private void OnEnable() { RefreshAssetChecks(); RefreshPackageStatus(); } private void OnDisable() { EditorApplication.update -= PollPackageListRequest; } private void OnGUI() { using (new EditorGUILayout.VerticalScope()) { EditorGUILayout.LabelField("FlowScope Diagnostics", EditorStyles.boldLabel); EditorGUILayout.HelpBox("Read-only diagnostics. This window checks project state and does not create, move, delete, or repair user assets.", MessageType.Info); using (new EditorGUILayout.HorizontalScope()) { if (GUILayout.Button("Refresh", GUILayout.Width(96f))) { RefreshAll(); } GUILayout.Label( _packageListRequest != null && !_packageListRequest.IsCompleted ? "Refreshing package status..." : "Package status is complete or has not been requested.", EditorStyles.miniLabel); } EditorGUILayout.Space(); _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition); DrawResult(_addressablesPackageResult); foreach (DiagnosticResult result in _assetResults) { DrawResult(result); } EditorGUILayout.EndScrollView(); } } private void RefreshAll() { RefreshAssetChecks(); RefreshPackageStatus(); Repaint(); } private void RefreshAssetChecks() { _assetResults.Clear(); foreach (AssetCheck check in AssetChecks) { string foundPath = FindExistingAssetPath(check.Paths); _assetResults.Add(foundPath != null ? DiagnosticResult.Pass(check.Label, foundPath) : DiagnosticResult.Fail(check.Label, string.Join(" or ", check.Paths))); } } private static string FindExistingAssetPath(IReadOnlyList paths) { foreach (string path in paths) { if (AssetDatabase.LoadAssetAtPath(path) != null) { return path; } } return null; } private void RefreshPackageStatus() { EditorApplication.update -= PollPackageListRequest; _addressablesPackageResult = DiagnosticResult.Pending( "Addressables package", "Querying com.unity.addressables through Unity Package Manager."); _packageListRequest = Client.List(true); EditorApplication.update += PollPackageListRequest; } private void PollPackageListRequest() { if (_packageListRequest == null || !_packageListRequest.IsCompleted) { return; } EditorApplication.update -= PollPackageListRequest; if (_packageListRequest.Status == StatusCode.Success) { bool found = false; foreach (UnityEditor.PackageManager.PackageInfo packageInfo in _packageListRequest.Result) { if (string.Equals(packageInfo.name, AddressablesPackageName, StringComparison.Ordinal)) { found = true; _addressablesPackageResult = DiagnosticResult.Pass( "Addressables package", packageInfo.name + " " + packageInfo.version); break; } } if (!found) { _addressablesPackageResult = DiagnosticResult.Fail( "Addressables package", "com.unity.addressables was not found in the current Package Manager list."); } } else { string errorMessage = _packageListRequest.Error != null ? _packageListRequest.Error.message : "Package Manager returned an unknown error."; _addressablesPackageResult = DiagnosticResult.Warning("Addressables package", errorMessage); } _packageListRequest = null; Repaint(); } private static void DrawResult(DiagnosticResult result) { MessageType messageType = ToMessageType(result.Status); using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { using (new EditorGUILayout.HorizontalScope()) { GUILayout.Label(ToStatusText(result.Status), GUILayout.Width(72f)); EditorGUILayout.LabelField(result.Label, EditorStyles.boldLabel); } EditorGUILayout.HelpBox(result.Detail, messageType); } } private static MessageType ToMessageType(DiagnosticStatus status) { switch (status) { case DiagnosticStatus.Pass: return MessageType.Info; case DiagnosticStatus.Fail: return MessageType.Error; case DiagnosticStatus.Warning: return MessageType.Warning; default: return MessageType.None; } } private static string ToStatusText(DiagnosticStatus status) { switch (status) { case DiagnosticStatus.Pass: return "OK"; case DiagnosticStatus.Fail: return "Missing"; case DiagnosticStatus.Warning: return "Warning"; default: return "Pending"; } } private readonly struct AssetCheck { public AssetCheck(string label, params string[] paths) { Label = label; Paths = paths; } public string Label { get; } public IReadOnlyList Paths { get; } } private readonly struct DiagnosticResult { private DiagnosticResult(DiagnosticStatus status, string label, string detail) { Status = status; Label = label; Detail = detail; } public DiagnosticStatus Status { get; } public string Label { get; } public string Detail { get; } public static DiagnosticResult Pass(string label, string detail) { return new DiagnosticResult(DiagnosticStatus.Pass, label, detail); } public static DiagnosticResult Fail(string label, string detail) { return new DiagnosticResult(DiagnosticStatus.Fail, label, detail); } public static DiagnosticResult Warning(string label, string detail) { return new DiagnosticResult(DiagnosticStatus.Warning, label, detail); } public static DiagnosticResult Pending(string label, string detail) { return new DiagnosticResult(DiagnosticStatus.Pending, label, detail); } } private enum DiagnosticStatus { Pending, Pass, Warning, Fail, } } }