Space Ad Mobile 9.5.1
Space Ad Mobile makes ads easy to add into your mobile game: one unified workflow for LevelPlay, AdMob, Yandex.Ads across banners, interstitials and rewarded.
Loading...
Searching...
No Matches
ConditionalMemberEvaluator.cs
1#if UNITY_EDITOR
2using System;
3using System.Collections;
4using System.Collections.Generic;
5using System.Linq;
6using System.Reflection;
7using System.Runtime.CompilerServices;
8using UnityEditor;
9using UnityEngine;
10
11#pragma warning disable IDE0130
12namespace SCG.Editor
13#pragma warning restore IDE0130
14{
19 internal static class ConditionalMemberEvaluator
20 {
21 #region Constants
22
23 private static readonly FieldInfo customPropertyDrawerTypeField =
24 typeof(CustomPropertyDrawer).GetField("m_Type", BindingFlags.Instance | BindingFlags.NonPublic);
25
26 private static readonly FieldInfo customPropertyDrawerUseForChildrenField =
27 typeof(CustomPropertyDrawer).GetField("m_UseForChildren", BindingFlags.Instance | BindingFlags.NonPublic);
28
29 private static readonly FieldInfo propertyDrawerFieldInfoField =
30 typeof(PropertyDrawer).GetField("m_FieldInfo", BindingFlags.Instance | BindingFlags.NonPublic);
31
32 private static readonly BuiltInDrawerRegistration[] builtInDrawerRegistrations = BuildBuiltInDrawerRegistrations();
33
34 private static readonly Type[] builtInDrawerConflictTypes = builtInDrawerRegistrations
35 .Select(registration => registration.TargetType)
36 .Distinct()
37 .ToArray();
38
39 private static readonly Dictionary<string, PropertyDrawer> builtInDrawerCache = new();
40 private static readonly Dictionary<Type, MethodInfo> builtInDrawerOnGuiMethodCache = new();
41 private static readonly Dictionary<Type, MethodInfo> builtInDrawerGetPropertyHeightMethodCache = new();
42
43 private static readonly Type[] onGuiParameterTypes =
44 {
45 typeof(Rect),
46 typeof(SerializedProperty),
47 typeof(GUIContent)
48 };
49
50 private static readonly Type[] getPropertyHeightParameterTypes =
51 {
52 typeof(SerializedProperty),
53 typeof(GUIContent)
54 };
55
56 #endregion
57
65 internal static bool ShouldShow(SerializedProperty property, ShowIfAttribute attribute) =>
66 attribute == null || property == null ||
67 !TryEvaluate(property, attribute.MemberName, Array.Empty<object>(), out var conditionMet) ||
68 (attribute.Invert
69 ? !conditionMet
70 : conditionMet);
71
79 internal static bool ShouldEnable(SerializedProperty property, EnableIfAttribute attribute) =>
80 attribute == null || property == null ||
81 !TryEvaluate(property, attribute.MemberName, attribute.ExpectedValues, out var conditionMet) ||
82 conditionMet;
83
92 internal static bool TryGetBuiltInDrawer(SerializedProperty property, FieldInfo fieldInfo, out PropertyDrawer drawer)
93 {
94 drawer = null;
95 if (property == null || fieldInfo == null || !RequiresBuiltInDrawerDelegation(fieldInfo.FieldType) ||
96 !TryGetBuiltInDrawerRegistration(fieldInfo.FieldType, out var registration))
97 return false;
98
99 var cacheKey = GetBuiltInDrawerCacheKey(property, registration.DrawerType);
100 if (!string.IsNullOrEmpty(cacheKey) && builtInDrawerCache.TryGetValue(cacheKey, out drawer))
101 return drawer != null;
102
103 drawer = CreateBuiltInDrawer(fieldInfo, registration);
104 if (drawer == null)
105 return false;
106
107 if (!string.IsNullOrEmpty(cacheKey))
108 builtInDrawerCache[cacheKey] = drawer;
109
110 return true;
111 }
112
121 internal static void DrawBuiltInDrawer(PropertyDrawer drawer, Rect position, SerializedProperty property, GUIContent label)
122 {
123 if (drawer == null)
124 return;
125
126 var method = GetDrawerMethod(drawer.GetType(), nameof(PropertyDrawer.OnGUI), builtInDrawerOnGuiMethodCache, onGuiParameterTypes);
127 method?.Invoke(drawer, new object[] { position, property, label });
128 }
129
138 internal static float GetBuiltInDrawerHeight(PropertyDrawer drawer, SerializedProperty property, GUIContent label)
139 {
140 if (drawer == null)
141 return EditorGUI.GetPropertyHeight(property, label, true);
142
143 var method = GetDrawerMethod(
144 drawer.GetType(),
145 nameof(PropertyDrawer.GetPropertyHeight),
146 builtInDrawerGetPropertyHeightMethodCache,
147 getPropertyHeightParameterTypes);
148 return method?.Invoke(drawer, new object[] { property, label }) is float height
149 ? height
150 : EditorGUI.GetPropertyHeight(property, label, true);
151 }
152
153 #region Evaluation
154
155 private static bool TryEvaluate(
156 SerializedProperty property,
157 string memberName,
158 object[] expectedValues,
159 out bool conditionMet)
160 {
161 conditionMet = false;
162 if (property == null || string.IsNullOrEmpty(memberName))
163 return false;
164
165 if (!TryGetConditionValue(property, memberName, out var conditionValue))
166 return false;
167
168 conditionMet = MatchesExpectedValues(conditionValue, expectedValues);
169 return true;
170 }
171
172 private static bool TryGetConditionValue(SerializedProperty property, string memberName, out object value) =>
173 TryGetConditionFromSerializedProperty(property, memberName, out value) ||
174 TryGetConditionFromMember(GetTargetObjectOfProperty(property, false), memberName, out value);
175
176 private static bool MatchesExpectedValues(object conditionValue, object[] expectedValues) =>
177 expectedValues == null || expectedValues.Length == 0
178 ? conditionValue is true
179 : expectedValues.Any(expectedValue => AreEqual(conditionValue, expectedValue));
180
181 private static bool AreEqual(object left, object right)
182 {
183 if (ReferenceEquals(left, right))
184 return true;
185
186 if (left == null || right == null)
187 return false;
188
189 if (left.Equals(right))
190 return true;
191
192 var leftText = left.ToString();
193 var rightText = right.ToString();
194 if (string.Equals(leftText, rightText, StringComparison.Ordinal))
195 return true;
196
197 try
198 {
199 var convertedRight = Convert.ChangeType(right, left.GetType());
200 return left.Equals(convertedRight);
201 }
202 catch
203 {
204 return false;
205 }
206 }
207
208 #endregion
209
210 #region Built-In Drawers
211
212 #region Nested
213
214 private sealed class BuiltInDrawerRegistration
215 {
216 public Type TargetType { get; }
217 public Type DrawerType { get; }
218 public bool UseForChildren { get; }
219
220 public BuiltInDrawerRegistration(Type targetType, Type drawerType, bool useForChildren)
221 {
222 TargetType = targetType;
223 DrawerType = drawerType;
224 UseForChildren = useForChildren;
225 }
226 }
227
228 #endregion
229
230 private static BuiltInDrawerRegistration[] BuildBuiltInDrawerRegistrations() => AppDomain.CurrentDomain
231 .GetAssemblies()
232 .Where(IsUnityAssembly)
233 .SelectMany(GetTypesSafely)
234 .Where(type => typeof(PropertyDrawer).IsAssignableFrom(type) && !type.IsAbstract)
235 .SelectMany(GetBuiltInDrawerRegistrations)
236 .GroupBy(registration => $"{registration.TargetType.AssemblyQualifiedName}|{registration.DrawerType.AssemblyQualifiedName}|{registration.UseForChildren}")
237 .Select(group => group.First())
238 .ToArray();
239
240 private static IEnumerable<Type> GetTypesSafely(Assembly assembly)
241 {
242 if (assembly == null)
243 return Array.Empty<Type>();
244
245 try
246 {
247 return assembly.GetTypes();
248 }
249 catch (ReflectionTypeLoadException exception)
250 {
251 return exception.Types.Where(type => type != null);
252 }
253 catch
254 {
255 return Array.Empty<Type>();
256 }
257 }
258
259 private static IEnumerable<BuiltInDrawerRegistration> GetBuiltInDrawerRegistrations(Type drawerType)
260 {
261 if (drawerType == null || customPropertyDrawerTypeField == null || customPropertyDrawerUseForChildrenField == null)
262 yield break;
263
264 var attributes = drawerType.GetCustomAttributes(typeof(CustomPropertyDrawer), false);
265 foreach (var attribute in attributes)
266 {
267 var targetType = customPropertyDrawerTypeField.GetValue(attribute) as Type;
268 if (targetType == null || typeof(PropertyAttribute).IsAssignableFrom(targetType))
269 continue;
270
271 yield return new BuiltInDrawerRegistration(
272 targetType,
273 drawerType,
274 customPropertyDrawerUseForChildrenField.GetValue(attribute) is true);
275 }
276 }
277
278 private static bool TryGetBuiltInDrawerRegistration(Type fieldType, out BuiltInDrawerRegistration registration)
279 {
280 registration = null;
281 if (fieldType == null)
282 return false;
283
284 registration = builtInDrawerRegistrations.FirstOrDefault(entry => IsExactDrawerTarget(entry.TargetType, fieldType));
285 if (registration != null)
286 return true;
287
288 registration = builtInDrawerRegistrations
289 .Where(entry => entry.UseForChildren && MatchesDrawerTarget(entry.TargetType, fieldType))
290 .OrderByDescending(entry => GetSpecificity(entry.TargetType))
291 .FirstOrDefault();
292 return registration != null;
293 }
294
295 private static bool RequiresBuiltInDrawerDelegation(Type fieldType) =>
296 fieldType != null &&
297 builtInDrawerConflictTypes.Any(conflictType => IsExactDrawerTarget(conflictType, fieldType) || conflictType.IsAssignableFrom(fieldType));
298
299 private static bool IsExactDrawerTarget(Type targetType, Type fieldType) =>
300 targetType == fieldType ||
301 (targetType.IsGenericTypeDefinition &&
302 fieldType.IsGenericType &&
303 fieldType.GetGenericTypeDefinition() == targetType);
304
305 private static bool MatchesDrawerTarget(Type targetType, Type fieldType) =>
306 !targetType.IsGenericTypeDefinition &&
307 targetType.IsAssignableFrom(fieldType);
308
309 private static int GetSpecificity(Type type)
310 {
311 var depth = 0;
312 while (type != null)
313 {
314 depth++;
315 type = type.BaseType;
316 }
317
318 return depth;
319 }
320
321 private static bool IsUnityAssembly(Assembly assembly)
322 {
323 var assemblyName = assembly?.GetName().Name;
324 return !string.IsNullOrEmpty(assemblyName) &&
325 assemblyName.StartsWith(nameof(Unity), StringComparison.Ordinal);
326 }
327
328 private static PropertyDrawer CreateBuiltInDrawer(FieldInfo fieldInfo, BuiltInDrawerRegistration registration)
329 {
330 if (fieldInfo == null || registration?.DrawerType == null)
331 return null;
332
333 try
334 {
335 if (Activator.CreateInstance(registration.DrawerType, true) is not PropertyDrawer drawer)
336 return null;
337
338 propertyDrawerFieldInfoField?.SetValue(drawer, fieldInfo);
339 return drawer;
340 }
341 catch
342 {
343 return null;
344 }
345 }
346
347 private static MethodInfo GetDrawerMethod(
348 Type drawerType,
349 string methodName,
350 Dictionary<Type, MethodInfo> cache,
351 Type[] parameterTypes)
352 {
353 if (drawerType == null || string.IsNullOrEmpty(methodName) || cache == null || parameterTypes == null)
354 return null;
355
356 if (cache.TryGetValue(drawerType, out var method))
357 return method;
358
359 method = drawerType.GetMethod(
360 methodName,
361 BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
362 null,
363 parameterTypes,
364 null);
365 cache[drawerType] = method;
366 return method;
367 }
368
369 private static string GetBuiltInDrawerCacheKey(SerializedProperty property, Type drawerType)
370 {
371 if (property == null || drawerType == null)
372 return null;
373
374 var targetObject = property.serializedObject?.targetObject;
375 var targetId = targetObject != null
376 ? RuntimeHelpers.GetHashCode(targetObject)
377 : 0;
378 return $"{drawerType.AssemblyQualifiedName}|{targetId}|{property.propertyPath}";
379 }
380
381 #endregion
382
383 #region SerializedProperty
384
385 private static bool TryGetConditionFromSerializedProperty(SerializedProperty property, string memberName, out object value)
386 {
387 value = null;
388 if (property == null)
389 return false;
390
391 var conditionProperty = FindSiblingProperty(property, memberName);
392 return conditionProperty != null && TryGetSerializedPropertyValue(conditionProperty, out value);
393 }
394
395 private static bool TryGetSerializedPropertyValue(SerializedProperty property, out object value)
396 {
397 value = null;
398 if (property == null)
399 return false;
400
401 switch (property.propertyType)
402 {
403 case SerializedPropertyType.Boolean:
404 value = property.boolValue;
405 return true;
406 case SerializedPropertyType.Integer:
407 value = property.intValue;
408 return true;
409 case SerializedPropertyType.Float:
410 value = property.floatValue;
411 return true;
412 case SerializedPropertyType.String:
413 value = property.stringValue;
414 return true;
415 case SerializedPropertyType.Enum:
416 value = property.enumValueIndex >= 0 && property.enumValueIndex < property.enumNames.Length
417 ? property.enumNames[property.enumValueIndex]
418 : property.enumValueIndex;
419 return true;
420 case SerializedPropertyType.Generic:
421 case SerializedPropertyType.Color:
422 case SerializedPropertyType.ObjectReference:
423 case SerializedPropertyType.LayerMask:
424 case SerializedPropertyType.Vector2:
425 case SerializedPropertyType.Vector3:
426 case SerializedPropertyType.Vector4:
427 case SerializedPropertyType.Rect:
428 case SerializedPropertyType.ArraySize:
429 case SerializedPropertyType.Character:
430 case SerializedPropertyType.AnimationCurve:
431 case SerializedPropertyType.Bounds:
432 case SerializedPropertyType.Gradient:
433 case SerializedPropertyType.Quaternion:
434 case SerializedPropertyType.ExposedReference:
435 case SerializedPropertyType.FixedBufferSize:
436 case SerializedPropertyType.Vector2Int:
437 case SerializedPropertyType.Vector3Int:
438 case SerializedPropertyType.RectInt:
439 case SerializedPropertyType.BoundsInt:
440 case SerializedPropertyType.ManagedReference:
441 case SerializedPropertyType.Hash128:
442 default:
443 return false;
444 }
445 }
446
447 private static SerializedProperty FindSiblingProperty(SerializedProperty property, string name)
448 {
449 var path = property.propertyPath;
450 if (string.IsNullOrEmpty(path))
451 return null;
452
453 var lastDot = path.LastIndexOf('.');
454 var parentPath = lastDot >= 0
455 ? path[..(lastDot + 1)]
456 : string.Empty;
457 return property.serializedObject.FindProperty(parentPath + name);
458 }
459
460 #endregion
461
462 #region Reflection
463
464 private static bool TryGetConditionFromMember(object target, string memberName, out object value)
465 {
466 value = null;
467 if (target == null || string.IsNullOrEmpty(memberName))
468 return false;
469
470 const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
471 var type = target.GetType();
472 while (type != null)
473 {
474 var field = type.GetField(memberName, flags);
475 if (field != null)
476 {
477 value = field.GetValue(target);
478 return true;
479 }
480
481 var property = type.GetProperty(memberName, flags);
482 if (property != null && property.GetIndexParameters().Length == 0)
483 {
484 value = property.GetValue(target, null);
485 return true;
486 }
487
488 var method = type.GetMethod(memberName, flags, null, Type.EmptyTypes, null);
489 if (method != null)
490 {
491 value = method.Invoke(target, null);
492 return true;
493 }
494
495 type = type.BaseType;
496 }
497
498 return false;
499 }
500
501 private static object GetTargetObjectOfProperty(SerializedProperty property, bool includeSelf)
502 {
503 if (property?.serializedObject?.targetObject is not object target)
504 return null;
505
506 var path = property.propertyPath.Replace(".Array.data[", "[");
507 var elements = path.Split('.');
508 var endIndex = includeSelf
509 ? elements.Length
510 : Math.Max(0, elements.Length - 1);
511
512 for (var i = 0; i < endIndex; i++)
513 {
514 var element = elements[i];
515 if (element.EndsWith("]"))
516 {
517 var bracketIndex = element.IndexOf('[');
518 var fieldName = element[..bracketIndex];
519 var indexText = element.Substring(bracketIndex + 1, element.Length - bracketIndex - 2);
520 if (!int.TryParse(indexText, out var index))
521 return null;
522
523 target = GetIndexedValue(target, fieldName, index);
524 }
525 else
526 {
527 target = GetMemberValue(target, element);
528 }
529
530 if (target == null)
531 return null;
532 }
533
534 return target;
535 }
536
537 private static object GetMemberValue(object source, string name)
538 {
539 if (source == null || string.IsNullOrEmpty(name))
540 return null;
541
542 const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
543 var type = source.GetType();
544 var field = type.GetField(name, flags);
545 if (field != null)
546 return field.GetValue(source);
547
548 var property = type.GetProperty(name, flags);
549 return property != null && property.GetIndexParameters().Length == 0
550 ? property.GetValue(source, null)
551 : null;
552 }
553
554 private static object GetIndexedValue(object source, string name, int index)
555 {
556 if (GetMemberValue(source, name) is not IEnumerable enumerable)
557 return null;
558
559 var enumerator = enumerable.GetEnumerator();
560 using var disposableEnumerator = enumerator as IDisposable;
561 for (var i = 0; i <= index; i++)
562 {
563 if (!enumerator.MoveNext())
564 return null;
565 }
566
567 return enumerator.Current;
568 }
569
570 #endregion
571 }
572}
573#endif