Added initial version 0.1.0
This commit is contained in:
parent
6aa4cb8596
commit
416759c213
64 changed files with 2181 additions and 0 deletions
67
Editor/Utilities/EditorAssetUtility.cs
Normal file
67
Editor/Utilities/EditorAssetUtility.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Module.ProjectValidator.Editor
|
||||
{
|
||||
internal static class EditorAssetUtility
|
||||
{
|
||||
public static T LoadFirstAsset<T>(string name) where T : Object
|
||||
{
|
||||
var guids = AssetDatabase.FindAssetGUIDs($"a:assets t:{typeof(T).Name} {name}");
|
||||
return guids.Length != 0 ? AssetDatabase.LoadAssetByGUID<T>(guids[0]) : null;
|
||||
}
|
||||
|
||||
public static T[] LoadAllAssets<T>() where T : Object
|
||||
{
|
||||
var guids = AssetDatabase.FindAssetGUIDs($"a:assets t:{typeof(T).Name}");
|
||||
var list = new List<T>(guids.Length);
|
||||
|
||||
foreach (var guid in guids)
|
||||
{
|
||||
var asset = AssetDatabase.LoadAssetByGUID<T>(guid);
|
||||
|
||||
if (asset != null)
|
||||
list.Add(asset);
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
internal static GUID GetAssetGuid(Object obj)
|
||||
{
|
||||
var assetGuid = new GUID();
|
||||
|
||||
if (obj is GameObject gameObject)
|
||||
{
|
||||
if (gameObject.scene.isLoaded)
|
||||
GUID.TryParse(AssetDatabase.AssetPathToGUID(gameObject.scene.path), out assetGuid);
|
||||
else if (PrefabUtility.IsPartOfPrefabAsset(gameObject))
|
||||
GUID.TryParse(AssetDatabase.AssetPathToGUID(gameObject.scene.path), out assetGuid);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUID.TryParse(AssetDatabase.GetAssetPath(obj), out assetGuid);
|
||||
}
|
||||
|
||||
return assetGuid;
|
||||
}
|
||||
|
||||
internal static string GetAssetName(GUID assetGuid)
|
||||
{
|
||||
if (assetGuid.Empty())
|
||||
return string.Empty;
|
||||
|
||||
var assetPath = AssetDatabase.GUIDToAssetPath(assetGuid);
|
||||
return Path.GetFileNameWithoutExtension(assetPath);
|
||||
}
|
||||
|
||||
internal static GUID ObjectToAssetGuid(Object obj)
|
||||
{
|
||||
var assetPath = AssetDatabase.GetAssetPath(obj);
|
||||
var strGuid = AssetDatabase.AssetPathToGUID(assetPath);
|
||||
return GUID.TryParse(strGuid, out var guid) ? guid : new GUID();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Editor/Utilities/EditorAssetUtility.cs.meta
Normal file
3
Editor/Utilities/EditorAssetUtility.cs.meta
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: afafd2a389cc46b2bc66029cb8d2cfea
|
||||
timeCreated: 1778924949
|
||||
44
Editor/Utilities/EditorIconUtility.cs
Normal file
44
Editor/Utilities/EditorIconUtility.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace Module.ProjectValidator.Editor
|
||||
{
|
||||
internal static class EditorIconUtility
|
||||
{
|
||||
private static bool _initialized;
|
||||
private static GUIContent _warning;
|
||||
private static GUIContent _error;
|
||||
private static GUIContent _warningRedirect;
|
||||
private static GUIContent _errorRedirect;
|
||||
private static GUIStyle _style;
|
||||
|
||||
private static void Initialize()
|
||||
{
|
||||
_initialized = true;
|
||||
_warning = new GUIContent(EditorAssetUtility.LoadFirstAsset<Texture>("editor_project_validator_warning"));
|
||||
_error = new GUIContent(EditorAssetUtility.LoadFirstAsset<Texture>("editor_project_validator_error"));
|
||||
_warningRedirect = new GUIContent(EditorAssetUtility.LoadFirstAsset<Texture>("editor_project_validator_warning_redirect"));
|
||||
_errorRedirect = new GUIContent(EditorAssetUtility.LoadFirstAsset<Texture>("editor_project_validator_error_redirect"));
|
||||
_style = new GUIStyle();
|
||||
}
|
||||
|
||||
public static GUIContent GetIcon(EValidatorSeverity severity, bool isRedirect)
|
||||
{
|
||||
if (!_initialized)
|
||||
Initialize();
|
||||
|
||||
if (isRedirect)
|
||||
return severity == EValidatorSeverity.Warning ? _warningRedirect : _errorRedirect;
|
||||
|
||||
return severity == EValidatorSeverity.Warning ? _warning : _error;
|
||||
}
|
||||
|
||||
public static void Draw(Rect rect, EValidatorSeverity severity, bool isRedirect)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
var content = GetIcon(severity, isRedirect);
|
||||
_style.Draw(rect, content, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Editor/Utilities/EditorIconUtility.cs.meta
Normal file
3
Editor/Utilities/EditorIconUtility.cs.meta
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d246451b954e46769686738d623b833b
|
||||
timeCreated: 1779021007
|
||||
280
Editor/Utilities/ProjectValidatorUtility.cs
Normal file
280
Editor/Utilities/ProjectValidatorUtility.cs
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Editor/Utilities/ProjectValidatorUtility.cs.meta
Normal file
3
Editor/Utilities/ProjectValidatorUtility.cs.meta
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7775e42f8dfc437db085465caa4eefd9
|
||||
timeCreated: 1778924236
|
||||
Loading…
Add table
Add a link
Reference in a new issue