280 lines
No EOL
10 KiB
C#
280 lines
No EOL
10 KiB
C#
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<EditorProjectValidatorWindow>();
|
|
|
|
for (var i = 0; i < windows.Length; i++)
|
|
{
|
|
windows[i].Clear();
|
|
}
|
|
|
|
RefreshUnityWindows();
|
|
}
|
|
|
|
private static EditorProjectValidatorWindow InternalOpenWindow()
|
|
{
|
|
var window = EditorWindow.GetWindow<EditorProjectValidatorWindow>();
|
|
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("/", "<color=#00ff00><b>/</b></color>");
|
|
}
|
|
|
|
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(".", "<color=#00ff00><b>.</b></color>");
|
|
str = str.Replace("[", "<color=#00ff00><b>[</b></color>");
|
|
str = str.Replace("]", "<color=#00ff00><b>]</b></color>");
|
|
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<GameObject>.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<GUID, Report.MappingEntry> dictMapping)
|
|
{
|
|
using var _ = DictionaryPool<GUID, Report.MappingEntry>.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<int, Report.MappingEntry> dictMapping)
|
|
{
|
|
dictMapping.Clear();
|
|
using var _ = ListPool<GameObject>.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<int, Report.MappingEntry> 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<int, Report.MappingEntry> dictMapping)
|
|
{
|
|
using var _ = DictionaryPool<int, Report.MappingEntry>.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;
|
|
}
|
|
}
|
|
} |