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}