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; } public static string GetAssetValidatorName(object validator) { var str = validator.GetType().Name; str = str.Replace("AssetValidator", string.Empty); str = ObjectNames.NicifyVariableName(str); return str; } internal static string GetGameObjectValidatorName(IGameObjectValidator validator) { var str = validator.GetType().Name; str = str.Replace("GameObjectValidator", string.Empty); str = ObjectNames.NicifyVariableName(str); return str; } internal static string GetComponentValidatorShortName(object obj) { var str = obj.GetType().Name; str = str.Replace("ComponentValidator", string.Empty); str = ObjectNames.NicifyVariableName(str); return str; } internal static string GetAttributeShortName(Attribute attribute) { var str = attribute.GetType().Name; str = str.Replace("Attribute", string.Empty); str = ObjectNames.NicifyVariableName(str); return str; } internal static void AppendToRelativePath(GameObject gameObject, ref string relativePath, bool initial) { if (string.IsNullOrEmpty(relativePath)) relativePath = gameObject.name; else relativePath = initial ? $"{relativePath}{gameObject.name}" : $"{relativePath}/{gameObject.name}"; } internal static string ApplyRichTextToRelativePath(string relativePath) { return relativePath.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.RelativePath, out var gameObject)) EditorGUIUtility.PingObject(gameObject); else EditorGUIUtility.PingObject(asset); } else { EditorGUIUtility.PingObject(asset); } } private static bool TryFindSceneObjectByPath(Scene scene, string relativePath, out GameObject gameObject) { using var _ = ListPool.Get(out var rootObjects); scene.GetRootGameObjects(rootObjects); var index = relativePath.IndexOf('/'); var rootName = index != -1 ? relativePath[..index] : relativePath; var childPath = index != -1 ? relativePath[(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); } } #if UNITY_6000_4_OR_NEWER 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 relativePath = string.Empty; RebuildSceneInstanceMapping(report, dictMapping, rootObject, assetGuid, relativePath, true); } } RebuildForAllParents(dictMapping); } private static void RebuildSceneInstanceMapping(Report report, Dictionary dictMapping, GameObject gameObject, GUID assetGuid, string relativePath, bool initial) { var transform = gameObject.transform; AppendToRelativePath(gameObject, ref relativePath, initial); if (report.TryGetSeverityFor(assetGuid, relativePath, out var mapping)) dictMapping.Add(gameObject.GetEntityId(), new Report.MappingEntry(mapping.Severity, false)); for (var i = 0; i < transform.childCount; i++) { RebuildSceneInstanceMapping(report, dictMapping, transform.GetChild(i).gameObject, assetGuid, relativePath, false); } } 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 entityId = gameObject.GetEntityId(); if (dictMapping.TryGetValue(entityId, out var parentMapping)) { if (severity < parentMapping.Severity) severity = parentMapping.Severity; } else if (newMappings.TryGetValue(entityId, out var currentMapping)) { if (currentMapping.Severity < severity) newMappings[entityId] = new Report.MappingEntry(severity, true); } else { newMappings.Add(entityId, new Report.MappingEntry(severity, true)); } transform = transform.parent; } } foreach (var pair in newMappings) { dictMapping.Add(pair.Key, pair.Value); } } #else 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 relativePath = string.Empty; RebuildSceneInstanceMapping(report, dictMapping, rootObject, assetGuid, relativePath, true); } } RebuildForAllParents(dictMapping); } private static void RebuildSceneInstanceMapping(Report report, Dictionary dictMapping, GameObject gameObject, GUID assetGuid, string relativePath, bool initial) { var transform = gameObject.transform; AppendToRelativePath(gameObject, ref relativePath, initial); if (report.TryGetSeverityFor(assetGuid, relativePath, 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, relativePath, false); } } 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); } } #endif internal static void RefreshUnityWindows() { EditorApplication.RepaintHierarchyWindow(); EditorApplication.RepaintProjectWindow(); } internal static bool IsValidForRun() { return !EditorApplication.isPlaying && !EditorApplication.isPlayingOrWillChangePlaymode && !EditorApplication.isCompiling; } } }