Skip to content

Commit

Permalink
Merge pull request #35 from Govorunb/editor-perf
Browse files Browse the repository at this point in the history
Improve editor performance
  • Loading branch information
Alexejhero authored Oct 19, 2023
2 parents f35fbf2 + 3337a01 commit 76f2ec2
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<SerializedProperty, object> _changedCache = new Dictionary<SerializedProperty, object>();

public override void ValidateProperty(SerializedProperty property)
{
ValidateTypeAttribute attr = PropertyUtility.GetAttribute<ValidateTypeAttribute>(property);
Expand All @@ -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<Type> sisterTypes = actualType.GetCustomAttributes<ActualTypeAttribute>().Select(a => AccessTools.TypeByName(a.typeName)).ToList();
List<Type> sisterTypes = actualType.GetCustomAttributes<ActualTypeAttribute>().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);
Expand Down
71 changes: 71 additions & 0 deletions Unity/Assets/Scripts/Editor/Patches/NaughtyPatches.cs
Original file line number Diff line number Diff line change
@@ -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<Type> __result, object target)
{
__result = WalkTypeHierarchy(target?.GetType()).ToList();
return false;
}
public static IEnumerable<Type> 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<FieldInfo> ____nonSerializedFields,
ref IEnumerable<PropertyInfo> ____nativeProperties,
ref IEnumerable<MethodInfo> ____methods)
{
if (!__instance.target) return;

____nonSerializedFields = ____nonSerializedFields.ToList();
____nativeProperties = ____nativeProperties.ToList();
____methods = ____methods.ToList();
}
}
11 changes: 11 additions & 0 deletions Unity/Assets/Scripts/Editor/Patches/NaughtyPatches.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
67 changes: 67 additions & 0 deletions Unity/Assets/Scripts/Editor/ReflectionCache.cs
Original file line number Diff line number Diff line change
@@ -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<TKey, TValue>
{
private readonly Func<TKey, TValue> getValueFunc;
private readonly Dictionary<TKey, TValue> _cache = new Dictionary<TKey, TValue>();
public Cache(Func<TKey, TValue> 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<string, Type> _types = new Cache<string, Type>(typeName => AccessTools.TypeByName(typeName));
private static readonly Cache<Type, List<FieldInfo>> _allFields = new Cache<Type, List<FieldInfo>>(
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<Type, List<MethodInfo>> _allMethods = new Cache<Type, List<MethodInfo>>(
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<Type, List<PropertyInfo>> _allProperties = new Cache<Type, List<PropertyInfo>>(
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<MemberInfo, List<Attribute>> _memberAttrs = new Cache<MemberInfo, List<Attribute>>(member => member.GetCustomAttributes().ToList());

public static Type GetType(string typeName) => _types.GetCached(typeName);
public static List<FieldInfo> GetAllFields(Type type) => _allFields.GetCached(type);
public static FieldInfo GetField(Type type, string name) => _fieldByName.GetCached((type, name));
public static List<MethodInfo> GetAllMethods(Type type) => _allMethods.GetCached(type);
public static MethodInfo GetMethod(Type type, string name) => _methodByName.GetCached((type, name));
public static List<PropertyInfo> GetAllProperties(Type type) => _allProperties.GetCached(type);
public static PropertyInfo GetProperty(Type type, string name) => _propertyByName.GetCached((type, name));

public static List<Attribute> GetCustomAttributes(MemberInfo member) => _memberAttrs.GetCached(member);
}
11 changes: 11 additions & 0 deletions Unity/Assets/Scripts/Editor/ReflectionCache.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 76f2ec2

Please sign in to comment.