From a16b3e8f52cb2184e70522ea3740b8056b097787 Mon Sep 17 00:00:00 2001 From: uurhaa Date: Fri, 1 Mar 2024 05:01:16 +0100 Subject: [PATCH] Add SerializedPropertyExtensions.cs --- Assets/BetterExtensions/Editor.meta | 3 + .../Editor/BetterExtensions.Editor.asmdef | 19 + .../BetterExtensions.Editor.asmdef.meta | 3 + .../BetterExtensions/Editor/Extensions.meta | 3 + .../SerializedPropertyExtensions.cs | 535 ++++++++++++++++++ .../SerializedPropertyExtensions.cs.meta | 11 + Assets/BetterExtensions/Editor/Helpers.meta | 3 + .../Editor/Helpers/CacheKey.cs | 36 ++ .../Editor/Helpers/CacheKey.cs.meta | 11 + .../Editor/Helpers/FieldInfoCache.cs | 17 + .../Editor/Helpers/FieldInfoCache.cs.meta | 11 + .../Editor/Helpers/PropertyItemInfo.cs | 8 + .../Editor/Helpers/PropertyItemInfo.cs.meta | 11 + Assets/BetterExtensions/Editor/Utility.meta | 3 + .../Utility/SerializedPropertyUtility.cs | 126 +++++ .../Utility/SerializedPropertyUtility.cs.meta | 11 + Assets/BetterExtensions/package.json | 2 +- BetterExtensions.Editor.csproj.DotSettings | 5 + BetterExtensions.Runtime.csproj.DotSettings | 4 + 19 files changed, 821 insertions(+), 1 deletion(-) create mode 100644 Assets/BetterExtensions/Editor.meta create mode 100644 Assets/BetterExtensions/Editor/BetterExtensions.Editor.asmdef create mode 100644 Assets/BetterExtensions/Editor/BetterExtensions.Editor.asmdef.meta create mode 100644 Assets/BetterExtensions/Editor/Extensions.meta create mode 100644 Assets/BetterExtensions/Editor/Extensions/SerializedPropertyExtensions.cs create mode 100644 Assets/BetterExtensions/Editor/Extensions/SerializedPropertyExtensions.cs.meta create mode 100644 Assets/BetterExtensions/Editor/Helpers.meta create mode 100644 Assets/BetterExtensions/Editor/Helpers/CacheKey.cs create mode 100644 Assets/BetterExtensions/Editor/Helpers/CacheKey.cs.meta create mode 100644 Assets/BetterExtensions/Editor/Helpers/FieldInfoCache.cs create mode 100644 Assets/BetterExtensions/Editor/Helpers/FieldInfoCache.cs.meta create mode 100644 Assets/BetterExtensions/Editor/Helpers/PropertyItemInfo.cs create mode 100644 Assets/BetterExtensions/Editor/Helpers/PropertyItemInfo.cs.meta create mode 100644 Assets/BetterExtensions/Editor/Utility.meta create mode 100644 Assets/BetterExtensions/Editor/Utility/SerializedPropertyUtility.cs create mode 100644 Assets/BetterExtensions/Editor/Utility/SerializedPropertyUtility.cs.meta diff --git a/Assets/BetterExtensions/Editor.meta b/Assets/BetterExtensions/Editor.meta new file mode 100644 index 0000000..f81b5d1 --- /dev/null +++ b/Assets/BetterExtensions/Editor.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1c1deb99cdf948e68e9e1f5cf18e5356 +timeCreated: 1709259478 \ No newline at end of file diff --git a/Assets/BetterExtensions/Editor/BetterExtensions.Editor.asmdef b/Assets/BetterExtensions/Editor/BetterExtensions.Editor.asmdef new file mode 100644 index 0000000..741a00a --- /dev/null +++ b/Assets/BetterExtensions/Editor/BetterExtensions.Editor.asmdef @@ -0,0 +1,19 @@ +{ + "name": "BetterExtensions.Editor", + "rootNamespace": "Better.Extensions.EditorAddons", + "references": [ + "GUID:01df13aca8d01e24a911bcc3e8277031", + "GUID:28da8d3b12e3efa47928e0c9070f853d" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/BetterExtensions/Editor/BetterExtensions.Editor.asmdef.meta b/Assets/BetterExtensions/Editor/BetterExtensions.Editor.asmdef.meta new file mode 100644 index 0000000..ab07a2a --- /dev/null +++ b/Assets/BetterExtensions/Editor/BetterExtensions.Editor.asmdef.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1ac867a6259e45a1856740fe8f7623aa +timeCreated: 1709259502 \ No newline at end of file diff --git a/Assets/BetterExtensions/Editor/Extensions.meta b/Assets/BetterExtensions/Editor/Extensions.meta new file mode 100644 index 0000000..7138d50 --- /dev/null +++ b/Assets/BetterExtensions/Editor/Extensions.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3067a55d494f40a88d730aac4e5273bb +timeCreated: 1709260016 \ No newline at end of file diff --git a/Assets/BetterExtensions/Editor/Extensions/SerializedPropertyExtensions.cs b/Assets/BetterExtensions/Editor/Extensions/SerializedPropertyExtensions.cs new file mode 100644 index 0000000..f0a1e5f --- /dev/null +++ b/Assets/BetterExtensions/Editor/Extensions/SerializedPropertyExtensions.cs @@ -0,0 +1,535 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Better.Extensions.Runtime; +using Better.Internal.Core.Runtime; +using UnityEditor; +using UnityEngine; + +namespace Better.Extensions.EditorAddons +{ + public static class SerializedPropertyExtensions + { + private const string ScriptFieldName = "m_Script"; + private const string NativeObjectPtrName = "m_NativeObjectPtr"; + private const string NativePropertyPtrName = "m_NativePropertyPtr"; + private const string VerifyMethodName = "Verify"; + private static readonly MethodInfo VerifyMethod; + private static readonly FieldInfo PropertyPrtInfo; + private static readonly FieldInfo ObjectPrtInfo; + + static SerializedPropertyExtensions() + { + var serializedPropertyType = typeof(SerializedProperty); + VerifyMethod = serializedPropertyType.GetMethod(VerifyMethodName, Defines.FieldsFlags); + PropertyPrtInfo = serializedPropertyType.GetField(NativePropertyPtrName, Defines.FieldsFlags); + ObjectPrtInfo = typeof(SerializedObject).GetField(NativeObjectPtrName, Defines.FieldsFlags); + } + + public static Type GetManagedType(this SerializedProperty self) + { + if (self == null) + { + DebugUtility.LogException(nameof(self)); + return null; + } + +#if UNITY_2021_2_OR_NEWER + return self.managedReferenceValue?.GetType(); +#else + if (string.IsNullOrEmpty(self.managedReferenceFullTypename)) + { + return null; + } + + var split = self.managedReferenceFullTypename.Split(' '); + var assembly = GetAssembly(split[0]); + var currentValue = assembly.GetType(split[1]); + return currentValue; +#endif + } + + private static Assembly GetAssembly(string name) + { + return AppDomain.CurrentDomain.GetAssemblies() + .SingleOrDefault(assembly => assembly.GetName().Name == name); + } + + public static bool IsArrayElement(this SerializedProperty self) + { + if (self == null) + { + DebugUtility.LogException(nameof(self)); + return false; + } + + return self.propertyPath.EndsWith("]"); + } + + public static int GetArrayIndex(this SerializedProperty self) + { + if (self == null) + { + DebugUtility.LogException(nameof(self)); + return -1; + } + + var matches = SerializedPropertyUtility.ArrayIndexRegex.Matches(self.propertyPath); + if (matches.Count > 0) + { + if (int.TryParse(matches[matches.Count - 1].Name, out var result)) + { + return result; + } + } + + return -1; + } + + public static CachedFieldInfo GetFieldInfoAndStaticTypeFromProperty(this SerializedProperty self) + { + if (self == null) + { + DebugUtility.LogException(nameof(self)); + return null; + } + + var classType = GetScriptTypeFromProperty(self); + if (classType == null) + { + return null; + } + + var fieldPath = self.propertyPath; + if (self.propertyType == SerializedPropertyType.ManagedReference) + { + var objectTypename = self.managedReferenceFullTypename; + if (!SerializedPropertyUtility.GetTypeFromManagedReferenceFullTypeName(objectTypename, out classType)) + { + return null; + } + + fieldPath = self.propertyPath; + } + + if (classType == null) + { + return null; + } + + return SerializedPropertyUtility.GetFieldInfoFromPropertyPath(classType, fieldPath); + } + + public static Type GetScriptTypeFromProperty(this SerializedProperty self) + { + if (self == null) + { + DebugUtility.LogException(nameof(self)); + return null; + } + + if (self.serializedObject.targetObject != null) + return self.serializedObject.targetObject.GetType(); + + // Fallback in case the targetObject has been destroyed but the property is still valid. + var scriptProp = self.serializedObject.FindProperty(ScriptFieldName); + + if (scriptProp == null) + return null; + + var script = scriptProp.objectReferenceValue as MonoScript; + + return script == null ? null : script.GetClass(); + } + + public static List GetAttributes(this SerializedProperty self, bool inherit = false) where TAttributes : Attribute + { + if (self == null) + { + DebugUtility.LogException(nameof(self)); + return new List(); + } + + var fieldInfo = self.GetFieldInfoAndStaticTypeFromProperty(); + if (fieldInfo == null) return null; + var attributes = fieldInfo.FieldInfo.GetCustomAttributes(inherit); + return attributes.ToList(); + } + + public static string GetPropertyParentList(this SerializedProperty self) + { + if (self == null) + { + DebugUtility.LogException(nameof(self)); + return string.Empty; + } + + string propertyPath = self.propertyPath; + return SerializedPropertyUtility.GetPropertyParentList(propertyPath); + } + + public static string GetArrayNameFromPath(this SerializedProperty self) + { + if (self == null) + { + DebugUtility.LogException(nameof(self)); + return string.Empty; + } + + return SerializedPropertyUtility.ArrayDataWithIndexRegex.Replace(self.propertyPath, ""); + } + + public static string GetArrayPath(this SerializedProperty self) + { + if (self == null) + { + DebugUtility.LogException(nameof(self)); + return string.Empty; + } + + return SerializedPropertyUtility.ArrayRegex.Replace(self.propertyPath, ""); + } + + public static bool IsDisposed(this SerializedObject self) + { + if (self == null) + { + DebugUtility.LogException(nameof(self)); + return true; + } + + try + { + if (ObjectPrtInfo != null) + { + var objectPrt = (IntPtr)ObjectPrtInfo.GetValue(self); + return objectPrt == IntPtr.Zero; + } + } + catch + { + return true; + } + + return true; + } + + public static bool IsDisposed(this SerializedProperty self) + { + if (self == null) + { + DebugUtility.LogException(nameof(self)); + return true; + } + + if (self.serializedObject == null) + { + return false; + } + + try + { + if (PropertyPrtInfo != null && ObjectPrtInfo != null) + { + var propertyPrt = (IntPtr)PropertyPrtInfo.GetValue(self); + var objectPrt = (IntPtr)ObjectPrtInfo.GetValue(self.serializedObject); + return propertyPrt == IntPtr.Zero || objectPrt == IntPtr.Zero; + } + } + catch + { + return true; + } + + return true; + } + + public static bool Verify(this SerializedProperty self) + { + if (self == null) + { + DebugUtility.LogException(nameof(self)); + return false; + } + + if (self.serializedObject == null) + { + return false; + } + + + try + { + if (VerifyMethod != null) + { + VerifyMethod.Invoke(self, new object[] { SerializedPropertyUtility.IteratorNotAtEnd }); + return true; + } + } + catch + { + return false; + } + + return false; + } + + public static bool IsTargetComponent(this SerializedProperty self, out Component component) + { + if (self == null) + { + DebugUtility.LogException(nameof(self)); + component = null; + return false; + } + + if (self.serializedObject.targetObject is Component inner) + { + component = inner; + return true; + } + + component = null; + return false; + } + + //https://gist.github.com/aholkner/214628a05b15f0bb169660945ac7923b + + public static object GetValue(this SerializedProperty self) + { + if (self == null) + { + DebugUtility.LogException(nameof(self)); + return null; + } + + string propertyPath = self.propertyPath; + object value = self.serializedObject.targetObject; + int i = 0; + while (NextPathComponent(propertyPath, ref i, out var token)) + value = GetPathComponentValue(value, token); + return value; + } + + public static void SetValue(this SerializedProperty self, object value) + { + Undo.RecordObject(self.serializedObject.targetObject, $"Set {self.name}"); + + SetValueNoRecord(self, value); + + EditorUtility.SetDirty(self.serializedObject.targetObject); + self.serializedObject.ApplyModifiedProperties(); + } + + public static void SetValueNoRecord(this SerializedProperty self, object value) + { + if (self == null) + { + DebugUtility.LogException(nameof(self)); + return; + } + + var container = GetPropertyContainer(self, out var deferredToken); + + SetPathComponentValue(container, deferredToken, value); + } + + public static object GetPropertyContainer(this SerializedProperty self) + { + if (self == null) + { + DebugUtility.LogException(nameof(self)); + return null; + } + + return GetPropertyContainer(self, out _); + } + + public static object GetLastNonCollectionContainer(this SerializedProperty self) + { + if (self == null) + { + DebugUtility.LogException(nameof(self)); + return null; + } + + var containers = self.GetPropertyContainers(); + for (var index = containers.Count - 1; index >= 0; index--) + { + var container = containers[index]; + if (container.GetType().IsEnumerable()) continue; + return container; + } + + return containers.FirstOrDefault(); + } + + public static List GetPropertyContainers(this SerializedProperty self) + { + if (self == null) + { + DebugUtility.LogException(nameof(self)); + return new List(); + } + + string propertyPath = self.propertyPath; + object container = self.serializedObject.targetObject; + + int i = 0; + PropertyItemInfo deferredToken; + var list = new List(); + list.Add(container); + NextPathComponent(propertyPath, ref i, out deferredToken); + while (NextPathComponent(propertyPath, ref i, out var token)) + { + container = GetPathComponentValue(container, deferredToken); + deferredToken = token; + list.Add(container); + } + + if (container.GetType().IsValueType) + { + var message = + $"Cannot use SerializedObject.SetValue on a struct object, as the result will be set on a temporary. Either change {container.GetType().Name} to a class, or use SetValue with a parent member."; + Debug.LogWarning(message); + } + + return list; + } + + private static object GetPropertyContainer(SerializedProperty self, out PropertyItemInfo deferredToken) + { + string propertyPath = self.propertyPath; + object container = self.serializedObject.targetObject; + + int i = 0; + NextPathComponent(propertyPath, ref i, out deferredToken); + while (NextPathComponent(propertyPath, ref i, out var token)) + { + container = GetPathComponentValue(container, deferredToken); + deferredToken = token; + } + + return container; + } + + private static bool NextPathComponent(string propertyPath, ref int index, out PropertyItemInfo component) + { + component = new PropertyItemInfo(); + + if (index >= propertyPath.Length) + return false; + + var arrayElementMatch = SerializedPropertyUtility.ArrayElementRegex.Match(propertyPath, index); + if (arrayElementMatch.Success) + { + index += arrayElementMatch.Length + 1; // Skip past next '.' + component.ElementIndex = int.Parse(arrayElementMatch.Groups[1].Value); + return true; + } + + int dot = propertyPath.IndexOf('.', index); + if (dot == -1) + { + component.PropertyName = propertyPath.Substring(index); + index = propertyPath.Length; + } + else + { + component.PropertyName = propertyPath.Substring(index, dot - index); + index = dot + 1; // Skip past next '.' + } + + return true; + } + + private static object GetPathComponentValue(object container, PropertyItemInfo component) + { + if (component.PropertyName == null) + return ((IList)container)[component.ElementIndex]; + + return GetMemberValue(container, component.PropertyName); + } + + private static void SetPathComponentValue(object container, PropertyItemInfo component, object value) + { + if (component.PropertyName == null) + ((IList)container)[component.ElementIndex] = value; + else + SetMemberValue(container, component.PropertyName, value); + } + + private static object GetMemberValue(object container, string name) + { + if (container == null) + return null; + var type = container.GetType(); + var members = TraverseBaseClasses(type, name); + for (int i = 0; i < members.Count; ++i) + { + if (members[i] is FieldInfo field) + return field.GetValue(container); + + if (members[i] is PropertyInfo property) + return property.GetValue(container); + } + + return null; + } + + private static List TraverseBaseClasses(Type currentType, string name) + { + var memberInfos = new List(); + + var currentTypeFields = currentType.GetMember(name, Defines.FieldsFlags); + memberInfos.AddRange(currentTypeFields); + + var baseType = currentType.BaseType; + + if (baseType == null) return memberInfos; + var baseTypeFields = baseType.GetMember(name, Defines.FieldsFlags); + memberInfos.AddRange(baseTypeFields); + + memberInfos.AddRange(TraverseBaseClasses(baseType, name)); // Recursively traverse the base classes + + return memberInfos; + } + + private static void SetMemberValue(object container, string name, object value) + { + var type = container.GetType(); + var members = type.GetMember(name, Defines.FieldsFlags); + for (int i = 0; i < members.Length; ++i) + { + if (members[i] is FieldInfo field) + { + if (!type.IsValueType && !type.IsEnum) + { + field.SetValue(container, value); + } + else + { + field.SetValueDirect(__makeref(container), value); + } + + return; + } + + if (members[i] is PropertyInfo property) + { + if (!type.IsValueType && !type.IsEnum) + { + property.SetValue(container, value); + } + + return; + } + } + + var message = $"Failed to set member {container}.{name} via reflection"; + Debug.LogWarning(message); + } + } +} \ No newline at end of file diff --git a/Assets/BetterExtensions/Editor/Extensions/SerializedPropertyExtensions.cs.meta b/Assets/BetterExtensions/Editor/Extensions/SerializedPropertyExtensions.cs.meta new file mode 100644 index 0000000..af267bb --- /dev/null +++ b/Assets/BetterExtensions/Editor/Extensions/SerializedPropertyExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4caf630c44a1e5e4c9ff59e6086bbc1f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/BetterExtensions/Editor/Helpers.meta b/Assets/BetterExtensions/Editor/Helpers.meta new file mode 100644 index 0000000..402fef4 --- /dev/null +++ b/Assets/BetterExtensions/Editor/Helpers.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 51154586f3b84016a2b68bcf6dd0ae69 +timeCreated: 1709260030 \ No newline at end of file diff --git a/Assets/BetterExtensions/Editor/Helpers/CacheKey.cs b/Assets/BetterExtensions/Editor/Helpers/CacheKey.cs new file mode 100644 index 0000000..5c628ff --- /dev/null +++ b/Assets/BetterExtensions/Editor/Helpers/CacheKey.cs @@ -0,0 +1,36 @@ +using System; +using Better.Extensions.Runtime; + +namespace Better.Extensions.EditorAddons +{ + public struct CacheKey : IEquatable + { + private readonly Type _type; + private readonly string _propertyPath; + + public CacheKey(Type type, string propertyPath) + { + _type = type; + _propertyPath = propertyPath; + } + + public bool Equals(CacheKey other) + { + return _type == other._type && _propertyPath.CompareOrdinal(other._propertyPath); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is CacheKey key && Equals(key); + } + + public override int GetHashCode() + { + unchecked + { + return ((_type != null ? _type.GetHashCode() : 0) * 397) ^ (_propertyPath != null ? _propertyPath.GetHashCode() : 0); + } + } + } +} \ No newline at end of file diff --git a/Assets/BetterExtensions/Editor/Helpers/CacheKey.cs.meta b/Assets/BetterExtensions/Editor/Helpers/CacheKey.cs.meta new file mode 100644 index 0000000..e939bd5 --- /dev/null +++ b/Assets/BetterExtensions/Editor/Helpers/CacheKey.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f010485a16537de4fa7d3752da6c2076 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/BetterExtensions/Editor/Helpers/FieldInfoCache.cs b/Assets/BetterExtensions/Editor/Helpers/FieldInfoCache.cs new file mode 100644 index 0000000..ffb32c0 --- /dev/null +++ b/Assets/BetterExtensions/Editor/Helpers/FieldInfoCache.cs @@ -0,0 +1,17 @@ +using System; +using System.Reflection; + +namespace Better.Extensions.EditorAddons +{ + public class CachedFieldInfo + { + public FieldInfo FieldInfo { get; } + public Type Type { get; } + + public CachedFieldInfo(FieldInfo fieldInfo, Type type) + { + FieldInfo = fieldInfo; + Type = type; + } + } +} \ No newline at end of file diff --git a/Assets/BetterExtensions/Editor/Helpers/FieldInfoCache.cs.meta b/Assets/BetterExtensions/Editor/Helpers/FieldInfoCache.cs.meta new file mode 100644 index 0000000..2e2a6fa --- /dev/null +++ b/Assets/BetterExtensions/Editor/Helpers/FieldInfoCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6f9422a2cb69f7a438d85eede2522837 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/BetterExtensions/Editor/Helpers/PropertyItemInfo.cs b/Assets/BetterExtensions/Editor/Helpers/PropertyItemInfo.cs new file mode 100644 index 0000000..1478188 --- /dev/null +++ b/Assets/BetterExtensions/Editor/Helpers/PropertyItemInfo.cs @@ -0,0 +1,8 @@ +namespace Better.Extensions.EditorAddons +{ + public struct PropertyItemInfo + { + public string PropertyName { get; set; } + public int ElementIndex { get; set; } + } +} \ No newline at end of file diff --git a/Assets/BetterExtensions/Editor/Helpers/PropertyItemInfo.cs.meta b/Assets/BetterExtensions/Editor/Helpers/PropertyItemInfo.cs.meta new file mode 100644 index 0000000..f53d637 --- /dev/null +++ b/Assets/BetterExtensions/Editor/Helpers/PropertyItemInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d08fc8f80709ae84da7b30cd657c4205 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/BetterExtensions/Editor/Utility.meta b/Assets/BetterExtensions/Editor/Utility.meta new file mode 100644 index 0000000..ab0465f --- /dev/null +++ b/Assets/BetterExtensions/Editor/Utility.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b7ec40bd603d4315a9eb745e89ab64ab +timeCreated: 1709260024 \ No newline at end of file diff --git a/Assets/BetterExtensions/Editor/Utility/SerializedPropertyUtility.cs b/Assets/BetterExtensions/Editor/Utility/SerializedPropertyUtility.cs new file mode 100644 index 0000000..5a1fc2c --- /dev/null +++ b/Assets/BetterExtensions/Editor/Utility/SerializedPropertyUtility.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text.RegularExpressions; +using Better.Extensions.Runtime; +using Better.Internal.Core.Runtime; +using UnityEditor.Callbacks; + +namespace Better.Extensions.EditorAddons +{ + public static class SerializedPropertyUtility + { + public static readonly Regex ArrayDataWithIndexRegex = new Regex(@"\.Array\.data\[[0-9]+\]", RegexOptions.Compiled); + public static readonly Regex ArrayDataWithIndexRegexAny = new Regex(@"\.Array\.data\[[0-9]+\]$", RegexOptions.Compiled); + + public static readonly Regex ArrayElementRegex = new Regex(@"\GArray\.data\[(\d+)\]", RegexOptions.Compiled); + public static readonly Regex ArrayIndexRegex = new Regex(@"\[([^\[\]]*)\]", RegexOptions.Compiled); + + public static readonly Regex ArrayRegex = new Regex(@"\.Array\.data", RegexOptions.Compiled); + + public const int IteratorNotAtEnd = 2; + public const string ArrayDataName = ".Array.data["; + private const string ArrayElementDotName = "." + ArrayElementName; + private const string ArrayElementName = "___ArrayElement___"; + + private static readonly Dictionary FieldInfoFromPropertyPathCache = new Dictionary(); + + [DidReloadScripts] + private static void Reload() + { + FieldInfoFromPropertyPathCache.Clear(); + } + + public static string GetPropertyParentList(string propertyPath) + { + if (propertyPath.IsNullOrEmpty()) + { + DebugUtility.LogException(nameof(propertyPath)); + return string.Empty; + } + + int length = propertyPath.LastIndexOf(ArrayDataName, StringComparison.Ordinal); + return length < 0 ? null : propertyPath.Substring(0, length); + } + + public static bool GetTypeFromManagedReferenceFullTypeName(string managedReferenceFullTypename, out Type managedReferenceInstanceType) + { + if (managedReferenceFullTypename.IsNullOrEmpty()) + { + DebugUtility.LogException(nameof(managedReferenceFullTypename)); + managedReferenceInstanceType = null; + return false; + } + + var parts = managedReferenceFullTypename.Split(' '); + if (parts.Length == 2) + { + var assemblyPart = parts[0]; + var classNamePart = parts[1]; + managedReferenceInstanceType = Type.GetType($"{classNamePart}, {assemblyPart}"); + } + + managedReferenceInstanceType = null; + return managedReferenceInstanceType != null; + } + + public static CachedFieldInfo GetFieldInfoFromPropertyPath(Type type, string propertyPath) + { + var cache = new CacheKey(type, propertyPath); + + if (FieldInfoFromPropertyPathCache.TryGetValue(cache, out var fieldInfoCache)) + { + return fieldInfoCache; + } + + var arrayElement = ArrayDataWithIndexRegexAny.IsMatch(propertyPath); + propertyPath = ArrayDataWithIndexRegex.Replace(propertyPath, ArrayElementDotName); + + if (FieldInfoFromPropertyPath(propertyPath, ref type, out var fieldInfo)) + { + return null; + } + + if (arrayElement && type != null && type.IsArrayOrList()) + { + type = type.GetCollectionElementType(); + } + + fieldInfoCache = new CachedFieldInfo(fieldInfo, type); + FieldInfoFromPropertyPathCache.Add(cache, fieldInfoCache); + return fieldInfoCache; + } + + private static bool FieldInfoFromPropertyPath(string propertyPath, ref Type type, out FieldInfo fieldInfo) + { + var originalType = type; + fieldInfo = null; + var parts = propertyPath.Split('.'); + for (var i = 0; i < parts.Length; i++) + { + var member = parts[i]; + FieldInfo foundField = null; + for (var currentType = type; foundField == null && currentType != null; currentType = currentType.BaseType) + { + foundField = currentType.GetField(member, Defines.ConstructorFlags); + } + + if (foundField == null) + { + var cacheKey = new CacheKey(originalType, propertyPath); + FieldInfoFromPropertyPathCache.Add(cacheKey, null); + return true; + } + + fieldInfo = foundField; + type = fieldInfo.FieldType; + + if (i >= parts.Length - 1 || parts[i + 1] != ArrayElementName || !type.IsArrayOrList()) continue; + i++; + type = type.GetCollectionElementType(); + } + + return false; + } + } +} \ No newline at end of file diff --git a/Assets/BetterExtensions/Editor/Utility/SerializedPropertyUtility.cs.meta b/Assets/BetterExtensions/Editor/Utility/SerializedPropertyUtility.cs.meta new file mode 100644 index 0000000..dd737df --- /dev/null +++ b/Assets/BetterExtensions/Editor/Utility/SerializedPropertyUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 20048c1506051da449415949257906e8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/BetterExtensions/package.json b/Assets/BetterExtensions/package.json index d3221f6..6c5dfef 100644 --- a/Assets/BetterExtensions/package.json +++ b/Assets/BetterExtensions/package.json @@ -1,7 +1,7 @@ { "name": "com.uurha.betterextensions", "displayName": "Better Extensions", - "version": "1.5.6", + "version": "1.5.7", "unity": "2021.3", "description": "Unity extensions, serialize extension, async extension, string extension and UI extensions", "dependencies": { diff --git a/BetterExtensions.Editor.csproj.DotSettings b/BetterExtensions.Editor.csproj.DotSettings index 6dbfc01..d26355f 100644 --- a/BetterExtensions.Editor.csproj.DotSettings +++ b/BetterExtensions.Editor.csproj.DotSettings @@ -1,4 +1,9 @@  + True + True + True + True + True True True True \ No newline at end of file diff --git a/BetterExtensions.Runtime.csproj.DotSettings b/BetterExtensions.Runtime.csproj.DotSettings index 6ef0f2d..50a0eb1 100644 --- a/BetterExtensions.Runtime.csproj.DotSettings +++ b/BetterExtensions.Runtime.csproj.DotSettings @@ -1,6 +1,10 @@  True + True True + True + True + True True True True