using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.Pool; using UnityEngine.SceneManagement; using Debug = UnityEngine.Debug; namespace Module.ProjectValidator.Editor { internal static class ValidatorRunner { private static ValidatorList _validatorList; private static TypeTree _typeTree; public static bool Run(bool showWindow = true) { if (!ProjectValidatorUtility.IsValidForRun()) return false; var stopwatch = new Stopwatch(); stopwatch.Start(); Initialize(); var report = new Report(); ValidateAllScenes(report); ValidateAllAssets(report); ValidateAllPrefabs(report); report.RebuildAssetMapping(); report.RebuildInstanceMapping(); report.SetAsActive(); ProjectValidatorUtility.RefreshUnityWindows(); if (showWindow) ProjectValidatorUtility.OpenWindow(); stopwatch.Stop(); Debug.Log($"Validator took {stopwatch.Elapsed.TotalMilliseconds}ms"); return true; } public static void Clear() { if (!Report.HasActive) return; Report.ClearActive(); ProjectValidatorUtility.ClearWindow(); } private static void Initialize() { var settings = ProjectValidatorSettings.GetOrCreate(); var assemblies = GetAssembliesFrom(settings); var enabled = GetEnabledValidators(settings); _validatorList = new ValidatorList(); _typeTree = new TypeTree(); FetchAllGameObjectValidators(enabled); FetchAllComponentValidators(enabled); FetchAllAttributeValidators(enabled); FetchAllAssetValidators(enabled); FetchAllTypesWithValidators(assemblies); FetchAllTypesWithValidators(assemblies); } private static Assembly[] GetAssembliesFrom(ProjectValidatorSettings settings) { var assemblies = new List(settings.assemblies.Count); for (var i = 0; i < settings.assemblies.Count; i++) { try { var assembly = Assembly.Load(settings.assemblies[i]); assemblies.Add(assembly); } catch (Exception e) { Debug.LogException(e); } } return assemblies.ToArray(); } private static HashSet GetEnabledValidators(ProjectValidatorSettings settings) { var enabled = new HashSet(settings.validators.Count); for (var i = 0; i < settings.validators.Count; i++) { try { if (!settings.validators[i].enabled) continue; var type = Type.GetType(settings.validators[i].assemblyQualifiedName); if (type != null) enabled.Add(type); } catch (Exception e) { Debug.LogException(e); } } return enabled; } private static void FetchAllAttributeValidators(HashSet enabled) { var types = TypeCache.GetTypesDerivedFrom(typeof(IAttributeValidator<>)); for (var i = 0; i < types.Count; i++) { if (enabled.Contains(types[i])) _validatorList.AddAttribute(types[i]); } } private static void FetchAllGameObjectValidators(HashSet enabled) { var types = TypeCache.GetTypesDerivedFrom(typeof(IGameObjectValidator)); for (var i = 0; i < types.Count; i++) { if (enabled.Contains(types[i])) _validatorList.AddGameObject(types[i]); } } private static void FetchAllComponentValidators(HashSet enabled) { var types = TypeCache.GetTypesDerivedFrom(typeof(IComponentValidator<>)); for (var i = 0; i < types.Count; i++) { if (enabled.Contains(types[i])) _validatorList.AddComponent(types[i]); } } private static void FetchAllAssetValidators(HashSet enabled) { var types = TypeCache.GetTypesDerivedFrom(typeof(IAssetValidator<>)); for (var i = 0; i < types.Count; i++) { if (enabled.Contains(types[i])) _validatorList.AddAsset(types[i]); } } private static void FetchAllTypesWithValidators(Assembly[] assemblies) { var types = TypeCache.GetTypesDerivedFrom(); for (var i = 0; i < types.Count; i++) { var type = types[i]; if (assemblies.Length == 0 || Array.IndexOf(assemblies, type.Assembly) != -1) _typeTree.Add(type, _validatorList); } } private static void ValidateAllScenes(Report report) { var assets = EditorAssetUtility.LoadAllAssets(); var rootObjects = new List(); for (var i = 0; i < assets.Length; i++) { var assetPath = AssetDatabase.GetAssetPath(assets[i]); var scene = SceneManager.GetSceneByPath(assetPath); var isLoaded = scene.isLoaded; try { if (!isLoaded) scene = EditorSceneManager.OpenScene(assetPath, OpenSceneMode.Additive); scene.GetRootGameObjects(rootObjects); for (var j = 0; j < rootObjects.Count; j++) { ValidateGameObject(rootObjects[j], string.Empty, report, true); } } catch (Exception e) { Debug.LogException(e); } finally { if (!isLoaded && scene.isLoaded) EditorSceneManager.CloseScene(scene, true); } } } private static void ValidateAllAssets(Report report) { ValidateAssetsBytype(report); foreach (var pair in _validatorList.AssetValidators) { var assets = EditorAssetUtility.LoadAllAssets(pair.Key); for (var i = 0; i < assets.Length; i++) { try { var assetPath = AssetDatabase.GetAssetPath(assets[i]); var assetGuid = AssetDatabase.GUIDFromAssetPath(assetPath); ValidateAsset(assets[i], assetGuid, assetPath, pair.Value, report); } catch (Exception e) { Debug.LogException(e); } } } } private static void ValidateAllPrefabs(Report report) { var assets = EditorAssetUtility.LoadAllAssets(); for (var i = 0; i < assets.Length; i++) { try { ValidateGameObject(assets[i], string.Empty, report, true); } catch (Exception e) { Debug.LogException(e); } } } private static void ValidateAssetsBytype(Report report) where T : UnityEngine.Object { var assets = EditorAssetUtility.LoadAllAssets(); for (var i = 0; i < assets.Length; i++) { try { ValidateUnityObject(assets[i], report); } catch (Exception e) { Debug.LogException(e); } } } private static void ValidateUnityObject(UnityEngine.Object obj, Report report) { var assetGuid = EditorAssetUtility.ObjectToAssetGuid(obj); var assetPath = AssetDatabase.GUIDToAssetPath(assetGuid); Validate(assetGuid, assetPath, obj, report); } private static void ValidateGameObject(GameObject gameObject, string relativePath, Report report, bool initial) { ProjectValidatorUtility.AppendToRelativePath(gameObject, ref relativePath, initial); var assetGuid = EditorAssetUtility.GetAssetGuid(gameObject); using var _ = ListPool.Get(out var results); for (var i = 0; i < _validatorList.GameObjectValidators.Count; i++) { results.Clear(); _validatorList.GameObjectValidators[i].Validate(gameObject, results); var type = ProjectValidatorUtility.GetGameObjectValidatorName(_validatorList.GameObjectValidators[i]); for (var j = 0; j < results.Count; j++) { var result = results[j]; if (result.Severity != EValidatorSeverity.Valid) report.Add(assetGuid, relativePath, string.Empty, type, result.Severity, result.Message); } } ValidateComponents(gameObject, assetGuid, relativePath, report); ValidateChildren(gameObject, relativePath, report); } private static void ValidateComponents(GameObject gameObject, GUID assetGuid, string relativePath, Report report) { using var _ = ListPool.Get(out var components); gameObject.GetComponents(components); for (var i = 0; i < components.Count; i++) { if (components[i] != null) Validate(assetGuid, relativePath, components[i], report); } } private static void ValidateAsset(UnityEngine.Object obj, GUID assetGuid, string relativePath, List validators, Report report) { using var _ = ListPool.Get(out var results); for (var i = 0; i < validators.Count; i++) { results.Clear(); var validator = validators[i]; validator.ValidatorMethod.Invoke(validator.Validator, new object[] { obj, results }); for (var j = 0; j < results.Count; j++) { var result = results[j]; if (result.Severity == EValidatorSeverity.Valid) continue; var validatorName = ProjectValidatorUtility.GetAssetValidatorName(validator.Validator); report.Add(assetGuid, relativePath, string.Empty, validatorName, result.Severity, result.Message); } } } private static void ValidateChildren(GameObject gameObject, string relativePath, Report report) { var transform = gameObject.transform; for (var i = 0; i < transform.childCount; i++) { ValidateGameObject(transform.GetChild(i).gameObject, relativePath, report, false); } } private static void Validate(GUID assetGuid, string relativePath, object obj, Report report) { var type = obj.GetType(); if (!_typeTree.Types.TryGetValue(type, out var entry)) return; var fieldPath = obj.GetType().Name; Validate(assetGuid, relativePath, fieldPath, obj, entry, report); } private static void Validate(GUID assetGuid, string relativePath, string parentFieldPath, object obj, TypeTree.Entry entry, Report report) { if (obj == null) return; if (entry.Components != null) { for (var i = 0; i < entry.Components.Count; i++) { try { var component = entry.Components[i]; ValidateComponent(component, obj, assetGuid, relativePath, report); } catch (Exception e) { Debug.LogException(e); } } } if (entry.Fields != null) { for (var i = 0; i < entry.Fields.Count; i++) { try { var field = entry.Fields[i]; var value = field.FieldInfo.GetValue(obj); var fieldPath = parentFieldPath; ProjectValidatorUtility.AppendToFieldPath(field.FieldInfo, ref fieldPath); if (value is IEnumerable ie) { var idx = 0; foreach (var eObj in ie) { var fieldPathArrElement = fieldPath; ProjectValidatorUtility.AppendToFieldPath(idx, ref fieldPathArrElement); ValidateField(field, eObj, assetGuid, relativePath, fieldPathArrElement, report); idx++; } } else { ValidateField(field, value, assetGuid, relativePath, fieldPath, report); } } catch (Exception e) { Debug.LogException(e); } } } if (entry.Entries != null) { for (var i = 0; i < entry.Entries.Count; i++) { try { var e = entry.Entries[i]; var value = e.FieldInfo.GetValue(obj); var fieldPath = parentFieldPath; ProjectValidatorUtility.AppendToFieldPath(e.FieldInfo, ref fieldPath); if (value is Array arr) { for (var j = 0; j < arr.Length; j++) { var eObj = arr.GetValue(j); var fieldPathArrElement = fieldPath; ProjectValidatorUtility.AppendToFieldPath(j, ref fieldPathArrElement); Validate(assetGuid, relativePath, fieldPathArrElement, eObj, e.Entry, report); } } else if (value is IEnumerable ie) { var idx = 0; foreach (var eObj in ie) { var fieldPathArrElement = fieldPath; ProjectValidatorUtility.AppendToFieldPath(idx, ref fieldPathArrElement); Validate(assetGuid, relativePath, fieldPathArrElement, eObj, e.Entry, report); idx++; } } else { Validate(assetGuid, relativePath, fieldPath, value, e.Entry, report); } } catch (Exception e) { Debug.LogException(e); } } } } private static void ValidateField(TypeTree.ValidatorField field, object value, GUID assetGuid, string relativePath, string fieldPath, Report report) { var result = (ValidatorResult)field.ValidatorMethod.Invoke(field.Validator, new[] { field.Attribute, value }); if (result.Severity != EValidatorSeverity.Valid) report.Add(assetGuid, relativePath, fieldPath, field.Attribute, result.Severity, result.Message); } private static void ValidateComponent(TypeTree.ValidatorComponent component, object value, GUID assetGuid, string relativePath, Report report) { using var _ = ListPool.Get(out var results); component.ValidatorMethod.Invoke(component.Validator, new[] { value, results }); var type = ProjectValidatorUtility.GetComponentValidatorShortName(component.Validator); for (var i = 0; i < results.Count; i++) { var result = results[i]; if (result.Severity != EValidatorSeverity.Valid) report.Add(assetGuid, relativePath, string.Empty, type, result.Severity, result.Message); } } } }