diff --git a/Unity/Assets/Scripts/Editor/NaughtyExtensions/ValidateTypePropertyValidator.cs b/Unity/Assets/Scripts/Editor/NaughtyExtensions/ValidateTypePropertyValidator.cs index 4da93cc0..af3580c1 100644 --- a/Unity/Assets/Scripts/Editor/NaughtyExtensions/ValidateTypePropertyValidator.cs +++ b/Unity/Assets/Scripts/Editor/NaughtyExtensions/ValidateTypePropertyValidator.cs @@ -6,11 +6,14 @@ using NaughtyAttributes.Editor; using SCHIZO.Attributes.Typing; using UnityEditor; +using UnityEngine.Profiling; namespace NaughtyExtensions { public class ValidateTypePropertyValidator : PropertyValidatorBase { + private static readonly Dictionary _changedCache = new Dictionary(); + public override void ValidateProperty(SerializedProperty property) { ValidateTypeAttribute attr = PropertyUtility.GetAttribute(property); @@ -20,14 +23,19 @@ public override void ValidateProperty(SerializedProperty property) NaughtyEditorGUI.HelpBox_Layout(warning, MessageType.Warning, context: property.serializedObject.targetObject); return; } + + if (_changedCache.TryGetValue(property, out object value) + && ReferenceEquals(property.objectReferenceValue, value)) return; + _changedCache[property] = property.objectReferenceValue; + if (property.objectReferenceValue == null) return; - Type expectedType = AccessTools.TypeByName(attr.typeName); + Type expectedType = ReflectionCache.GetType(attr.typeName); Type actualType = property.objectReferenceValue.GetType(); if (expectedType.IsAssignableFrom(actualType)) return; - List sisterTypes = actualType.GetCustomAttributes().Select(a => AccessTools.TypeByName(a.typeName)).ToList(); + List sisterTypes = actualType.GetCustomAttributes().Select(a => ReflectionCache.GetType(a.typeName)).ToList(); if (sisterTypes.Any(t => expectedType.IsAssignableFrom(t))) return; NaughtyEditorGUI.HelpBox_Layout($"{property.displayName} must be of type {attr.typeName}", MessageType.Error, context: property.serializedObject.targetObject); diff --git a/Unity/Assets/Scripts/Editor/Patches/NaughtyPatches.cs b/Unity/Assets/Scripts/Editor/Patches/NaughtyPatches.cs new file mode 100644 index 00000000..4d5ca463 --- /dev/null +++ b/Unity/Assets/Scripts/Editor/Patches/NaughtyPatches.cs @@ -0,0 +1,71 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Reflection; +using HarmonyLib; +using NaughtyAttributes.Editor; + +[HarmonyPatch] +public static class NaughtyPatches +{ + [HarmonyPatch(typeof(ReflectionUtility))] + public static class ReflectionPerfDetours + { + [HarmonyPatch(nameof(ReflectionUtility.GetField))] + [HarmonyPrefix] + public static bool FieldLookupPerfDetour(out FieldInfo __result, object target, string fieldName) + { + __result = null; + if (target != null) __result = ReflectionCache.GetField(target.GetType(), fieldName); + + return false; + } + + [HarmonyPatch(nameof(ReflectionUtility.GetMethod))] + [HarmonyPrefix] + public static bool MethodLookupPerfDetour(out MethodInfo __result, object target, string methodName) + { + __result = null; + if (target != null) __result = ReflectionCache.GetMethod(target.GetType(), methodName); + + return false; + } + + [HarmonyPatch(nameof(ReflectionUtility.GetProperty))] + [HarmonyPrefix] + public static bool PropertyLookupPerfDetour(out PropertyInfo __result, object target, string propertyName) + { + __result = null; + if (target != null) __result = ReflectionCache.GetProperty(target.GetType(), propertyName); + + return false; + } + + [HarmonyPatch("GetSelfAndBaseTypes")] + [HarmonyPrefix] + public static bool TypeHierarchyPerfDetour(out List __result, object target) + { + __result = WalkTypeHierarchy(target?.GetType()).ToList(); + return false; + } + public static IEnumerable WalkTypeHierarchy(Type leaf) + { + for (Type curr = leaf; curr != null; curr = curr.BaseType) + yield return curr; + } + } + + [HarmonyPatch(typeof(NaughtyInspector), "OnEnable")] + [HarmonyPostfix] + public static void EnumerablePerfDetour(NaughtyInspector __instance, + ref IEnumerable ____nonSerializedFields, + ref IEnumerable ____nativeProperties, + ref IEnumerable ____methods) + { + if (!__instance.target) return; + + ____nonSerializedFields = ____nonSerializedFields.ToList(); + ____nativeProperties = ____nativeProperties.ToList(); + ____methods = ____methods.ToList(); + } +} diff --git a/Unity/Assets/Scripts/Editor/Patches/NaughtyPatches.cs.meta b/Unity/Assets/Scripts/Editor/Patches/NaughtyPatches.cs.meta new file mode 100644 index 00000000..a5459c21 --- /dev/null +++ b/Unity/Assets/Scripts/Editor/Patches/NaughtyPatches.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f03ff73a61240f94eacfc99f6748d41f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity/Assets/Scripts/Editor/PropertyDrawers/ExposedTypeAttributeDrawer.cs b/Unity/Assets/Scripts/Editor/PropertyDrawers/ExposedTypeAttributeDrawer.cs index 836931b5..9f370d20 100644 --- a/Unity/Assets/Scripts/Editor/PropertyDrawers/ExposedTypeAttributeDrawer.cs +++ b/Unity/Assets/Scripts/Editor/PropertyDrawers/ExposedTypeAttributeDrawer.cs @@ -15,9 +15,9 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten position = EditorGUI.PrefixLabel(position, DrawerUtils.ControlId(property.propertyPath + "label", position), label); string targetTypeName = ((ExposedTypeAttribute) attribute).typeName; - Type actualFieldType = AccessTools.Field(property.serializedObject.targetObject.GetType(), property.name).FieldType; + Type actualFieldType = ReflectionCache.GetField(property.serializedObject.targetObject.GetType(), property.name).FieldType; - if (!(AccessTools.TypeByName(targetTypeName) is Type targetType)) + if (!(ReflectionCache.GetType(targetTypeName) is Type targetType)) { GUI.Label(position, "Unknown target type"); } diff --git a/Unity/Assets/Scripts/Editor/ReflectionCache.cs b/Unity/Assets/Scripts/Editor/ReflectionCache.cs new file mode 100644 index 00000000..88f8fa1f --- /dev/null +++ b/Unity/Assets/Scripts/Editor/ReflectionCache.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using HarmonyLib; +using NaughtyAttributes.Editor; + +public static class ReflectionCache +{ + private class Cache + { + private readonly Func getValueFunc; + private readonly Dictionary _cache = new Dictionary(); + public Cache(Func getValueFunc) + { + this.getValueFunc = getValueFunc; + } + public TValue GetCached(TKey key) + { + if (!_cache.TryGetValue(key, out TValue value)) + value = _cache[key] = getValueFunc(key); + return value; + } + } + private static readonly Cache _types = new Cache(typeName => AccessTools.TypeByName(typeName)); + private static readonly Cache> _allFields = new Cache>( + type => NaughtyPatches.ReflectionPerfDetours.WalkTypeHierarchy(type) + .SelectMany(t => t.GetFields(AccessTools.all)) + .ToList() + ); + private static readonly Cache<(Type, string), FieldInfo> _fieldByName = new Cache<(Type, string), FieldInfo>( + pair => NaughtyPatches.ReflectionPerfDetours.WalkTypeHierarchy(pair.Item1) + .Select(t => t.GetField(pair.Item2, AccessTools.all)) + .FirstOrDefault(f => f != null) + ); + private static readonly Cache> _allMethods = new Cache>( + type => NaughtyPatches.ReflectionPerfDetours.WalkTypeHierarchy(type) + .SelectMany(t => t.GetMethods(AccessTools.all)) + .ToList() + ); + private static readonly Cache<(Type, string), MethodInfo> _methodByName = new Cache<(Type, string), MethodInfo>( + pair => NaughtyPatches.ReflectionPerfDetours.WalkTypeHierarchy(pair.Item1) + .Select(t => t.GetMethod(pair.Item2, AccessTools.all)) + .FirstOrDefault(f => f != null) + ); + private static readonly Cache> _allProperties = new Cache>( + type => NaughtyPatches.ReflectionPerfDetours.WalkTypeHierarchy(type) + .SelectMany(t => t.GetProperties(AccessTools.all)) + .ToList() + ); + private static readonly Cache<(Type, string), PropertyInfo> _propertyByName = new Cache<(Type, string), PropertyInfo>( + pair => NaughtyPatches.ReflectionPerfDetours.WalkTypeHierarchy(pair.Item1) + .Select(t => t.GetProperty(pair.Item2, AccessTools.all)) + .FirstOrDefault(f => f != null) + ); + private static readonly Cache> _memberAttrs = new Cache>(member => member.GetCustomAttributes().ToList()); + + public static Type GetType(string typeName) => _types.GetCached(typeName); + public static List GetAllFields(Type type) => _allFields.GetCached(type); + public static FieldInfo GetField(Type type, string name) => _fieldByName.GetCached((type, name)); + public static List GetAllMethods(Type type) => _allMethods.GetCached(type); + public static MethodInfo GetMethod(Type type, string name) => _methodByName.GetCached((type, name)); + public static List GetAllProperties(Type type) => _allProperties.GetCached(type); + public static PropertyInfo GetProperty(Type type, string name) => _propertyByName.GetCached((type, name)); + + public static List GetCustomAttributes(MemberInfo member) => _memberAttrs.GetCached(member); +} diff --git a/Unity/Assets/Scripts/Editor/ReflectionCache.cs.meta b/Unity/Assets/Scripts/Editor/ReflectionCache.cs.meta new file mode 100644 index 00000000..a48640fa --- /dev/null +++ b/Unity/Assets/Scripts/Editor/ReflectionCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ed85d6b2a294d044f96486068f4b1568 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: