#nullable enable using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Reflection; namespace Barotrauma { public static class ReflectionUtils { private static readonly ConcurrentDictionary> CachedNonAbstractTypes = new(); private static readonly ConcurrentDictionary> TypeSearchCache = new(); public static IEnumerable GetDerivedNonAbstract() { Type t = typeof(T); string typeName = t.FullName ?? t.Name; // search quick lookup cache if (TypeSearchCache.TryGetValue(typeName, out var value)) { return value; } // doesn't exist so let's add it. Assembly assembly = typeof(T).Assembly; if (!CachedNonAbstractTypes.ContainsKey(assembly)) { AddNonAbstractAssemblyTypes(assembly); } // build cache from registered assemblies' types. var list = CachedNonAbstractTypes.Values .SelectMany(arr => arr.Where(type => type.IsSubclassOf(t))) .ToImmutableArray(); if (list.Length == 0) { return ImmutableArray.Empty; // No types, don't add to cache } if (!TypeSearchCache.TryAdd(typeName, list)) { DebugConsole.LogError($"ReflectionUtils::AddNonAbstractAssemblyTypes() | Error while adding to quick lookup cache."); } return list; } /// /// Adds an assembly's Non-Abstract Types to the cache for Barotrauma's Type lookup. /// /// Assembly to be added /// Whether or not to overwrite an entry if the assembly already exists within it. public static void AddNonAbstractAssemblyTypes(Assembly assembly, bool overwrite = false) { if (CachedNonAbstractTypes.ContainsKey(assembly)) { if (!overwrite) { DebugConsole.LogError( $"ReflectionUtils::AddNonAbstractAssemblyTypes() | The assembly [{assembly.GetName()}] already exists in the cache."); return; } CachedNonAbstractTypes.Remove(assembly, out _); } try { if (!CachedNonAbstractTypes.TryAdd(assembly, assembly.GetSafeTypes().Where(t => !t.IsAbstract).ToImmutableArray())) { DebugConsole.LogError($"ReflectionUtils::AddNonAbstractAssemblyTypes() | Unable to add types from Assembly to cache."); } else { TypeSearchCache.Clear(); // Needs to be rebuilt to include potential new types } } catch (ReflectionTypeLoadException e) { DebugConsole.LogError($"ReflectionUtils::AddNonAbstractAssemblyTypes() | RTFException: Unable to load Assembly Types from {assembly.GetName()}."); } } /// /// Removes an assembly from the cache for Barotrauma's Type lookup. /// /// Assembly to remove. public static void RemoveAssemblyFromCache(Assembly assembly) { CachedNonAbstractTypes.Remove(assembly, out _); TypeSearchCache.Clear(); } /// /// Clears all cached assembly data and rebuilds types list only to include base Barotrauma types. /// internal static void ResetCache() { CachedNonAbstractTypes.Clear(); CachedNonAbstractTypes.TryAdd(typeof(ReflectionUtils).Assembly, typeof(ReflectionUtils).Assembly.GetSafeTypes().ToImmutableArray()); TypeSearchCache.Clear(); } public static Option ParseDerived(TInput input) where TInput : notnull where TBase : notnull { static Option none() => Option.None(); var derivedTypes = GetDerivedNonAbstract(); Option parseOfType(Type t) { //every TBase type is expected to have a method with the following signature: // public static Option Parse(TInput str) var parseFunc = t.GetMethod("Parse", BindingFlags.Public | BindingFlags.Static); if (parseFunc is null) { return none(); } var parameters = parseFunc.GetParameters(); if (parameters.Length != 1) { return none(); } var returnType = parseFunc.ReturnType; if (!returnType.IsConstructedGenericType) { return none(); } if (returnType.GetGenericTypeDefinition() != typeof(Option<>)) { return none(); } if (returnType.GenericTypeArguments[0] != t) { return none(); } //some hacky business to convert from Option to Option when we only know T2 at runtime static Option convert(Option option) where T2 : TBase => option.Select(v => (TBase)v); Func, Option> f = convert; var genericArgs = f.Method.GetGenericArguments(); genericArgs[^1] = t; var constructedConverter = f.Method.GetGenericMethodDefinition().MakeGenericMethod(genericArgs); return constructedConverter.Invoke(null, new[] { parseFunc.Invoke(null, new object[] { input }) }) as Option? ?? none(); } return derivedTypes.Select(parseOfType).FirstOrDefault(t => t.IsSome()); } public static string NameWithGenerics(this Type t) { if (!t.IsGenericType) { return t.Name; } string result = t.Name[..t.Name.IndexOf('`')]; result += $"<{string.Join(", ", t.GetGenericArguments().Select(NameWithGenerics))}>"; return result; } } }