A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections.Generic; 3using System.Linq; 4using System.Reflection; 5using UnityEngine; 6 7namespace Unity.VisualScripting 8{ 9 public static class AttributeUtility 10 { 11 private static readonly Dictionary<object, AttributeCache> optimizedCaches = new Dictionary<object, AttributeCache>(); 12 13 private class AttributeCache 14 { 15 // Using lists instead of hashsets because: 16 // - Insertion will be faster 17 // - Iteration will be just as fast 18 // - We don't need contains lookups 19 public List<Attribute> inheritedAttributes { get; } = new List<Attribute>(); 20 public List<Attribute> definedAttributes { get; } = new List<Attribute>(); 21 22 // Important to use Attribute.GetCustomAttributes, because MemberInfo.GetCustomAttributes 23 // ignores the inherited parameter on properties and events 24 25 // However, Attribute.GetCustomAttributes seems to have at least two obscure Mono 2.0 bugs. 26 27 // 1. Basically, when a parameter is optional and is marked as [OptionalAttribute], 28 // the custom attributes array is typed object[] instead of Attribute[], which 29 // makes Mono throw an exception in Attribute.GetCustomAttributes when trying 30 // to cast the array. After some testing, it appears this only happens for 31 // non-inherited calls, and only for parameter infos (although I'm not sure why). 32 // I *believe* the offending line in the Mono source is this one: 33 // https://github.com/mono/mono/blob/mono-2-0/mcs/class/corlib/System/MonoCustomAttrs.cs#L143 34 35 // 2. For some other implementation reason, on iOS, GetCustomAttributes on MemberInfo fails. 36 // https://support.ludiq.io/forums/5-bolt/topics/729-systeminvalidcastexception-in-attributecache-on-ios/ 37 38 // As a fallback, we will use the GetCustomAttributes from the type itself, 39 // which doesn't seem to be bugged (ugh). But because this method ignores the 40 // inherited parameter on some occasions, we will warn if the inherited fetch fails. 41 42 // Additionally, some Unity built-in attributes use threaded API methods in their 43 // constructors and will therefore throw an error if GetCustomAttributes is called 44 // from the serialization thread or from a secondary thread. We'll generally fallback 45 // and warn on any exception to make sure not to block anything more than needed. 46 // https://support.ludiq.io/communities/5/topics/2024-/ 47 48 public AttributeCache(MemberInfo element) 49 { 50 Ensure.That(nameof(element)).IsNotNull(element); 51 52 try 53 { 54 try 55 { 56 Cache(Attribute.GetCustomAttributes(element, true), inheritedAttributes); 57 } 58 catch (InvalidCastException ex) 59 { 60 Cache(element.GetCustomAttributes(true).Cast<Attribute>().ToArray(), inheritedAttributes); 61 Debug.LogWarning($"Failed to fetch inherited attributes on {element}.\n{ex}"); 62 } 63 } 64 catch (Exception ex) 65 { 66 Debug.LogWarning($"Failed to fetch inherited attributes on {element}.\n{ex}"); 67 } 68 69 try 70 { 71 try 72 { 73 Cache(Attribute.GetCustomAttributes(element, false), definedAttributes); 74 } 75 catch (InvalidCastException) 76 { 77 Cache(element.GetCustomAttributes(false).Cast<Attribute>().ToArray(), definedAttributes); 78 } 79 } 80 catch (Exception ex) 81 { 82 Debug.LogWarning($"Failed to fetch defined attributes on {element}.\n{ex}"); 83 } 84 } 85 86 public AttributeCache(ParameterInfo element) 87 { 88 Ensure.That(nameof(element)).IsNotNull(element); 89 90 try 91 { 92 try 93 { 94 Cache(Attribute.GetCustomAttributes(element, true), inheritedAttributes); 95 } 96 catch (InvalidCastException ex) 97 { 98 Cache(element.GetCustomAttributes(true).Cast<Attribute>().ToArray(), inheritedAttributes); 99 Debug.LogWarning($"Failed to fetch inherited attributes on {element}.\n{ex}"); 100 } 101 } 102 catch (Exception ex) 103 { 104 Debug.LogWarning($"Failed to fetch inherited attributes on {element}.\n{ex}"); 105 } 106 107 try 108 { 109 try 110 { 111 Cache(Attribute.GetCustomAttributes(element, false), definedAttributes); 112 } 113 catch (InvalidCastException) 114 { 115 Cache(element.GetCustomAttributes(false).Cast<Attribute>().ToArray(), definedAttributes); 116 } 117 } 118 catch (Exception ex) 119 { 120 Debug.LogWarning($"Failed to fetch defined attributes on {element}.\n{ex}"); 121 } 122 } 123 124 public AttributeCache(IAttributeProvider element) 125 { 126 Ensure.That(nameof(element)).IsNotNull(element); 127 128 try 129 { 130 Cache(element.GetCustomAttributes(true), inheritedAttributes); 131 } 132 catch (Exception ex) 133 { 134 Debug.LogWarning($"Failed to fetch inherited attributes on {element}.\n{ex}"); 135 } 136 137 try 138 { 139 Cache(element.GetCustomAttributes(false), definedAttributes); 140 } 141 catch (Exception ex) 142 { 143 Debug.LogWarning($"Failed to fetch defined attributes on {element}.\n{ex}"); 144 } 145 } 146 147 private void Cache(Attribute[] attributeObjects, List<Attribute> cache) 148 { 149 foreach (var attributeObject in attributeObjects) 150 { 151 cache.Add(attributeObject); 152 } 153 } 154 155 private bool HasAttribute(Type attributeType, List<Attribute> cache) 156 { 157 for (int i = 0; i < cache.Count; i++) 158 { 159 var attribute = cache[i]; 160 161 if (attributeType.IsInstanceOfType(attribute)) 162 { 163 return true; 164 } 165 } 166 167 return false; 168 } 169 170 private Attribute GetAttribute(Type attributeType, List<Attribute> cache) 171 { 172 for (int i = 0; i < cache.Count; i++) 173 { 174 var attribute = cache[i]; 175 176 if (attributeType.IsInstanceOfType(attribute)) 177 { 178 return attribute; 179 } 180 } 181 182 return null; 183 } 184 185 private IEnumerable<Attribute> GetAttributes(Type attributeType, List<Attribute> cache) 186 { 187 for (int i = 0; i < cache.Count; i++) 188 { 189 var attribute = cache[i]; 190 191 if (attributeType.IsInstanceOfType(attribute)) 192 { 193 yield return attribute; 194 } 195 } 196 } 197 198 public bool HasAttribute(Type attributeType, bool inherit = true) 199 { 200 if (inherit) 201 { 202 return HasAttribute(attributeType, inheritedAttributes); 203 } 204 else 205 { 206 return HasAttribute(attributeType, definedAttributes); 207 } 208 } 209 210 public Attribute GetAttribute(Type attributeType, bool inherit = true) 211 { 212 if (inherit) 213 { 214 return GetAttribute(attributeType, inheritedAttributes); 215 } 216 else 217 { 218 return GetAttribute(attributeType, definedAttributes); 219 } 220 } 221 222 public IEnumerable<Attribute> GetAttributes(Type attributeType, bool inherit = true) 223 { 224 if (inherit) 225 { 226 return GetAttributes(attributeType, inheritedAttributes); 227 } 228 else 229 { 230 return GetAttributes(attributeType, definedAttributes); 231 } 232 } 233 234 public bool HasAttribute<TAttribute>(bool inherit = true) 235 where TAttribute : Attribute 236 { 237 return HasAttribute(typeof(TAttribute), inherit); 238 } 239 240 public TAttribute GetAttribute<TAttribute>(bool inherit = true) 241 where TAttribute : Attribute 242 { 243 return (TAttribute)GetAttribute(typeof(TAttribute), inherit); 244 } 245 246 public IEnumerable<TAttribute> GetAttributes<TAttribute>(bool inherit = true) 247 where TAttribute : Attribute 248 { 249 return GetAttributes(typeof(TAttribute), inherit).Cast<TAttribute>(); 250 } 251 } 252 253 private static AttributeCache GetAttributeCache(MemberInfo element) 254 { 255 Ensure.That(nameof(element)).IsNotNull(element); 256 257 // For MemberInfo (and therefore Type), we use the MetadataToken 258 // as a key instead of the object itself, because member infos 259 // are not singletons but their tokens are, optimizing the cache. 260 var key = element; 261 262 lock (optimizedCaches) 263 { 264 if (!optimizedCaches.TryGetValue(key, out var cache)) 265 { 266 cache = new AttributeCache(element); 267 optimizedCaches.Add(key, cache); 268 } 269 270 return cache; 271 } 272 } 273 274 private static AttributeCache GetAttributeCache(ParameterInfo element) 275 { 276 Ensure.That(nameof(element)).IsNotNull(element); 277 278 // For ParameterInfo, we maybe also should use the MetadataToken, 279 // but I'm not sure they're globally unique or just locally unique. TODO: Check 280 var key = element; 281 282 lock (optimizedCaches) 283 { 284 if (!optimizedCaches.TryGetValue(key, out var cache)) 285 { 286 cache = new AttributeCache(element); 287 optimizedCaches.Add(key, cache); 288 } 289 290 return cache; 291 } 292 } 293 294 private static AttributeCache GetAttributeCache(IAttributeProvider element) 295 { 296 Ensure.That(nameof(element)).IsNotNull(element); 297 298 var key = element; 299 300 lock (optimizedCaches) 301 { 302 if (!optimizedCaches.TryGetValue(key, out var cache)) 303 { 304 cache = new AttributeCache(element); 305 optimizedCaches.Add(key, cache); 306 } 307 308 return cache; 309 } 310 } 311 312 #region Members (& Types) 313 314 public static void CacheAttributes(MemberInfo element) 315 { 316 GetAttributeCache(element); 317 } 318 319 /// <summary> 320 /// Gets attributes on an enum member, eg. enum E { [Attr] A } 321 /// </summary> 322 internal static IEnumerable<T> GetAttributeOfEnumMember<T>(this Enum enumVal) where T : System.Attribute 323 { 324 var type = enumVal.GetType(); 325 var memInfo = type.GetMember(enumVal.ToString()); 326 var attributes = memInfo[0].GetCustomAttributes(typeof(T), false); 327 return attributes.Cast<T>(); 328 } 329 330 public static bool HasAttribute(this MemberInfo element, Type attributeType, bool inherit = true) 331 { 332 return GetAttributeCache(element).HasAttribute(attributeType, inherit); 333 } 334 335 public static Attribute GetAttribute(this MemberInfo element, Type attributeType, bool inherit = true) 336 { 337 return GetAttributeCache(element).GetAttribute(attributeType, inherit); 338 } 339 340 public static IEnumerable<Attribute> GetAttributes(this MemberInfo element, Type attributeType, bool inherit = true) 341 { 342 return GetAttributeCache(element).GetAttributes(attributeType, inherit); 343 } 344 345 public static bool HasAttribute<TAttribute>(this MemberInfo element, bool inherit = true) 346 where TAttribute : Attribute 347 { 348 return GetAttributeCache(element).HasAttribute<TAttribute>(inherit); 349 } 350 351 public static TAttribute GetAttribute<TAttribute>(this MemberInfo element, bool inherit = true) 352 where TAttribute : Attribute 353 { 354 return GetAttributeCache(element).GetAttribute<TAttribute>(inherit); 355 } 356 357 public static IEnumerable<TAttribute> GetAttributes<TAttribute>(this MemberInfo element, bool inherit = true) 358 where TAttribute : Attribute 359 { 360 return GetAttributeCache(element).GetAttributes<TAttribute>(inherit); 361 } 362 363 #endregion 364 365 #region Parameters 366 367 public static void CacheAttributes(ParameterInfo element) 368 { 369 GetAttributeCache(element); 370 } 371 372 public static bool HasAttribute(this ParameterInfo element, Type attributeType, bool inherit = true) 373 { 374 return GetAttributeCache(element).HasAttribute(attributeType, inherit); 375 } 376 377 public static Attribute GetAttribute(this ParameterInfo element, Type attributeType, bool inherit = true) 378 { 379 return GetAttributeCache(element).GetAttribute(attributeType, inherit); 380 } 381 382 public static IEnumerable<Attribute> GetAttributes(this ParameterInfo element, Type attributeType, bool inherit = true) 383 { 384 return GetAttributeCache(element).GetAttributes(attributeType, inherit); 385 } 386 387 public static bool HasAttribute<TAttribute>(this ParameterInfo element, bool inherit = true) 388 where TAttribute : Attribute 389 { 390 return GetAttributeCache(element).HasAttribute<TAttribute>(inherit); 391 } 392 393 public static TAttribute GetAttribute<TAttribute>(this ParameterInfo element, bool inherit = true) 394 where TAttribute : Attribute 395 { 396 return GetAttributeCache(element).GetAttribute<TAttribute>(inherit); 397 } 398 399 public static IEnumerable<TAttribute> GetAttributes<TAttribute>(this ParameterInfo element, bool inherit = true) 400 where TAttribute : Attribute 401 { 402 return GetAttributeCache(element).GetAttributes<TAttribute>(inherit); 403 } 404 405 #endregion 406 407 #region Providers 408 409 public static void CacheAttributes(IAttributeProvider element) 410 { 411 GetAttributeCache(element); 412 } 413 414 public static bool HasAttribute(this IAttributeProvider element, Type attributeType, bool inherit = true) 415 { 416 return GetAttributeCache(element).HasAttribute(attributeType, inherit); 417 } 418 419 public static Attribute GetAttribute(this IAttributeProvider element, Type attributeType, bool inherit = true) 420 { 421 return GetAttributeCache(element).GetAttribute(attributeType, inherit); 422 } 423 424 public static IEnumerable<Attribute> GetAttributes(this IAttributeProvider element, Type attributeType, bool inherit = true) 425 { 426 return GetAttributeCache(element).GetAttributes(attributeType, inherit); 427 } 428 429 public static bool HasAttribute<TAttribute>(this IAttributeProvider element, bool inherit = true) 430 where TAttribute : Attribute 431 { 432 return GetAttributeCache(element).HasAttribute<TAttribute>(inherit); 433 } 434 435 public static TAttribute GetAttribute<TAttribute>(this IAttributeProvider element, bool inherit = true) 436 where TAttribute : Attribute 437 { 438 return GetAttributeCache(element).GetAttribute<TAttribute>(inherit); 439 } 440 441 public static IEnumerable<TAttribute> GetAttributes<TAttribute>(this IAttributeProvider element, bool inherit = true) 442 where TAttribute : Attribute 443 { 444 return GetAttributeCache(element).GetAttributes<TAttribute>(inherit); 445 } 446 447 #endregion 448 449 #region Conditions 450 451 public static bool CheckCondition(Type type, object target, string conditionMemberName, bool fallback) 452 { 453 Ensure.That(nameof(type)).IsNotNull(type); 454 455 try 456 { 457 if (target != null && !type.IsInstanceOfType(target)) 458 { 459 throw new ArgumentException("Target is not an instance of type.", nameof(target)); 460 } 461 462 if (conditionMemberName == null) 463 { 464 return fallback; 465 } 466 467 var manipulator = type.GetMember(conditionMemberName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault()?.ToManipulator(); 468 469 if (manipulator == null) 470 { 471 throw new MissingMemberException(type.ToString(), conditionMemberName); 472 } 473 474 return manipulator.Get<bool>(target); 475 } 476 catch (Exception ex) 477 { 478 Debug.LogWarning("Failed to check attribute condition: \n" + ex); 479 return fallback; 480 } 481 } 482 483 public static bool CheckCondition<T>(T target, string conditionMemberName, bool fallback) 484 { 485 return CheckCondition(target?.GetType() ?? typeof(T), target, conditionMemberName, fallback); 486 } 487 488 #endregion 489 } 490}