From d11165190a921225afa453d97f26de7ee04871fb Mon Sep 17 00:00:00 2001 From: Govorunb Date: Tue, 17 Oct 2023 20:26:55 +1100 Subject: [PATCH 1/2] improve editor performance --- .../ValidateTypePropertyValidator.cs | 42 +++++++++++----- .../Scripts/Editor/Patches/NaughtyPatches.cs | 50 +++++++++++++++++++ .../Editor/Patches/NaughtyPatches.cs.meta | 11 ++++ .../ExposedTypeAttributeDrawer.cs | 4 +- .../Assets/Scripts/Editor/ReflectionCache.cs | 37 ++++++++++++++ .../Scripts/Editor/ReflectionCache.cs.meta | 11 ++++ 6 files changed, 140 insertions(+), 15 deletions(-) create mode 100644 Unity/Assets/Scripts/Editor/Patches/NaughtyPatches.cs create mode 100644 Unity/Assets/Scripts/Editor/Patches/NaughtyPatches.cs.meta create mode 100644 Unity/Assets/Scripts/Editor/ReflectionCache.cs create mode 100644 Unity/Assets/Scripts/Editor/ReflectionCache.cs.meta diff --git a/Unity/Assets/Scripts/Editor/NaughtyExtensions/ValidateTypePropertyValidator.cs b/Unity/Assets/Scripts/Editor/NaughtyExtensions/ValidateTypePropertyValidator.cs index 4da93cc0..e439a56f 100644 --- a/Unity/Assets/Scripts/Editor/NaughtyExtensions/ValidateTypePropertyValidator.cs +++ b/Unity/Assets/Scripts/Editor/NaughtyExtensions/ValidateTypePropertyValidator.cs @@ -6,31 +6,47 @@ 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); - if (property.propertyType != SerializedPropertyType.ObjectReference) + Profiler.BeginSample($"{nameof(ValidateTypePropertyValidator)}.{nameof(ValidateProperty)}"); + Inner(); + Profiler.EndSample(); + + // no, this does not allocate + void Inner() { - string warning = attr.GetType().Name + " works only on reference types"; - NaughtyEditorGUI.HelpBox_Layout(warning, MessageType.Warning, context: property.serializedObject.targetObject); - return; - } - if (property.objectReferenceValue == null) return; + ValidateTypeAttribute attr = PropertyUtility.GetAttribute(property); + if (property.propertyType != SerializedPropertyType.ObjectReference) + { + string warning = attr.GetType().Name + " works only on reference types"; + NaughtyEditorGUI.HelpBox_Layout(warning, MessageType.Warning, context: property.serializedObject.targetObject); + return; + } - Type expectedType = AccessTools.TypeByName(attr.typeName); + if (_changedCache.TryGetValue(property, out object value) + && ReferenceEquals(property.objectReferenceValue, value)) return; + _changedCache[property] = property.objectReferenceValue; - Type actualType = property.objectReferenceValue.GetType(); - if (expectedType.IsAssignableFrom(actualType)) return; + if (property.objectReferenceValue == null) return; - List sisterTypes = actualType.GetCustomAttributes().Select(a => AccessTools.TypeByName(a.typeName)).ToList(); - if (sisterTypes.Any(t => expectedType.IsAssignableFrom(t))) return; + Type expectedType = ReflectionCache.GetType(attr.typeName); - NaughtyEditorGUI.HelpBox_Layout($"{property.displayName} must be of type {attr.typeName}", MessageType.Error, context: property.serializedObject.targetObject); + Type actualType = property.objectReferenceValue.GetType(); + if (expectedType.IsAssignableFrom(actualType)) return; + + 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..3f182c77 --- /dev/null +++ b/Unity/Assets/Scripts/Editor/Patches/NaughtyPatches.cs @@ -0,0 +1,50 @@ +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 = target?.GetType().GetField(fieldName, AccessTools.all); + + 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..fb911f00 100644 --- a/Unity/Assets/Scripts/Editor/PropertyDrawers/ExposedTypeAttributeDrawer.cs +++ b/Unity/Assets/Scripts/Editor/PropertyDrawers/ExposedTypeAttributeDrawer.cs @@ -1,4 +1,4 @@ -using System; +using System; using HarmonyLib; using SCHIZO.Attributes.Typing; using UnityEditor; @@ -17,7 +17,7 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten string targetTypeName = ((ExposedTypeAttribute) attribute).typeName; Type actualFieldType = AccessTools.Field(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..4eb41266 --- /dev/null +++ b/Unity/Assets/Scripts/Editor/ReflectionCache.cs @@ -0,0 +1,37 @@ +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 => type.GetFields(AccessTools.all).ToList()); + private static readonly Cache<(Type, string), FieldInfo> _fieldByName = new Cache<(Type, string), FieldInfo>(pair => pair.Item1.GetField(pair.Item2, AccessTools.all)); + private static readonly Cache> _methods = new Cache>(type => type.GetMethods(AccessTools.all).ToList()); + 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 GetMethods(Type type) => _methods.GetCached(type); + 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: From 3337a01f644e8aecd31c6ade8e1240f090a10257 Mon Sep 17 00:00:00 2001 From: Govorunb Date: Tue, 17 Oct 2023 21:30:02 +1100 Subject: [PATCH 2/2] a bit more caching remove profiler calls (did not mean to keep those) --- .../ValidateTypePropertyValidator.cs | 40 ++++++++----------- .../Scripts/Editor/Patches/NaughtyPatches.cs | 23 ++++++++++- .../ExposedTypeAttributeDrawer.cs | 4 +- .../Assets/Scripts/Editor/ReflectionCache.cs | 40 ++++++++++++++++--- 4 files changed, 75 insertions(+), 32 deletions(-) diff --git a/Unity/Assets/Scripts/Editor/NaughtyExtensions/ValidateTypePropertyValidator.cs b/Unity/Assets/Scripts/Editor/NaughtyExtensions/ValidateTypePropertyValidator.cs index e439a56f..af3580c1 100644 --- a/Unity/Assets/Scripts/Editor/NaughtyExtensions/ValidateTypePropertyValidator.cs +++ b/Unity/Assets/Scripts/Editor/NaughtyExtensions/ValidateTypePropertyValidator.cs @@ -16,37 +16,29 @@ public class ValidateTypePropertyValidator : PropertyValidatorBase public override void ValidateProperty(SerializedProperty property) { - Profiler.BeginSample($"{nameof(ValidateTypePropertyValidator)}.{nameof(ValidateProperty)}"); - Inner(); - Profiler.EndSample(); - - // no, this does not allocate - void Inner() + ValidateTypeAttribute attr = PropertyUtility.GetAttribute(property); + if (property.propertyType != SerializedPropertyType.ObjectReference) { - ValidateTypeAttribute attr = PropertyUtility.GetAttribute(property); - if (property.propertyType != SerializedPropertyType.ObjectReference) - { - string warning = attr.GetType().Name + " works only on reference types"; - NaughtyEditorGUI.HelpBox_Layout(warning, MessageType.Warning, context: property.serializedObject.targetObject); - return; - } + string warning = attr.GetType().Name + " works only on reference types"; + 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 (_changedCache.TryGetValue(property, out object value) + && ReferenceEquals(property.objectReferenceValue, value)) return; + _changedCache[property] = property.objectReferenceValue; - if (property.objectReferenceValue == null) return; + if (property.objectReferenceValue == null) return; - Type expectedType = ReflectionCache.GetType(attr.typeName); + Type expectedType = ReflectionCache.GetType(attr.typeName); - Type actualType = property.objectReferenceValue.GetType(); - if (expectedType.IsAssignableFrom(actualType)) return; + Type actualType = property.objectReferenceValue.GetType(); + if (expectedType.IsAssignableFrom(actualType)) return; - List sisterTypes = actualType.GetCustomAttributes().Select(a => ReflectionCache.GetType(a.typeName)).ToList(); - if (sisterTypes.Any(t => expectedType.IsAssignableFrom(t))) return; + 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); - } + 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 index 3f182c77..4d5ca463 100644 --- a/Unity/Assets/Scripts/Editor/Patches/NaughtyPatches.cs +++ b/Unity/Assets/Scripts/Editor/Patches/NaughtyPatches.cs @@ -15,7 +15,28 @@ public static class ReflectionPerfDetours [HarmonyPrefix] public static bool FieldLookupPerfDetour(out FieldInfo __result, object target, string fieldName) { - __result = target?.GetType().GetField(fieldName, AccessTools.all); + __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; } diff --git a/Unity/Assets/Scripts/Editor/PropertyDrawers/ExposedTypeAttributeDrawer.cs b/Unity/Assets/Scripts/Editor/PropertyDrawers/ExposedTypeAttributeDrawer.cs index fb911f00..9f370d20 100644 --- a/Unity/Assets/Scripts/Editor/PropertyDrawers/ExposedTypeAttributeDrawer.cs +++ b/Unity/Assets/Scripts/Editor/PropertyDrawers/ExposedTypeAttributeDrawer.cs @@ -1,4 +1,4 @@ -using System; +using System; using HarmonyLib; using SCHIZO.Attributes.Typing; using UnityEditor; @@ -15,7 +15,7 @@ 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 (!(ReflectionCache.GetType(targetTypeName) is Type targetType)) { diff --git a/Unity/Assets/Scripts/Editor/ReflectionCache.cs b/Unity/Assets/Scripts/Editor/ReflectionCache.cs index 4eb41266..88f8fa1f 100644 --- a/Unity/Assets/Scripts/Editor/ReflectionCache.cs +++ b/Unity/Assets/Scripts/Editor/ReflectionCache.cs @@ -23,15 +23,45 @@ public TValue GetCached(TKey key) } } private static readonly Cache _types = new Cache(typeName => AccessTools.TypeByName(typeName)); - private static readonly Cache> _allFields = new Cache>(type => type.GetFields(AccessTools.all).ToList()); - private static readonly Cache<(Type, string), FieldInfo> _fieldByName = new Cache<(Type, string), FieldInfo>(pair => pair.Item1.GetField(pair.Item2, AccessTools.all)); - private static readonly Cache> _methods = new Cache>(type => type.GetMethods(AccessTools.all).ToList()); + 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 GetMethods(Type type) => _methods.GetCached(type); + 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); }