Barotrauma Client Doc
ReflectionUtils.cs
1 #nullable enable
2 using System;
3 using System.Collections.Concurrent;
4 using System.Collections.Generic;
5 using System.Collections.Immutable;
6 using System.Linq;
7 using System.Reflection;
8 
9 namespace Barotrauma
10 {
11  public static class ReflectionUtils
12  {
13  private static readonly ConcurrentDictionary<Assembly, ImmutableArray<Type>> CachedNonAbstractTypes = new();
14  private static readonly ConcurrentDictionary<string, ImmutableArray<Type>> TypeSearchCache = new();
15 
16  public static IEnumerable<Type> GetDerivedNonAbstract<T>()
17  {
18  Type t = typeof(T);
19  string typeName = t.FullName ?? t.Name;
20 
21  // search quick lookup cache
22  if (TypeSearchCache.TryGetValue(typeName, out var value))
23  {
24  return value;
25  }
26 
27  // doesn't exist so let's add it.
28  Assembly assembly = typeof(T).Assembly;
29  if (!CachedNonAbstractTypes.ContainsKey(assembly))
30  {
31  AddNonAbstractAssemblyTypes(assembly);
32  }
33 
34  // build cache from registered assemblies' types.
35  var list = CachedNonAbstractTypes.Values
36  .SelectMany(arr => arr.Where(type => type.IsSubclassOf(t)))
37  .ToImmutableArray();
38 
39  if (list.Length == 0)
40  {
41  return ImmutableArray<Type>.Empty; // No types, don't add to cache
42  }
43 
44  if (!TypeSearchCache.TryAdd(typeName, list))
45  {
46  DebugConsole.LogError($"ReflectionUtils::AddNonAbstractAssemblyTypes() | Error while adding to quick lookup cache.");
47  }
48  return list;
49  }
50 
56  public static void AddNonAbstractAssemblyTypes(Assembly assembly, bool overwrite = false)
57  {
58  if (CachedNonAbstractTypes.ContainsKey(assembly))
59  {
60  if (!overwrite)
61  {
62  DebugConsole.LogError(
63  $"ReflectionUtils::AddNonAbstractAssemblyTypes() | The assembly [{assembly.GetName()}] already exists in the cache.");
64  return;
65  }
66 
67  CachedNonAbstractTypes.Remove(assembly, out _);
68  }
69 
70  try
71  {
72  if (!CachedNonAbstractTypes.TryAdd(assembly, assembly.GetSafeTypes().Where(t => !t.IsAbstract).ToImmutableArray()))
73  {
74  DebugConsole.LogError($"ReflectionUtils::AddNonAbstractAssemblyTypes() | Unable to add types from Assembly to cache.");
75  }
76  else
77  {
78  TypeSearchCache.Clear(); // Needs to be rebuilt to include potential new types
79  }
80  }
81  catch (ReflectionTypeLoadException e)
82  {
83  DebugConsole.LogError($"ReflectionUtils::AddNonAbstractAssemblyTypes() | RTFException: Unable to load Assembly Types from {assembly.GetName()}.");
84  }
85  }
86 
91  public static void RemoveAssemblyFromCache(Assembly assembly)
92  {
93  CachedNonAbstractTypes.Remove(assembly, out _);
94  TypeSearchCache.Clear();
95  }
96 
100  internal static void ResetCache()
101  {
102  CachedNonAbstractTypes.Clear();
103  CachedNonAbstractTypes.TryAdd(typeof(ReflectionUtils).Assembly, typeof(ReflectionUtils).Assembly.GetSafeTypes().ToImmutableArray());
104  TypeSearchCache.Clear();
105  }
106 
107  public static Option<TBase> ParseDerived<TBase, TInput>(TInput input) where TInput : notnull where TBase : notnull
108  {
109  static Option<TBase> none() => Option<TBase>.None();
110 
111  var derivedTypes = GetDerivedNonAbstract<TBase>();
112 
113  Option<TBase> parseOfType(Type t)
114  {
115  //every TBase type is expected to have a method with the following signature:
116  // public static Option<T> Parse(TInput str)
117  var parseFunc = t.GetMethod("Parse", BindingFlags.Public | BindingFlags.Static);
118  if (parseFunc is null) { return none(); }
119 
120  var parameters = parseFunc.GetParameters();
121  if (parameters.Length != 1) { return none(); }
122 
123  var returnType = parseFunc.ReturnType;
124  if (!returnType.IsConstructedGenericType) { return none(); }
125  if (returnType.GetGenericTypeDefinition() != typeof(Option<>)) { return none(); }
126  if (returnType.GenericTypeArguments[0] != t) { return none(); }
127 
128  //some hacky business to convert from Option<T2> to Option<TBase> when we only know T2 at runtime
129  static Option<TBase> convert<T2>(Option<T2> option) where T2 : TBase
130  => option.Select(v => (TBase)v);
131  Func<Option<TBase>, Option<TBase>> f = convert;
132  var genericArgs = f.Method.GetGenericArguments();
133  genericArgs[^1] = t;
134  var constructedConverter =
135  f.Method.GetGenericMethodDefinition().MakeGenericMethod(genericArgs);
136 
137  return constructedConverter.Invoke(null, new[] { parseFunc.Invoke(null, new object[] { input }) })
138  as Option<TBase>? ?? none();
139  }
140 
141  return derivedTypes.Select(parseOfType).FirstOrDefault(t => t.IsSome());
142  }
143 
144  public static string NameWithGenerics(this Type t)
145  {
146  if (!t.IsGenericType) { return t.Name; }
147 
148  string result = t.Name[..t.Name.IndexOf('`')];
149  result += $"<{string.Join(", ", t.GetGenericArguments().Select(NameWithGenerics))}>";
150  return result;
151  }
152  }
153 }