From fecdd9f4f7743f44fbd20f09cedf2cbaca2dddc3 Mon Sep 17 00:00:00 2001 From: Pietro420 <77608535+Pietrodjaowjao@users.noreply.github.com> Date: Wed, 15 Apr 2026 22:13:26 -0300 Subject: [PATCH] optimization and caching most of the caching stuff here can likely be removed --- MiraAPI/GameOptions/ModdedOptionsManager.cs | 30 ++++- .../Patches/Options/GameSettingMenuPatches.cs | 6 - .../Patches/Options/RoleSettingMenuPatches.cs | 17 ++- MiraAPI/PluginLoading/MiraPluginManager.cs | 105 +++++++++++------- MiraAPI/Presets/PresetManager.cs | 6 - .../Assets/LoadableAudioResourceAsset.cs | 24 ++++ .../Utilities/Assets/LoadableBundleAsset.cs | 21 +++- MiraAPI/Utilities/Assets/SpriteTools.cs | 38 ++++++- 8 files changed, 183 insertions(+), 64 deletions(-) diff --git a/MiraAPI/GameOptions/ModdedOptionsManager.cs b/MiraAPI/GameOptions/ModdedOptionsManager.cs index 3b85a73..4082cfc 100644 --- a/MiraAPI/GameOptions/ModdedOptionsManager.cs +++ b/MiraAPI/GameOptions/ModdedOptionsManager.cs @@ -21,6 +21,8 @@ namespace MiraAPI.GameOptions; public static class ModdedOptionsManager { private static readonly Dictionary OptionAttributes = []; + private static readonly Dictionary SetterAttributes = []; + private static readonly Dictionary GetterAttributes = []; private static readonly Dictionary TypeToGroup = []; internal static readonly Dictionary CreatedPlayerOptions = []; @@ -100,12 +102,20 @@ public static class ModdedOptionsManager } var setterOriginal = property.GetSetMethod(); - var setterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertySetterPatch)); - PluginSingleton.Instance.Harmony.Patch(setterOriginal, postfix: new HarmonyMethod(setterPatch)); + if (setterOriginal != null) + { + var setterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertySetterPatch)); + PluginSingleton.Instance.Harmony.Patch(setterOriginal, postfix: new HarmonyMethod(setterPatch)); + SetterAttributes[setterOriginal] = attribute; + } var getterOriginal = property.GetGetMethod(); - var getterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyGetterPatch)); - PluginSingleton.Instance.Harmony.Patch(getterOriginal, prefix: new HarmonyMethod(getterPatch)); + if (getterOriginal != null) + { + var getterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertyGetterPatch)); + PluginSingleton.Instance.Harmony.Patch(getterOriginal, prefix: new HarmonyMethod(getterPatch)); + GetterAttributes[getterOriginal] = attribute; + } OptionAttributes.Add(property, attribute); attribute.HolderOption = option; @@ -183,7 +193,11 @@ public static class ModdedOptionsManager public static void PropertySetterPatch(MethodBase __originalMethod, object value) #pragma warning restore CA1707 { - var attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod() == __originalMethod).Value; + if (!SetterAttributes.TryGetValue(__originalMethod, out var attribute)) + { + return; + } + attribute.SetValue(value); } @@ -198,7 +212,11 @@ public static class ModdedOptionsManager public static bool PropertyGetterPatch(MethodBase __originalMethod, ref object __result) #pragma warning restore CA1707 { - var attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod() == __originalMethod).Value; + if (!GetterAttributes.TryGetValue(__originalMethod, out var attribute)) + { + return true; + } + __result = attribute.GetValue(); return false; } diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs index 89e6f57..130f419 100644 --- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs @@ -5,7 +5,6 @@ using HarmonyLib; using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.PluginLoading; -using MiraAPI.Presets; using MiraAPI.Utilities.Assets; using Reactor.Utilities.Extensions; using TMPro; @@ -352,11 +351,6 @@ internal static class GameSettingMenuPatches _modifierBtnOgPos = _modifiersButton.transform.localPosition; - foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins) - { - PresetManager.LoadPresets(plugin); - } - UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab); } diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 09530d4..e39f64d 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -29,6 +29,7 @@ public static class RoleSettingMenuPatches private static Dictionary RoleGroupHidden { get; set; } = []; private static List Headers { get; set; } = []; private static List RoleOptionSettings { get; set; } = []; + private static Dictionary RoleScreenshotSprites { get; } = []; private static float ScrollerNum { get; set; } = 0.522f; @@ -506,11 +507,17 @@ public static class RoleSettingMenuPatches __instance.roleDescriptionText.transform.parent.localScale = new Vector3(0.0675f, 0.1494f, 0.5687f); labelBg.transform.localPosition = new Vector3(1.082f, 0.1054f, -2.5f); - __instance.roleScreenshot.sprite = Sprite.Create( - role.RoleScreenshot.texture, - new Rect(0, 0, 370, 230), - Vector2.one / 2, - 100); + if (!RoleScreenshotSprites.TryGetValue(role, out var screenshotSprite) || screenshotSprite == null) + { + screenshotSprite = Sprite.Create( + role.RoleScreenshot.texture, + new Rect(0, 0, 370, 230), + Vector2.one / 2, + 100); + RoleScreenshotSprites[role] = screenshotSprite; + } + + __instance.roleScreenshot.sprite = screenshotSprite; __instance.roleScreenshot.drawMode = SpriteDrawMode.Sliced; } diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index 4e29988..923e7cf 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using BepInEx.Configuration; using BepInEx.Unity.IL2CPP; using MiraAPI.Colors; @@ -30,6 +32,9 @@ namespace MiraAPI.PluginLoading; /// public sealed class MiraPluginManager { + private const BindingFlags DeclaredInstanceMembers = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly; + private const BindingFlags DeclaredStaticMembers = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly; + private readonly Dictionary _registeredPlugins = []; internal MiraPluginInfo[] RegisteredPlugins { get; private set; } = null!; @@ -49,41 +54,24 @@ public sealed class MiraPluginManager var info = new MiraPluginInfo(miraPlugin, pluginInfo); var roles = new List(); + var registrationTimer = Stopwatch.StartNew(); var oldConfigSetting = info.PluginConfig.SaveOnConfigSet; info.PluginConfig.SaveOnConfigSet = false; - foreach (var type in assembly.GetTypes()) + foreach (var type in GetAssemblyTypes(assembly)) { + if (type == null) + { + continue; + } + if (type.GetCustomAttribute() != null) { continue; } - foreach (var method in type.GetMethods()) - { - var eventAttribute = method.GetCustomAttribute(); - if (eventAttribute == null) - { - continue; - } - - if (!method.IsStatic) - { - Error($"Event method {method.Name} in {type.Name} must be static."); - continue; - } - - var parameters = method.GetParameters(); - if (parameters.Length != 1 || !parameters[0].ParameterType.IsSubclassOf(typeof(MiraEvent))) - { - Error($"Invalid event registration method {method.Name} in {type.Name}"); - continue; - } - - var paramType = parameters[0].ParameterType; - MiraEventManager.RegisterEventHandler(paramType, method, eventAttribute.Priority); - } + RegisterEvents(type); if (RegisterModifier(type, info)) { @@ -131,7 +119,9 @@ public sealed class MiraPluginManager info.SavePublicCollections(); PresetManager.CreateDefaultPreset(info); PresetManager.LoadPresets(info); - Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API."); + + registrationTimer.Stop(); + Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API in {registrationTimer.ElapsedMilliseconds}ms."); }; IL2CPPChainloader.Instance.Finished += PaletteManager.RegisterAllColors; IL2CPPChainloader.Instance.Finished += () => @@ -159,6 +149,45 @@ public sealed class MiraPluginManager return Instance._registeredPlugins.Values.FirstOrDefault(plugin => plugin.PluginId == pluginId); } + private static void RegisterEvents(Type type) + { + foreach (var method in type.GetMethods(DeclaredStaticMembers)) + { + var eventAttribute = method.GetCustomAttribute(); + if (eventAttribute == null) + { + continue; + } + + var parameters = method.GetParameters(); + if (parameters.Length != 1 || !typeof(MiraEvent).IsAssignableFrom(parameters[0].ParameterType)) + { + Error($"Invalid event registration method {method.Name} in {type.Name}"); + continue; + } + + MiraEventManager.RegisterEventHandler(parameters[0].ParameterType, method, eventAttribute.Priority); + } + } + + private static Type?[] GetAssemblyTypes(Assembly assembly) + { + try + { + return assembly.GetTypes(); + } + catch (ReflectionTypeLoadException exception) + { + return exception.Types; + } + } + + private static bool IsAutoPropertyBackingField(FieldInfo field) + { + return field.IsDefined(typeof(CompilerGeneratedAttribute), false) && + field.Name.Contains("k__BackingField", StringComparison.Ordinal); + } + private static bool RegisterGameOver(Type type) { try @@ -186,7 +215,7 @@ public sealed class MiraPluginManager return false; } - foreach (var property in type.GetProperties()) + foreach (var property in type.GetProperties(DeclaredInstanceMembers)) { if (property.GetMethod?.IsStatic == true) { @@ -209,7 +238,7 @@ public sealed class MiraPluginManager ModdedOptionsManager.RegisterAttributeOption(type, attribute, property, pluginInfo); } - foreach (var field in type.GetFields().Where(f => f.FieldType.IsAssignableTo(typeof(IModdedOption)))) + foreach (var field in type.GetFields(DeclaredInstanceMembers).Where(f => !IsAutoPropertyBackingField(f) && f.FieldType.IsAssignableTo(typeof(IModdedOption)))) { Error($"{field.Name} is a field, not a property. Use properties for options."); } @@ -264,7 +293,7 @@ public sealed class MiraPluginManager return; } - foreach (var property in type.GetProperties()) + foreach (var property in type.GetProperties(DeclaredStaticMembers)) { if (property.PropertyType != typeof(CustomColor)) { @@ -280,7 +309,7 @@ public sealed class MiraPluginManager PaletteManager.CustomColors.Add(color); } - foreach (var field in type.GetFields().Where(f => f.FieldType.IsAssignableTo(typeof(CustomColor)))) + foreach (var field in type.GetFields(DeclaredStaticMembers).Where(f => !IsAutoPropertyBackingField(f) && f.FieldType.IsAssignableTo(typeof(CustomColor)))) { Error($"{field.Name} is a field, not a property. Use properties for colors."); } @@ -332,13 +361,13 @@ public sealed class MiraPluginManager return false; } - foreach (var property in type.GetProperties()) + if (!LocalSettingsManager.TypeToTab.TryGetValue(type, out var tabInstance)) { - if (LocalSettingsManager.TypeToTab[type] is not { } tabInstance) - { - continue; - } + return false; + } + foreach (var property in type.GetProperties(DeclaredInstanceMembers)) + { if (property.GetCustomAttribute() != null && property.PropertyType.IsAssignableTo(typeof(LocalSettingsButton))) { @@ -374,7 +403,7 @@ public sealed class MiraPluginManager attribute.CreateSetting(type, configEntry); } - foreach (var field in type.GetFields().Where(f => f.FieldType.IsAssignableTo(typeof(ConfigEntryBase)) && f.GetCustomAttribute() != null)) + foreach (var field in type.GetFields(DeclaredInstanceMembers).Where(f => !IsAutoPropertyBackingField(f) && f.FieldType.IsAssignableTo(typeof(ConfigEntryBase)) && f.GetCustomAttribute() != null)) { Error($"{field.Name} is a field, not a property. Use properties for local settings."); } @@ -404,7 +433,7 @@ public sealed class MiraPluginManager return; } - foreach (var property in type.GetProperties()) + foreach (var property in type.GetProperties(DeclaredStaticMembers)) { if (property.PropertyType != typeof(MiraKeybind)) { @@ -428,7 +457,7 @@ public sealed class MiraPluginManager } } - foreach (var field in type.GetFields().Where(f => f.FieldType.IsAssignableTo(typeof(MiraKeybind)))) + foreach (var field in type.GetFields(DeclaredStaticMembers).Where(f => !IsAutoPropertyBackingField(f) && f.FieldType.IsAssignableTo(typeof(MiraKeybind)))) { Error($"{field.Name} is a field, not a property. Use properties for keybinds."); } diff --git a/MiraAPI/Presets/PresetManager.cs b/MiraAPI/Presets/PresetManager.cs index ba4289f..0c3fa7d 100644 --- a/MiraAPI/Presets/PresetManager.cs +++ b/MiraAPI/Presets/PresetManager.cs @@ -4,7 +4,6 @@ using System.Linq; using BepInEx.Configuration; using MiraAPI.PluginLoading; using MiraAPI.Roles; -using Reactor.Utilities; using UnityEngine; namespace MiraAPI.Presets; @@ -79,8 +78,6 @@ public static class PresetManager foreach (var file in Directory.GetFiles(pluginPresetPath, "*.cfg")) { - var fileName = Path.GetFileName(file); - Info($"Loading preset file {fileName}"); var presetName = Path.GetFileNameWithoutExtension(file); var presetConfig = new ConfigFile(file, false) { @@ -131,9 +128,6 @@ public static class PresetManager foreach (var file in Directory.GetFiles(pluginPresetPath, "*.cfg")) { - var fileName = Path.GetFileName(file); - Info($"Loading preset file {fileName}"); - var presetName = Path.GetFileNameWithoutExtension(file); var presetConfig = new ConfigFile(file, false) { SaveOnConfigSet = false, diff --git a/MiraAPI/Utilities/Assets/LoadableAudioResourceAsset.cs b/MiraAPI/Utilities/Assets/LoadableAudioResourceAsset.cs index a9b8eb1..c2ed55e 100644 --- a/MiraAPI/Utilities/Assets/LoadableAudioResourceAsset.cs +++ b/MiraAPI/Utilities/Assets/LoadableAudioResourceAsset.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Reflection; using System.Resources; @@ -13,6 +14,8 @@ namespace MiraAPI.Utilities.Assets; /// The path of the wave file. public class LoadableAudioResourceAsset(string path) : LoadableAsset { + private static readonly object CacheLock = new(); + private static readonly Dictionary<(Assembly Assembly, string Path), AudioClip> AudioClipCache = []; private readonly Assembly _assembly = Assembly.GetCallingAssembly(); /// @@ -23,6 +26,20 @@ public class LoadableAudioResourceAsset(string path) : LoadableAsset /// Stream failed to load. Check if the name of your asset was correct. public override AudioClip LoadAsset() { + if (LoadedAsset != null) + { + return LoadedAsset; + } + + lock (CacheLock) + { + if (AudioClipCache.TryGetValue((_assembly, path), out var cachedClip) && cachedClip != null) + { + LoadedAsset = cachedClip; + return cachedClip; + } + } + using var assetStream = _assembly.GetManifestResourceStream(path) ?? throw new MissingManifestResourceException($"Stream failed to load. Check if the asset name is correct: {path}"); @@ -92,6 +109,13 @@ public class LoadableAudioResourceAsset(string path) : LoadableAsset var audioClip = AudioClip.Create(path, samples.Length / channels, channels, sampleRate, false); audioClip.SetData(samples, 0); + LoadedAsset = audioClip; + + lock (CacheLock) + { + AudioClipCache[(_assembly, path)] = audioClip; + } + return audioClip; } diff --git a/MiraAPI/Utilities/Assets/LoadableBundleAsset.cs b/MiraAPI/Utilities/Assets/LoadableBundleAsset.cs index 6f46eef..ada43af 100644 --- a/MiraAPI/Utilities/Assets/LoadableBundleAsset.cs +++ b/MiraAPI/Utilities/Assets/LoadableBundleAsset.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Collections.Generic; using Reactor.Utilities.Extensions; using UnityEngine; @@ -12,6 +13,9 @@ namespace MiraAPI.Utilities.Assets; /// The type of the asset to be loaded. public class LoadableBundleAsset(string name, AssetBundle bundle) : LoadableAsset where T : UnityEngine.Object { + private static readonly object CacheLock = new(); + private static readonly Dictionary<(int BundleId, Type AssetType, string Name), UnityEngine.Object> BundleAssetCache = []; + /// /// Loads the asset from the asset bundle. /// @@ -24,6 +28,16 @@ public class LoadableBundleAsset(string name, AssetBundle bundle) : LoadableA return LoadedAsset; } + var cacheKey = (bundle.GetInstanceID(), typeof(T), name); + lock (CacheLock) + { + if (BundleAssetCache.TryGetValue(cacheKey, out var cachedAsset) && cachedAsset is T typedAsset) + { + LoadedAsset = typedAsset; + return typedAsset; + } + } + LoadedAsset = bundle.LoadAsset(name); if (LoadedAsset == null) @@ -31,6 +45,11 @@ public class LoadableBundleAsset(string name, AssetBundle bundle) : LoadableA throw new InvalidOperationException($"INVALID ASSET: {name}"); } + lock (CacheLock) + { + BundleAssetCache[cacheKey] = LoadedAsset; + } + return LoadedAsset; } } diff --git a/MiraAPI/Utilities/Assets/SpriteTools.cs b/MiraAPI/Utilities/Assets/SpriteTools.cs index 8bea6f1..2347632 100644 --- a/MiraAPI/Utilities/Assets/SpriteTools.cs +++ b/MiraAPI/Utilities/Assets/SpriteTools.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Collections.Generic; using System.Reflection; using Reactor.Utilities.Extensions; using UnityEngine; @@ -10,6 +11,10 @@ namespace MiraAPI.Utilities.Assets; /// public static class SpriteTools { + private static readonly object CacheLock = new(); + private static readonly Dictionary<(Assembly Assembly, string ResourcePath), Texture2D> TextureCache = []; + private static readonly Dictionary<(Assembly Assembly, string ResourcePath, float PixelsPerUnit), Sprite> SpriteCache = []; + /// /// Loads and returns a texture from a resource path using the specified assembly. /// @@ -19,11 +24,20 @@ public static class SpriteTools /// Thrown when the resource cannot be found in the specified assembly. public static Texture2D LoadTextureFromResourcePath(string resourcePath, Assembly assembly) { + lock (CacheLock) + { + if (TextureCache.TryGetValue((assembly, resourcePath), out var cachedTexture) && cachedTexture != null) + { + return cachedTexture; + } + } + var tex = new Texture2D(1, 1, TextureFormat.ARGB32, false) { wrapMode = TextureWrapMode.Clamp, }; - var myStream = assembly.GetManifestResourceStream(resourcePath); + + using var myStream = assembly.GetManifestResourceStream(resourcePath); if (myStream != null) { var buttonTexture = myStream.ReadFully(); @@ -35,6 +49,12 @@ public static class SpriteTools } tex.name = resourcePath; + + lock (CacheLock) + { + TextureCache[(assembly, resourcePath)] = tex; + } + return tex; } @@ -47,9 +67,23 @@ public static class SpriteTools /// A object created from the texture loaded from the specified resource path. public static Sprite LoadSpriteFromPath(string resourcePath, Assembly assembly, float pixelsPerUnit) { + lock (CacheLock) + { + if (SpriteCache.TryGetValue((assembly, resourcePath, pixelsPerUnit), out var cachedSprite) && cachedSprite != null) + { + return cachedSprite; + } + } + var tex = LoadTextureFromResourcePath(resourcePath, assembly); var sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), pixelsPerUnit); sprite.name = resourcePath; + + lock (CacheLock) + { + SpriteCache[(assembly, resourcePath, pixelsPerUnit)] = sprite; + } + return sprite; } }