From dd55a87740941201d13f2c4ac90bee71ae1bfb29 Mon Sep 17 00:00:00 2001 From: Anders Ejlersen Date: Sun, 24 May 2026 18:06:56 +0200 Subject: [PATCH] - Validator: Added asset validators with material texture and shader validation - Validator: Added option to enable/disable certain validators - Project Settings: Fixed issue, where changes weren't always saved - Unity: Removed deprecated warnings in Unity 6.4 --- .../EditorProjectValidatorHierarchy.cs | 15 ++- .../editor_project_validator_settings.png | Bin 0 -> 595 bytes ...editor_project_validator_settings.png.meta | 117 ++++++++++++++++++ Editor/Objects/Report.cs | 23 +++- Editor/Objects/TypeTree.cs | 13 +- Editor/Objects/ValidatorList.cs | 41 ++++++ Editor/Settings/ProjectValidatorSettings.cs | 83 +++++++++++-- Editor/Utilities/EditorAssetUtility.cs | 20 ++- Editor/Utilities/ProjectValidatorUtility.cs | 97 +++++++++++++++ Editor/ValidatorRunner.cs | 108 +++++++++++++--- Editor/Validators/Assets.meta | 3 + .../Assets/AssetValidatorMaterialShader.cs | 43 +++++++ .../AssetValidatorMaterialShader.cs.meta | 3 + .../Assets/AssetValidatorMaterialTexture.cs | 31 +++++ .../AssetValidatorMaterialTexture.cs.meta | 3 + .../Attributes/AttributeValidatorObsolete.cs | 12 ++ .../AttributeValidatorObsolete.cs.meta | 2 + .../GameObjectValidatorTransform.cs | 28 +++++ .../GameObjectValidatorTransform.cs.meta | 3 + Editor/Window/EditorProjectValidatorWindow.cs | 6 + Editor/Window/Objects.meta | 3 + ...orProjectValidatorEnabledPropertyDrawer.cs | 23 ++++ ...jectValidatorEnabledPropertyDrawer.cs.meta | 3 + ...StyleSheetEditorProjectValidatorWindow.uss | 13 ++ .../UxmlEditorProjectValidatorWindow.uxml | 1 + README.md | 45 ++++++- Runtime/Interfaces/IAssetValidator.cs | 10 ++ Runtime/Interfaces/IAssetValidator.cs.meta | 3 + package.json | 2 +- ~Images/editor-project-settings.png | Bin 0 -> 35163 bytes 30 files changed, 716 insertions(+), 38 deletions(-) create mode 100644 Editor/Icons/editor_project_validator_settings.png create mode 100644 Editor/Icons/editor_project_validator_settings.png.meta create mode 100644 Editor/Validators/Assets.meta create mode 100644 Editor/Validators/Assets/AssetValidatorMaterialShader.cs create mode 100644 Editor/Validators/Assets/AssetValidatorMaterialShader.cs.meta create mode 100644 Editor/Validators/Assets/AssetValidatorMaterialTexture.cs create mode 100644 Editor/Validators/Assets/AssetValidatorMaterialTexture.cs.meta create mode 100644 Editor/Validators/Attributes/AttributeValidatorObsolete.cs create mode 100644 Editor/Validators/Attributes/AttributeValidatorObsolete.cs.meta create mode 100644 Editor/Validators/GameObject/GameObjectValidatorTransform.cs create mode 100644 Editor/Validators/GameObject/GameObjectValidatorTransform.cs.meta create mode 100644 Editor/Window/Objects.meta create mode 100644 Editor/Window/Objects/EditorProjectValidatorEnabledPropertyDrawer.cs create mode 100644 Editor/Window/Objects/EditorProjectValidatorEnabledPropertyDrawer.cs.meta create mode 100644 Runtime/Interfaces/IAssetValidator.cs create mode 100644 Runtime/Interfaces/IAssetValidator.cs.meta create mode 100644 ~Images/editor-project-settings.png diff --git a/Editor/Hierarchy/EditorProjectValidatorHierarchy.cs b/Editor/Hierarchy/EditorProjectValidatorHierarchy.cs index a41dbc3..cacd52c 100644 --- a/Editor/Hierarchy/EditorProjectValidatorHierarchy.cs +++ b/Editor/Hierarchy/EditorProjectValidatorHierarchy.cs @@ -10,19 +10,32 @@ namespace Module.ProjectValidator.Editor { static EditorProjectValidatorHierarchy() { +#if UNITY_6000_4_OR_NEWER + EditorApplication.hierarchyWindowItemByEntityIdOnGUI -= OnHierarchyWindowItemByEntityIdOnGUI; + EditorApplication.hierarchyWindowItemByEntityIdOnGUI += OnHierarchyWindowItemByEntityIdOnGUI; +#else EditorApplication.hierarchyWindowItemOnGUI -= OnHierarchyWindowItemOnGUI; EditorApplication.hierarchyWindowItemOnGUI += OnHierarchyWindowItemOnGUI; +#endif EditorSceneManager.sceneOpened -= OnSceneOpened; EditorSceneManager.sceneOpened += OnSceneOpened; } +#if UNITY_6000_4_OR_NEWER + private static void OnHierarchyWindowItemByEntityIdOnGUI(EntityId entityId, Rect selectionRect) + { + if (Report.HasActive && Report.Active.TryGetSeverityFor(entityId, out var instance) && instance.Severity != EValidatorSeverity.Valid) + EditorIconUtility.Draw(new Rect(selectionRect.x, selectionRect.y, selectionRect.height, selectionRect.height), instance.Severity, instance.IsRedirect); + } +#else private static void OnHierarchyWindowItemOnGUI(int instanceID, Rect selectionRect) { if (Report.HasActive && Report.Active.TryGetSeverityFor(instanceID, out var instance) && instance.Severity != EValidatorSeverity.Valid) EditorIconUtility.Draw(new Rect(selectionRect.x, selectionRect.y, selectionRect.height, selectionRect.height), instance.Severity, instance.IsRedirect); } - +#endif + private static void OnSceneOpened(Scene scene, OpenSceneMode mode) { if (Report.HasActive && ProjectValidatorUtility.IsValidForRun()) diff --git a/Editor/Icons/editor_project_validator_settings.png b/Editor/Icons/editor_project_validator_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..f938577b9a9acb7b4166b6f9d554303ba72d4792 GIT binary patch literal 595 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucLCF%=h?3y^w370~qEv=}#LT=BJwMkF1yemk zJwqd5M)wk+q8+Ibo@t(*S_~XO4l9EcBP#N%Y$7#-9wVF#;%fNjR%^H5D##Suk zTY=&aYeC(P48{k}FP=9|XN-DX%V5hOksOtso4@vL=L%V!`%$lLEratLey!n}949D{ z{D)}+H-lnJ@AtZQd*?5oV%D6he(Z;4`YYadtoA;dxxrr14D3$(h4=7C?Ay&`z`Q|h z?eaOFc@A{V%x|^Zo7qyF$N7MjVM@^HWtsP~&f6d4XSnUFG-2gbV8}8sc)I$ztaD0e F0st^2rsMzs literal 0 HcmV?d00001 diff --git a/Editor/Icons/editor_project_validator_settings.png.meta b/Editor/Icons/editor_project_validator_settings.png.meta new file mode 100644 index 0000000..524687d --- /dev/null +++ b/Editor/Icons/editor_project_validator_settings.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: a6c61d5fbd310894d8159ba6af32d7e3 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Objects/Report.cs b/Editor/Objects/Report.cs index aa6d136..278955a 100644 --- a/Editor/Objects/Report.cs +++ b/Editor/Objects/Report.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; -using UnityEditor; +using UnityEngine; + namespace Module.ProjectValidator.Editor { @@ -11,7 +12,12 @@ namespace Module.ProjectValidator.Editor public readonly List Entries = new(); private readonly Dictionary _assetToSeverityMapping = new(); + +#if UNITY_6000_4_OR_NEWER + private readonly Dictionary _instanceToSeverityMapping = new(); +#else private readonly Dictionary _instanceToSeverityMapping = new(); +#endif public void Add(GUID assetGuid, string relativePath, string fieldPath, Attribute attribute, EValidatorSeverity severity, string message) { @@ -58,13 +64,23 @@ namespace Module.ProjectValidator.Editor public bool TryGetSeverityFor(string guid, out MappingEntry mapping) { - if (GUID.TryParse(guid, out var assetGuid) && _assetToSeverityMapping.TryGetValue(assetGuid, out mapping)) + if (UnityEngine.GUID.TryParse(guid, out var assetGuid) && _assetToSeverityMapping.TryGetValue(assetGuid, out mapping)) return true; mapping = new MappingEntry(); return false; } - + +#if UNITY_6000_4_OR_NEWER + public bool TryGetSeverityFor(EntityId entityId, out MappingEntry mapping) + { + if (_instanceToSeverityMapping.TryGetValue(entityId, out mapping)) + return true; + + mapping = new MappingEntry(); + return false; + } +#else public bool TryGetSeverityFor(int instanceId, out MappingEntry mapping) { if (_instanceToSeverityMapping.TryGetValue(instanceId, out mapping)) @@ -73,6 +89,7 @@ namespace Module.ProjectValidator.Editor mapping = new MappingEntry(); return false; } +#endif public bool TryGetSeverityFor(GUID assetGuid, string relativePath, out MappingEntry mapping) { diff --git a/Editor/Objects/TypeTree.cs b/Editor/Objects/TypeTree.cs index 5b69e0c..4cddc30 100644 --- a/Editor/Objects/TypeTree.cs +++ b/Editor/Objects/TypeTree.cs @@ -31,9 +31,16 @@ namespace Module.ProjectValidator.Editor if (!IsFieldSerializable(fi)) continue; - var attributes = fi.GetCustomAttributes(); + var fieldAttributes = fi.GetCustomAttributes(); + var fieldTypeAttributes = fi.FieldType.GetCustomAttributes(); - foreach (var attribute in attributes) + foreach (var attribute in fieldAttributes) + { + if (validatorList.TryGetAttributeValidator(attribute.GetType(), out var validator)) + entry.AddField(fi, attribute, validator); + } + + foreach (var attribute in fieldTypeAttributes) { if (validatorList.TryGetAttributeValidator(attribute.GetType(), out var validator)) entry.AddField(fi, attribute, validator); @@ -164,7 +171,7 @@ namespace Module.ProjectValidator.Editor return Components == null && Fields == null && Entries == null; } } - + public sealed class ValidatorField { public readonly FieldInfo FieldInfo; diff --git a/Editor/Objects/ValidatorList.cs b/Editor/Objects/ValidatorList.cs index 0f498fa..39221ff 100644 --- a/Editor/Objects/ValidatorList.cs +++ b/Editor/Objects/ValidatorList.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Runtime.Serialization; using UnityEngine; @@ -11,6 +12,7 @@ namespace Module.ProjectValidator.Editor private readonly Dictionary _attributeValidators = new(); private readonly Dictionary> _componentValidators = new(); public readonly List GameObjectValidators = new(); + public readonly Dictionary> AssetValidators = new(); public void AddAttribute(Type type) { @@ -75,6 +77,33 @@ namespace Module.ProjectValidator.Editor Debug.LogException(e); } } + + public void AddAsset(Type type) + { + if (type.IsInterface || type.IsAbstract) + return; + + var typeValidator = type.GetInterfaces().FirstOrDefault(typeInterface => typeInterface.IsGenericType && typeInterface.GetGenericTypeDefinition() == typeof(IAssetValidator<>)); + var componentType = typeValidator?.GetGenericArguments()[0]; + + if (componentType == null) + return; + + try + { + var instance = FormatterServices.GetUninitializedObject(type); + var validator = new AssetValidator(instance); + + if (AssetValidators.TryGetValue(componentType, out var list)) + list.Add(validator); + else + AssetValidators.Add(componentType, new List { validator }); + } + catch (Exception e) + { + Debug.LogException(e); + } + } public bool TryGetAttributeValidator(Type type, out object validatorInstance) { @@ -85,5 +114,17 @@ namespace Module.ProjectValidator.Editor { return _componentValidators.TryGetValue(type, out validatorInstances); } + + public sealed class AssetValidator + { + public readonly object Validator; + public readonly MethodInfo ValidatorMethod; + + public AssetValidator(object validator) + { + Validator = validator; + ValidatorMethod = validator.GetType().GetMethod("Validate"); + } + } } } \ No newline at end of file diff --git a/Editor/Settings/ProjectValidatorSettings.cs b/Editor/Settings/ProjectValidatorSettings.cs index b526db7..7efb992 100644 --- a/Editor/Settings/ProjectValidatorSettings.cs +++ b/Editor/Settings/ProjectValidatorSettings.cs @@ -1,27 +1,35 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.UIElements; using UnityEditor.UIElements; using UnityEditorInternal; +using UnityEngine.Pool; +using Object = UnityEngine.Object; namespace Module.ProjectValidator.Editor { internal sealed class ProjectValidatorSettings : ScriptableObject { public List assemblies = new(); - + public List validators = new(); + + public const string MenuPath = "Project/Project Validator"; private const string AssetPath = "ProjectSettings/ProjectValidatorSettings.asset"; private const string StyleSheetName = "StyleSheetProjectValidatorSettings"; internal static ProjectValidatorSettings GetOrCreate() { var objects = InternalEditorUtility.LoadSerializedFileAndForget(AssetPath); + ProjectValidatorSettings settings; if (objects.Length != 0) - return (ProjectValidatorSettings)objects[0]; + settings = (ProjectValidatorSettings)objects[0]; + else + settings = CreateInstance(); - var settings = CreateInstance(); + PopulateValidatorList(settings); InternalEditorUtility.SaveToSerializedFileAndForget(new Object[] { settings }, AssetPath, true); return settings; } @@ -29,7 +37,7 @@ namespace Module.ProjectValidator.Editor [SettingsProvider] public static SettingsProvider CreateProvider() { - return new SettingsProvider("Project/Project Validator", SettingsScope.Project) + return new SettingsProvider(MenuPath, SettingsScope.Project) { label = "Project Validator", activateHandler = (_, root) => @@ -37,16 +45,73 @@ namespace Module.ProjectValidator.Editor var settings = GetOrCreate(); var serializedObject = new SerializedObject(settings); var container = new VisualElement { style = { flexDirection = FlexDirection.Column } }; - var propertyField = new PropertyField(serializedObject.FindProperty("assemblies"), "Assemblies"); - propertyField.RegisterCallback>(_ => InternalEditorUtility.SaveToSerializedFileAndForget(new[] { serializedObject.targetObject }, AssetPath, true)); - propertyField.RegisterValueChangeCallback(_ => InternalEditorUtility.SaveToSerializedFileAndForget(new[] { serializedObject.targetObject }, AssetPath, true)); - container.Add(propertyField); + + var assemblyField = new PropertyField(serializedObject.FindProperty(nameof(assemblies)), "Assemblies"); + container.Add(assemblyField); + + var enabledField = new PropertyField(serializedObject.FindProperty(nameof(validators)), "Validators"); + container.Add(enabledField); + root.Add(container); root.Bind(serializedObject); + root.RegisterCallback(_ => InternalEditorUtility.SaveToSerializedFileAndForget(new[] { serializedObject.targetObject }, AssetPath, true)); + root.RegisterCallback(_ => InternalEditorUtility.SaveToSerializedFileAndForget(new[] { serializedObject.targetObject }, AssetPath, true)); root.styleSheets.Add(EditorAssetUtility.LoadFirstAsset(StyleSheetName)); }, keywords = new HashSet(new[] { "Project", "Validator", "Assemblies" }) }; } + + private static void PopulateValidatorList(ProjectValidatorSettings settings) + { + using var pool0 = ListPool.Get(out var list); + using var pool1 = ListPool.Get(out var temp); + + FetchValidatorsOfType(typeof(IAssetValidator<>), list); + FetchValidatorsOfType(typeof(IAttributeValidator<>), list); + FetchValidatorsOfType(typeof(IComponentValidator<>), list); + FetchValidatorsOfType(typeof(IGameObjectValidator), list); + + for (var i = 0; i < settings.validators.Count; i++) + { + temp.Add(settings.validators[i].type); + } + + for (var i = 0; i < list.Count; i++) + { + if (!temp.Contains(list[i])) + settings.validators.Add(new ValidatorEnabled(list[i], true)); + } + + for (var i = temp.Count - 1; i >= 0; i--) + { + if (!list.Contains(temp[i])) + settings.validators.RemoveAt(i); + } + } + + private static void FetchValidatorsOfType(Type type, List typeNames) + { + var types = TypeCache.GetTypesDerivedFrom(type); + + for (var i = 0; i < types.Count; i++) + { + if (!types[i].IsInterface && !types[i].IsAbstract) + typeNames.Add(types[i].FullName); + } + } + + [Serializable] + public sealed class ValidatorEnabled + { + public string type; + public bool enabled; + + public ValidatorEnabled(string type, bool enabled) + { + this.type = type; + this.enabled = enabled; + } + } } } \ No newline at end of file diff --git a/Editor/Utilities/EditorAssetUtility.cs b/Editor/Utilities/EditorAssetUtility.cs index be7796a..e3c0ea4 100644 --- a/Editor/Utilities/EditorAssetUtility.cs +++ b/Editor/Utilities/EditorAssetUtility.cs @@ -1,7 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; +using Object = UnityEngine.Object; namespace Module.ProjectValidator.Editor { @@ -29,6 +31,22 @@ namespace Module.ProjectValidator.Editor return list.ToArray(); } + public static Object[] LoadAllAssets(Type type) + { + var guids = AssetDatabase.FindAssetGUIDs($"a:assets t:{type.Name}"); + var list = new List(guids.Length); + + foreach (var guid in guids) + { + var asset = AssetDatabase.LoadAssetByGUID(guid, type); + + if (asset != null) + list.Add(asset); + } + + return list.ToArray(); + } + internal static GUID GetAssetGuid(Object obj) { var assetPath = string.Empty; diff --git a/Editor/Utilities/ProjectValidatorUtility.cs b/Editor/Utilities/ProjectValidatorUtility.cs index f9f520c..10d6eef 100644 --- a/Editor/Utilities/ProjectValidatorUtility.cs +++ b/Editor/Utilities/ProjectValidatorUtility.cs @@ -37,6 +37,14 @@ namespace Module.ProjectValidator.Editor 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) { @@ -196,6 +204,93 @@ namespace Module.ProjectValidator.Editor } } +#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(); @@ -223,6 +318,7 @@ namespace Module.ProjectValidator.Editor RebuildForAllParents(dictMapping); } + private static void RebuildSceneInstanceMapping(Report report, Dictionary dictMapping, GameObject gameObject, GUID assetGuid, string relativePath, bool initial) { var transform = gameObject.transform; @@ -280,6 +376,7 @@ namespace Module.ProjectValidator.Editor dictMapping.Add(pair.Key, pair.Value); } } +#endif internal static void RefreshUnityWindows() { diff --git a/Editor/ValidatorRunner.cs b/Editor/ValidatorRunner.cs index 3cbfa63..54716f5 100644 --- a/Editor/ValidatorRunner.cs +++ b/Editor/ValidatorRunner.cs @@ -13,7 +13,6 @@ namespace Module.ProjectValidator.Editor { internal static class ValidatorRunner { - private static bool _initialized; private static ValidatorList _validatorList; private static TypeTree _typeTree; @@ -56,21 +55,19 @@ namespace Module.ProjectValidator.Editor private static void Initialize() { - if (_initialized) - return; - var settings = ProjectValidatorSettings.GetOrCreate(); var assemblies = GetAssembliesFrom(settings); - + var enabled = GetEnabledValidators(settings); + _validatorList = new ValidatorList(); _typeTree = new TypeTree(); - FetchAllGameObjectValidators(); - FetchAllComponentValidators(); - FetchAllAttributeValidators(); + FetchAllGameObjectValidators(enabled); + FetchAllComponentValidators(enabled); + FetchAllAttributeValidators(enabled); + FetchAllAssetValidators(enabled); FetchAllTypesWithValidators(assemblies); FetchAllTypesWithValidators(assemblies); - _initialized = true; } private static Assembly[] GetAssembliesFrom(ProjectValidatorSettings settings) @@ -93,33 +90,72 @@ namespace Module.ProjectValidator.Editor return assemblies.ToArray(); } - private static void FetchAllAttributeValidators() + 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].type); + + 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++) { - _validatorList.AddAttribute(types[i]); + if (enabled.Contains(types[i])) + _validatorList.AddAttribute(types[i]); } } - private static void FetchAllGameObjectValidators() + private static void FetchAllGameObjectValidators(HashSet enabled) { var types = TypeCache.GetTypesDerivedFrom(typeof(IGameObjectValidator)); for (var i = 0; i < types.Count; i++) { - _validatorList.AddGameObject(types[i]); + if (enabled.Contains(types[i])) + _validatorList.AddGameObject(types[i]); } } - private static void FetchAllComponentValidators() + private static void FetchAllComponentValidators(HashSet enabled) { var types = TypeCache.GetTypesDerivedFrom(typeof(IComponentValidator<>)); for (var i = 0; i < types.Count; i++) { - _validatorList.AddComponent(types[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]); } } @@ -172,6 +208,25 @@ namespace Module.ProjectValidator.Editor 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) @@ -253,6 +308,29 @@ namespace Module.ProjectValidator.Editor 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) { diff --git a/Editor/Validators/Assets.meta b/Editor/Validators/Assets.meta new file mode 100644 index 0000000..a1c08b2 --- /dev/null +++ b/Editor/Validators/Assets.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ddbf30cd5a674751be0c125c1f4e917b +timeCreated: 1779623970 \ No newline at end of file diff --git a/Editor/Validators/Assets/AssetValidatorMaterialShader.cs b/Editor/Validators/Assets/AssetValidatorMaterialShader.cs new file mode 100644 index 0000000..6a0dfce --- /dev/null +++ b/Editor/Validators/Assets/AssetValidatorMaterialShader.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; +using UnityEngine.Rendering; + +namespace Module.ProjectValidator.Editor +{ + internal sealed class AssetValidatorMaterialShader : IAssetValidator + { + public void Validate(Material obj, List results) + { + if (obj.shader == null) + results.Add(ValidatorResult.Create(EValidatorSeverity.Error, "Shader is Null")); + else if (!IsCompatible(obj.shader)) + results.Add(ValidatorResult.Create(EValidatorSeverity.Warning, $"Shader '{obj.shader.name}' is not compatible with render pipeline")); + else if (!obj.shader.isSupported) + results.Add(ValidatorResult.Create(EValidatorSeverity.Warning, $"Shader '{obj.shader.name}' is not supported")); + else if (ShaderUtil.ShaderHasError(obj.shader)) + results.Add(ValidatorResult.Create(EValidatorSeverity.Error, $"Shader '{obj.shader.name}' has compile errors")); + } + + private static bool IsCompatible(Shader shader) + { + var pipeline = GraphicsSettings.currentRenderPipeline; + + if (pipeline == null) + return true; + + var tagSearch = new ShaderTagId("RenderPipeline"); + var tagPipeline = new ShaderTagId(pipeline.renderPipelineShaderTag); + + for (var i = 0; i < shader.passCount; i++) + { + var tagPass = shader.FindPassTagValue(i, tagSearch); + + if (tagPass == tagPipeline) + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/Editor/Validators/Assets/AssetValidatorMaterialShader.cs.meta b/Editor/Validators/Assets/AssetValidatorMaterialShader.cs.meta new file mode 100644 index 0000000..a1eceb1 --- /dev/null +++ b/Editor/Validators/Assets/AssetValidatorMaterialShader.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2218d247daff44bf84629756b63ea650 +timeCreated: 1779623986 \ No newline at end of file diff --git a/Editor/Validators/Assets/AssetValidatorMaterialTexture.cs b/Editor/Validators/Assets/AssetValidatorMaterialTexture.cs new file mode 100644 index 0000000..9f07b96 --- /dev/null +++ b/Editor/Validators/Assets/AssetValidatorMaterialTexture.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Rendering; + +namespace Module.ProjectValidator.Editor +{ + internal sealed class AssetValidatorMaterialTexture : IAssetValidator + { + public void Validate(Material obj, List results) + { + if (obj.shader == null) + return; + + var count = obj.shader.GetPropertyCount(); + + for (var i = 0; i < count; i++) + { + var propertyType = obj.shader.GetPropertyType(i); + + if (propertyType != ShaderPropertyType.Texture) + continue; + + var propertyName = obj.shader.GetPropertyName(i); + var propertyValue = obj.GetTexture(propertyName); + + if (propertyValue == null) + results.Add(ValidatorResult.Create(EValidatorSeverity.Warning, $"Texture property '{propertyName}' is Null")); + } + } + } +} \ No newline at end of file diff --git a/Editor/Validators/Assets/AssetValidatorMaterialTexture.cs.meta b/Editor/Validators/Assets/AssetValidatorMaterialTexture.cs.meta new file mode 100644 index 0000000..d975053 --- /dev/null +++ b/Editor/Validators/Assets/AssetValidatorMaterialTexture.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: cfeb4b29a9cd4ea79c1e9325b8122e17 +timeCreated: 1779627240 \ No newline at end of file diff --git a/Editor/Validators/Attributes/AttributeValidatorObsolete.cs b/Editor/Validators/Attributes/AttributeValidatorObsolete.cs new file mode 100644 index 0000000..0eb3c19 --- /dev/null +++ b/Editor/Validators/Attributes/AttributeValidatorObsolete.cs @@ -0,0 +1,12 @@ +using System; + +namespace Module.ProjectValidator.Editor +{ + internal sealed class AttributeValidatorObsolete : IAttributeValidator + { + public ValidatorResult Validate(ObsoleteAttribute attribute, object value) + { + return ValidatorResult.Create(EValidatorSeverity.Error, "Obsolete"); + } + } +} \ No newline at end of file diff --git a/Editor/Validators/Attributes/AttributeValidatorObsolete.cs.meta b/Editor/Validators/Attributes/AttributeValidatorObsolete.cs.meta new file mode 100644 index 0000000..2a54b7b --- /dev/null +++ b/Editor/Validators/Attributes/AttributeValidatorObsolete.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 982ac4e898cc2ca438e98a2f0034a8d3 \ No newline at end of file diff --git a/Editor/Validators/GameObject/GameObjectValidatorTransform.cs b/Editor/Validators/GameObject/GameObjectValidatorTransform.cs new file mode 100644 index 0000000..58b6f92 --- /dev/null +++ b/Editor/Validators/GameObject/GameObjectValidatorTransform.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Module.ProjectValidator.Editor +{ + internal sealed class GameObjectValidatorTransform : IGameObjectValidator + { + public void Validate(GameObject gameObject, List results) + { + var transform = gameObject.transform; + var lp = transform.localPosition; + var lr = transform.localRotation; + var ls = transform.localScale; + + if (IsInvalid(lp.x) || IsInvalid(lp.y) || IsInvalid(lp.z)) + results.Add(ValidatorResult.Create(EValidatorSeverity.Error, $"Local position '{lp}' is invalid")); + if (IsInvalid(lr.x) || IsInvalid(lr.y) || IsInvalid(lr.z) || IsInvalid(lr.w)) + results.Add(ValidatorResult.Create(EValidatorSeverity.Error, $"Local rotation '{lr}' is invalid")); + if (IsInvalid(ls.x) || IsInvalid(ls.y) || IsInvalid(ls.z)) + results.Add(ValidatorResult.Create(EValidatorSeverity.Error, $"Local scale '{ls}' is invalid")); + } + + private static bool IsInvalid(float value) + { + return float.IsNaN(value) || float.IsInfinity(value); + } + } +} \ No newline at end of file diff --git a/Editor/Validators/GameObject/GameObjectValidatorTransform.cs.meta b/Editor/Validators/GameObject/GameObjectValidatorTransform.cs.meta new file mode 100644 index 0000000..89d7bda --- /dev/null +++ b/Editor/Validators/GameObject/GameObjectValidatorTransform.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 727d5de59b004deb8c192337bcee132e +timeCreated: 1779628852 \ No newline at end of file diff --git a/Editor/Window/EditorProjectValidatorWindow.cs b/Editor/Window/EditorProjectValidatorWindow.cs index 4989d7d..7966bc8 100644 --- a/Editor/Window/EditorProjectValidatorWindow.cs +++ b/Editor/Window/EditorProjectValidatorWindow.cs @@ -24,6 +24,7 @@ namespace Module.ProjectValidator.Editor root.Q("button-run").clicked += OnToolbarButtonRunClicked; root.Q("button-clear").clicked += OnToolbarButtonClearClicked; root.Q().RegisterValueChangedCallback(OnToolbarSearchFieldChanged); + root.Q("button-settings").clicked += OnToolbarButtonSettingsClicked; _treeView = root.Q(); _treeView.columns["asset"].makeCell = CreateObjectField; @@ -126,6 +127,11 @@ namespace Module.ProjectValidator.Editor ValidatorRunner.Clear(); } + private void OnToolbarButtonSettingsClicked() + { + SettingsService.OpenProjectSettings(ProjectValidatorSettings.MenuPath); + } + private void OnToolbarSearchFieldChanged(ChangeEvent evt) { _searchFilter = evt.newValue; diff --git a/Editor/Window/Objects.meta b/Editor/Window/Objects.meta new file mode 100644 index 0000000..b0b5e82 --- /dev/null +++ b/Editor/Window/Objects.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a22633e08b3d4c0fbbea654a8a8cd287 +timeCreated: 1779637991 \ No newline at end of file diff --git a/Editor/Window/Objects/EditorProjectValidatorEnabledPropertyDrawer.cs b/Editor/Window/Objects/EditorProjectValidatorEnabledPropertyDrawer.cs new file mode 100644 index 0000000..1bb94fa --- /dev/null +++ b/Editor/Window/Objects/EditorProjectValidatorEnabledPropertyDrawer.cs @@ -0,0 +1,23 @@ +using UnityEditor; +using UnityEditor.UIElements; +using UnityEngine.UIElements; + +namespace Module.ProjectValidator.Editor +{ + [CustomPropertyDrawer(typeof(ProjectValidatorSettings.ValidatorEnabled))] + internal sealed class EditorProjectValidatorEnabledPropertyDrawer : PropertyDrawer + { + public override VisualElement CreatePropertyGUI(SerializedProperty property) + { + var spType = property.FindPropertyRelative(nameof(ProjectValidatorSettings.ValidatorEnabled.type)); + var spEnabled = property.FindPropertyRelative(nameof(ProjectValidatorSettings.ValidatorEnabled.enabled)); + + var root = new VisualElement { style = { flexDirection = FlexDirection.Row } }; + var veType = new PropertyField(spType, string.Empty) { style = { flexGrow = 1f } }; + var veEnabled = new PropertyField(spEnabled, string.Empty); + root.Add(veEnabled); + root.Add(veType); + return root; + } + } +} \ No newline at end of file diff --git a/Editor/Window/Objects/EditorProjectValidatorEnabledPropertyDrawer.cs.meta b/Editor/Window/Objects/EditorProjectValidatorEnabledPropertyDrawer.cs.meta new file mode 100644 index 0000000..25b5872 --- /dev/null +++ b/Editor/Window/Objects/EditorProjectValidatorEnabledPropertyDrawer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e6c8cb829f514028b705bf121373c67c +timeCreated: 1779638011 \ No newline at end of file diff --git a/Editor/Window/Uxml/StyleSheetEditorProjectValidatorWindow.uss b/Editor/Window/Uxml/StyleSheetEditorProjectValidatorWindow.uss index 18612b5..ab2bb00 100644 --- a/Editor/Window/Uxml/StyleSheetEditorProjectValidatorWindow.uss +++ b/Editor/Window/Uxml/StyleSheetEditorProjectValidatorWindow.uss @@ -17,3 +17,16 @@ } + +.toolbar-button-settings { + width: 24px; + min-width: 24px; + max-width: 24px; + padding-top: 0; + padding-right: 0; + padding-bottom: 0; + padding-left: 0; + background-image: url("project://database/Packages/com.module.project-validator/Editor/Icons/editor_project_validator_settings.png?fileID=2800000&guid=a6c61d5fbd310894d8159ba6af32d7e3&type=3#editor_project_validator_settings"); + -unity-background-scale-mode: scale-to-fit; + margin-left: 0; +} diff --git a/Editor/Window/Uxml/UxmlEditorProjectValidatorWindow.uxml b/Editor/Window/Uxml/UxmlEditorProjectValidatorWindow.uxml index f06f29c..2d1b0d0 100644 --- a/Editor/Window/Uxml/UxmlEditorProjectValidatorWindow.uxml +++ b/Editor/Window/Uxml/UxmlEditorProjectValidatorWindow.uxml @@ -4,6 +4,7 @@ + diff --git a/README.md b/README.md index 5690ed5..c4d1feb 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,50 @@ # Description -A tool to help validate data across scenes, prefabs and scriptable objects. +A tool to help validate data across scenes, prefabs, scriptable objects and assets. ![Editor Window](~Images/editor-window.png) -### Unity Windows +## Unity Windows ![Hierachy Window](~Images/editor-hierarchy-window.png) ![Project Window](~Images/editor-project-window.png) -## Game Object Validators +## Settings +![Project Settings](~Images/editor-project-settings.png) + + +## Validators + +### Asset Validators + +```csharp +public sealed class AssetValidatorMaterialTexture : IAssetValidator +{ + public void Validate(Material obj, List results) + { + if (obj.shader == null) + return; + + var count = obj.shader.GetPropertyCount(); + + for (var i = 0; i < count; i++) + { + var propertyType = obj.shader.GetPropertyType(i); + + if (propertyType != ShaderPropertyType.Texture) + continue; + + var propertyName = obj.shader.GetPropertyName(i); + var propertyValue = obj.GetTexture(propertyName); + + if (propertyValue == null) + results.Add(ValidatorResult.Create(EValidatorSeverity.Warning, $"Texture property '{propertyName}' is Null")); + } + } +} +``` + +### Game Object Validators ```csharp public sealed class GameObjectValidatorBrokenPrefab : IGameObjectValidator @@ -22,7 +57,7 @@ public sealed class GameObjectValidatorBrokenPrefab : IGameObjectValidator } ``` -## Component Validators +### Component Validators ```csharp public sealed class ComponentValidatorMeshCollider : IComponentValidator @@ -35,7 +70,7 @@ public sealed class ComponentValidatorMeshCollider : IComponentValidator where T : Object + { + void Validate(T obj, List results); + } +} \ No newline at end of file diff --git a/Runtime/Interfaces/IAssetValidator.cs.meta b/Runtime/Interfaces/IAssetValidator.cs.meta new file mode 100644 index 0000000..bf826d7 --- /dev/null +++ b/Runtime/Interfaces/IAssetValidator.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 24ee414b7fa14a368a48cab1b608ee3b +timeCreated: 1779624012 \ No newline at end of file diff --git a/package.json b/package.json index aaedccf..84141fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.module.project-validator", - "version": "0.6.0", + "version": "1.0.0", "displayName": "Module.ProjectValidator", "description": "", "unity": "6000.3", diff --git a/~Images/editor-project-settings.png b/~Images/editor-project-settings.png new file mode 100644 index 0000000000000000000000000000000000000000..00da999ce4e07147939ef81ac8ebb4f510c4d570 GIT binary patch literal 35163 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 U!Y;CMD8P^Wa}}9VDbx4=7h{=#i2wiq literal 0 HcmV?d00001