using System; using System.Collections.Generic; using System.IO; using System.Reflection; using UnityEditor; using UnityEngine; using UnityEngine.Pool; using UnityEngine.SceneManagement; namespace Module.ProjectValidator.Editor { public static class ProjectValidatorUtility { [MenuItem("Window/Analysis/Project Validator")] public static void OpenWindow() { var window = InternalOpenWindow(); window.Show(); window.Rebuild(); } public static void ClearWindow() { var windows = Resources.FindObjectsOfTypeAll(); for (var i = 0; i < windows.Length; i++) { windows[i].Clear(); } RefreshUnityWindows(); } private static EditorProjectValidatorWindow InternalOpenWindow() { var window = EditorWindow.GetWindow(); window.titleContent = new GUIContent("Project Validator"); return window; } internal static string GetAttributeShortName(Attribute attribute) { var str = attribute.GetType().Name; var index = str.IndexOf("Attribute", StringComparison.Ordinal); if (index != -1) str = str[..index]; str = ObjectNames.NicifyVariableName(str); return str; } internal static void AppendToScenePath(GameObject gameObject, ref string scenePath) { scenePath = string.IsNullOrEmpty(scenePath) ? gameObject.name : $"{scenePath}/{gameObject.name}"; } internal static string ApplyRichTextToScenePath(string scenePath) { return scenePath.Replace("/", "/"); } public static void AppendToFieldPath(FieldInfo fieldInfo, ref string fieldPath) { fieldPath = string.IsNullOrEmpty(fieldPath) ? fieldInfo.Name : $"{fieldPath}.{fieldInfo.Name}"; } public static void AppendToFieldPath(int index, ref string fieldPath) { fieldPath += $"[{index}]"; } public static string ApplyRichTextToFieldPath(string fieldPath) { var str = fieldPath.Replace(".", "."); str = str.Replace("[", "["); str = str.Replace("]", "]"); return str; } internal static void PingObject(Report.Entry entry) { if (entry.AssetGuid.Empty()) return; var assetPath = AssetDatabase.GUIDToAssetPath(entry.AssetGuid); var asset = AssetDatabase.LoadMainAssetAtPath(assetPath); if (asset == null) return; if (asset is SceneAsset) { var scene = SceneManager.GetSceneByPath(assetPath); if (scene.isLoaded && TryFindSceneObjectByPath(scene, entry.ScenePath, out var gameObject)) EditorGUIUtility.PingObject(gameObject); else EditorGUIUtility.PingObject(asset); } else { EditorGUIUtility.PingObject(asset); } } private static bool TryFindSceneObjectByPath(Scene scene, string scenePath, out GameObject gameObject) { using var _ = ListPool.Get(out var rootObjects); scene.GetRootGameObjects(rootObjects); var index = scenePath.IndexOf('/'); var rootName = index != -1 ? scenePath[..index] : scenePath; var childPath = index != -1 ? scenePath[(index + 1)..] : string.Empty; for (var i = 0; i < rootObjects.Count; i++) { var rootObject = rootObjects[i]; if (rootObject.name != rootName) continue; if (string.IsNullOrEmpty(childPath)) { gameObject = rootObject; return true; } var child = rootObject.transform.Find(childPath); if (child == null) continue; gameObject = child.gameObject; return true; } gameObject = null; return false; } internal static void RebuildAssetMapping(Dictionary dictMapping) { using var _ = DictionaryPool.Get(out var newMappings); foreach (var pair in dictMapping) { var severity = pair.Value.Severity; var assetPath = AssetDatabase.GUIDToAssetPath(pair.Key); var folderPath = Path.GetDirectoryName(assetPath); while (!string.IsNullOrEmpty(folderPath)) { var strGuid = AssetDatabase.AssetPathToGUID(folderPath); if (GUID.TryParse(strGuid, out var assetGuid)) { if (dictMapping.TryGetValue(assetGuid, out var parentMapping)) { if (severity < parentMapping.Severity) severity = parentMapping.Severity; } else if (newMappings.TryGetValue(assetGuid, out var currentMapping)) { if (currentMapping.Severity < severity) newMappings[assetGuid] = new Report.MappingEntry(severity, true); } else { newMappings.Add(assetGuid, new Report.MappingEntry(severity, true)); } } folderPath = Path.GetDirectoryName(folderPath); } } foreach (var pair in newMappings) { dictMapping.Add(pair.Key, pair.Value); } } internal static void RebuildSceneInstanceMapping(Report report, Dictionary dictMapping) { dictMapping.Clear(); using var _ = ListPool.Get(out var rootObjects); for (var i = 0; i < SceneManager.sceneCount; i++) { var scene = SceneManager.GetSceneAt(i); if (!scene.isLoaded) continue; var strAssetGuid = AssetDatabase.AssetPathToGUID(scene.path); GUID.TryParse(strAssetGuid, out var assetGuid); scene.GetRootGameObjects(rootObjects); for (var j = 0; j < rootObjects.Count; j++) { var rootObject = rootObjects[j]; var scenePath = string.Empty; RebuildSceneInstanceMapping(report, dictMapping, rootObject, assetGuid, scenePath); } } RebuildForAllParents(dictMapping); } private static void RebuildSceneInstanceMapping(Report report, Dictionary dictMapping, GameObject gameObject, GUID assetGuid, string scenePath) { var transform = gameObject.transform; AppendToScenePath(gameObject, ref scenePath); if (report.TryGetSeverityFor(assetGuid, scenePath, out var mapping)) dictMapping.Add(gameObject.GetInstanceID(), new Report.MappingEntry(mapping.Severity, false)); for (var i = 0; i < transform.childCount; i++) { RebuildSceneInstanceMapping(report, dictMapping, transform.GetChild(i).gameObject, assetGuid, scenePath); } } private static void RebuildForAllParents(Dictionary dictMapping) { using var _ = DictionaryPool.Get(out var newMappings); foreach (var pair in dictMapping) { var obj = EditorUtility.EntityIdToObject(pair.Key); if (obj is not GameObject gameObject) continue; var severity = pair.Value.Severity; var transform = gameObject.transform.parent; while (transform != null) { gameObject = transform.gameObject; var instanceId = gameObject.GetInstanceID(); if (dictMapping.TryGetValue(instanceId, out var parentMapping)) { if (severity < parentMapping.Severity) severity = parentMapping.Severity; } else if (newMappings.TryGetValue(instanceId, out var currentMapping)) { if (currentMapping.Severity < severity) newMappings[instanceId] = new Report.MappingEntry(severity, true); } else { newMappings.Add(instanceId, new Report.MappingEntry(severity, true)); } transform = transform.parent; } } foreach (var pair in newMappings) { dictMapping.Add(pair.Key, pair.Value); } } internal static void RefreshUnityWindows() { EditorApplication.RepaintHierarchyWindow(); EditorApplication.RepaintProjectWindow(); } internal static bool IsValidForRun() { return !EditorApplication.isPlaying && !EditorApplication.isPlayingOrWillChangePlaymode && !EditorApplication.isCompiling; } } }