From d250c439d096944e51b68987f07abdbefba6ecaf Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 2 Nov 2022 21:12:51 -0400 Subject: [PATCH] Support for Reflection Lookup of Mod Types (#117) * Modified reflection utils to support lookup across multiple assemblies. * Added registration of generated mod assembly to LuaCs Compiler. --- .../SharedSource/LuaCs/Cs/CsScriptLoader.cs | 42 ++++++++++++--- .../SharedSource/Utils/ReflectionUtils.cs | 52 +++++++++++++++++-- 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Cs/CsScriptLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Cs/CsScriptLoader.cs index e56b9f8a7..8a03955be 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Cs/CsScriptLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Cs/CsScriptLoader.cs @@ -87,7 +87,6 @@ namespace Barotrauma foreach (var cp in ContentPackageManager.AllPackages.Concat(ContentPackageManager.EnabledPackages.All)) { if (packagesAdded.Contains(cp)) { continue; } - var path = $"{Path.GetFullPath(Path.GetDirectoryName(cp.Path)).Replace('\\', '/')}/"; if (ShouldRun(cp, path)) { @@ -102,7 +101,6 @@ namespace Barotrauma { paths.Add(cp.Name, path); } - packagesAdded.Add(cp); } } @@ -169,14 +167,14 @@ namespace Barotrauma return syntaxTrees; } - public List Compile() + public List Compile() { IEnumerable syntaxTrees = ParseSources(); var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) .WithMetadataImportOptions(MetadataImportOptions.All) .WithOptimizationLevel(OptimizationLevel.Release) - .WithAllowUnsafe(false); + .WithAllowUnsafe(true); var compilation = CSharpCompilation.Create(CsScriptAssembly, syntaxTrees, defaultReferences, options); using (var mem = new MemoryStream()) @@ -188,7 +186,9 @@ namespace Barotrauma string errStr = "CS MODS NOT LOADED | Compilation errors:"; foreach (Diagnostic diagnostic in failures) + { errStr += $"\n{diagnostic}"; + } LuaCsSetup.PrintCsError(errStr); } else @@ -200,7 +200,16 @@ namespace Barotrauma if (Assembly != null) { - return Assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(ACsMod))).ToList(); + RegisterAssemblyWithNativeGame(Assembly); + try + { + return Assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(ACsMod))).ToList(); + } + catch (ReflectionTypeLoadException re) + { + LuaCsSetup.PrintCsError($"Unable to load CsMod Types. {re.Message}"); + throw re; + } } else { @@ -208,6 +217,23 @@ namespace Barotrauma } } + /// + /// This function should be used whenever a new assembly is created. Wrapper to allow more complicated setup later if need be. + /// + private static void RegisterAssemblyWithNativeGame(Assembly assembly) + { + Barotrauma.ReflectionUtils.AddNonAbstractAssemblyTypes(assembly); + } + + /// + /// This function should be used whenever a new assembly is about to be destroyed/unloaded. Wrapper to allow more complicated setup later if need be. + /// + /// Assembly to remove + private static void UnregisterAssemblyFromNativeGame(Assembly assembly) + { + Barotrauma.ReflectionUtils.RemoveAssemblyFromCache(assembly); + } + private static string[] DirSearch(string sDir) { if (!Directory.Exists(sDir)) @@ -220,7 +246,11 @@ namespace Barotrauma public void Clear() { - Assembly = null; + if (Assembly != null) + { + UnregisterAssemblyFromNativeGame(Assembly); + Assembly = null; + } } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/ReflectionUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/ReflectionUtils.cs index f65b53aab..7b6c6a26f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/ReflectionUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/ReflectionUtils.cs @@ -12,17 +12,61 @@ namespace Barotrauma private static readonly Dictionary> cachedNonAbstractTypes = new Dictionary>(); + public static IEnumerable GetDerivedNonAbstract() { Assembly assembly = typeof(T).Assembly; if (!cachedNonAbstractTypes.ContainsKey(assembly)) { - cachedNonAbstractTypes[assembly] = assembly.GetTypes() - .Where(t => !t.IsAbstract).ToImmutableArray(); + AddNonAbstractAssemblyTypes(assembly); } - return cachedNonAbstractTypes[assembly].Where(t => t.IsSubclassOf(typeof(T))); + + #warning TODO: Add safety checks in case an assembly is unloaded without being removed from the cache. + + List types = new List(); + foreach (var typearr in cachedNonAbstractTypes) + { + types = types.Concat(typearr.Value.Where(t => t.IsSubclassOf(typeof(T)))).ToList(); + } + + return types; } + /// + /// 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); + } + + try + { + if (!cachedNonAbstractTypes.TryAdd(assembly, assembly.GetTypes().Where(t => !t.IsAbstract).ToImmutableArray())) + DebugConsole.LogError($"ReflectionUtils::AddNonAbstractAssemblyTypes() | Unable to add types from Assembly to cache."); + } + 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); + public static Option ParseDerived(TInput input) where TInput : notnull { static Option none() => Option.None(); @@ -60,4 +104,4 @@ namespace Barotrauma return derivedTypes.Select(parseOfType).FirstOrDefault(t => t.IsSome()) ?? none(); } } -} \ No newline at end of file +}