From 83be29d6a2fda0d7207cc37b85c9afeac35df705 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Sat, 13 Jun 2026 12:10:21 -0500 Subject: [PATCH] Add wip caching --- .../ModifierDisplayComponent.cs | 4 +- MiraAPI/Patches/Freeplay/TaskAdderPatches.cs | 3 +- MiraAPI/Patches/HudManagerPatches.cs | 3 +- MiraAPI/Patches/Menu/HatsTabPatches.cs | 5 +- MiraAPI/Patches/Menu/NameplatesTabPatches.cs | 5 +- MiraAPI/Patches/Menu/SkinsTabPatches.cs | 5 +- MiraAPI/Patches/Menu/VisorsTabPatches.cs | 5 +- .../Patches/Options/GameOptionsMenuPatch.cs | 84 ++++++-- .../Patches/Options/GamePresetsTabPatches.cs | 2 +- .../Patches/Options/GameSettingMenuPatches.cs | 184 ++++++++++++++---- .../Patches/Options/RoleSettingMenuPatches.cs | 53 ++--- MiraAPI/Patches/Roles/HudManagerPatches.cs | 5 +- MiraAPI/Patches/Roles/RoleManagerPatches.cs | 3 +- MiraAPI/Presets/PresetManager.cs | 3 +- MiraAPI/Presets/SavePresetPopup.cs | 2 +- MiraAPI/Roles/CustomRoleManager.cs | 2 +- MiraAPI/Utilities/Extensions.cs | 124 ++++++++++-- MiraAPI/Utilities/Helpers.cs | 2 +- 18 files changed, 368 insertions(+), 126 deletions(-) diff --git a/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs b/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs index e47d775..18bd476 100644 --- a/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs +++ b/MiraAPI/Modifiers/ModifierDisplay/ModifierDisplayComponent.cs @@ -138,7 +138,7 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr) internal void DestroyComponent(ModifierUiComponent component) { _modifiers.Remove(component.Modifier!); - component.gameObject.FakeDestroy(); + component.gameObject.DeepDestroy(); RefreshModifiers(); } @@ -208,7 +208,7 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr) continue; } - mod.Value.gameObject.FakeDestroy(); + mod.Value.gameObject.DeepDestroy(); _modifiers.Remove(mod.Key); } diff --git a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs index ea13199..f1612d9 100644 --- a/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs +++ b/MiraAPI/Patches/Freeplay/TaskAdderPatches.cs @@ -276,8 +276,9 @@ public static class TaskAdderPatches __instance.transform.FindChild("TitleText_TMP")?.gameObject.DestroyImmediate(); } - __instance.ActiveItems.ToArray().Do(x => x.gameObject.FakeDestroy()); + __instance.ActiveItems.ToArray().Do(x => x.gameObject.DeepDestroy(false)); __instance.ActiveItems.Clear(); + Utilities.Extensions.ClearGarbageCollector(); float num = 0f; float num2 = 0f; diff --git a/MiraAPI/Patches/HudManagerPatches.cs b/MiraAPI/Patches/HudManagerPatches.cs index 7af625e..e3a6b91 100644 --- a/MiraAPI/Patches/HudManagerPatches.cs +++ b/MiraAPI/Patches/HudManagerPatches.cs @@ -143,8 +143,9 @@ public static class HudManagerPatches foreach (var t in BottomLeft.GetComponentsInChildren(true)) { - t.gameObject.FakeDestroy(); + t.gameObject.DeepDestroy(false); } + Utilities.Extensions.ClearGarbageCollector(); var gridArrange = BottomLeft.GetComponent(); var aspectPosition = BottomLeft.GetComponent(); diff --git a/MiraAPI/Patches/Menu/HatsTabPatches.cs b/MiraAPI/Patches/Menu/HatsTabPatches.cs index 98bbc92..25eca33 100644 --- a/MiraAPI/Patches/Menu/HatsTabPatches.cs +++ b/MiraAPI/Patches/Menu/HatsTabPatches.cs @@ -92,9 +92,10 @@ public static class HatsTabPatches if (loadRoutine != null) Coroutines.Stop(loadRoutine); hatIndex = 0; - foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.FakeDestroy(); + foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.DeepDestroy(false); __instance.ColorChips.Clear(); - __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.FakeDestroy()); + __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.DeepDestroy(false)); + Utilities.Extensions.ClearGarbageCollector(); var groupNameText = __instance.GetComponentInChildren(false); diff --git a/MiraAPI/Patches/Menu/NameplatesTabPatches.cs b/MiraAPI/Patches/Menu/NameplatesTabPatches.cs index 6fd1031..0875cb2 100644 --- a/MiraAPI/Patches/Menu/NameplatesTabPatches.cs +++ b/MiraAPI/Patches/Menu/NameplatesTabPatches.cs @@ -81,9 +81,10 @@ public static class NameplatesTabPatches private static void GenerateHats(NameplatesTab __instance, int page) { - foreach (ColorChip instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.FakeDestroy(); + foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.DeepDestroy(false); __instance.ColorChips.Clear(); - __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.FakeDestroy()); + __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.DeepDestroy(false)); + Utilities.Extensions.ClearGarbageCollector(); var groupNameText = __instance.GetComponentInChildren(false); diff --git a/MiraAPI/Patches/Menu/SkinsTabPatches.cs b/MiraAPI/Patches/Menu/SkinsTabPatches.cs index bc8e64e..24bec7c 100644 --- a/MiraAPI/Patches/Menu/SkinsTabPatches.cs +++ b/MiraAPI/Patches/Menu/SkinsTabPatches.cs @@ -83,9 +83,10 @@ public static class SkinsTabPatches private static void GenerateHats(SkinsTab __instance, int page) { - foreach (ColorChip instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.FakeDestroy(); + foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.DeepDestroy(false); __instance.ColorChips.Clear(); - __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.FakeDestroy()); + __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.DeepDestroy(false)); + Utilities.Extensions.ClearGarbageCollector(); var groupNameText = __instance.GetComponentInChildren(false); diff --git a/MiraAPI/Patches/Menu/VisorsTabPatches.cs b/MiraAPI/Patches/Menu/VisorsTabPatches.cs index f3a8d93..86bfa24 100644 --- a/MiraAPI/Patches/Menu/VisorsTabPatches.cs +++ b/MiraAPI/Patches/Menu/VisorsTabPatches.cs @@ -81,9 +81,10 @@ public static class VisorsTabPatches private static void GenerateHats(VisorsTab __instance, int page) { - foreach (ColorChip instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.FakeDestroy(); + foreach (var instanceColorChip in __instance.ColorChips) instanceColorChip.gameObject.DeepDestroy(false); __instance.ColorChips.Clear(); - __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.FakeDestroy()); + __instance.scroller.Inner.GetComponentsInChildren().Do(x => x.gameObject.DeepDestroy(false)); + Utilities.Extensions.ClearGarbageCollector(); var groupNameText = __instance.GetComponentInChildren(false); diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index dee3fa3..485a906 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -139,11 +139,25 @@ internal static class GameOptionsMenuPatch private static void CustomMenuOneCreate(GameOptionsMenu menu) { - var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x => x.ParentMenu == MenuCategory.CustomOne) ?? []; - foreach (var group in groups) + var existing = GameSettingMenuPatches.CachedGameSettings.FirstOrDefault(x => x.ModId == GameSettingMenuPatches.SelectedModIdx && x.Category == MenuCategory.CustomOne); + if (existing != null) { - CreateGroup(menu, group); + GameSettingMenuPatches.ShowGameOptions(existing, menu); + } + else + { + var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups + .Where(x => x.ParentMenu == MenuCategory.CustomOne) ?? []; + foreach (var group in groups) + { + CreateGroup(menu, group); + } + GameSettingMenuPatches.AddGameSettings( + MenuCategory.CustomOne, + menu.Children, + menu.settingsContainer + .GetComponentsInChildren() + .ToList()); } } @@ -160,11 +174,25 @@ internal static class GameOptionsMenuPatch private static void CustomMenuTwoCreate(GameOptionsMenu menu) { - var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x => x.ParentMenu == MenuCategory.CustomTwo) ?? []; - foreach (var group in groups) + var existing = GameSettingMenuPatches.CachedGameSettings.FirstOrDefault(x => x.ModId == GameSettingMenuPatches.SelectedModIdx && x.Category == MenuCategory.CustomTwo); + if (existing != null) { - CreateGroup(menu, group); + GameSettingMenuPatches.ShowGameOptions(existing, menu); + } + else + { + var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups + .Where(x => x.ParentMenu == MenuCategory.CustomTwo) ?? []; + foreach (var group in groups) + { + CreateGroup(menu, group); + } + GameSettingMenuPatches.AddGameSettings( + MenuCategory.CustomTwo, + menu.Children, + menu.settingsContainer + .GetComponentsInChildren() + .ToList()); } } @@ -181,11 +209,25 @@ internal static class GameOptionsMenuPatch private static void ModifiersCreate(GameOptionsMenu menu) { - var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? []; - foreach (var group in groups) + var existing = GameSettingMenuPatches.CachedGameSettings.FirstOrDefault(x => x.ModId == GameSettingMenuPatches.SelectedModIdx && x.Category == MenuCategory.Modifiers); + if (existing != null) { - CreateGroup(menu, group); + GameSettingMenuPatches.ShowGameOptions(existing, menu); + } + else + { + var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups + .Where(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true) ?? []; + foreach (var group in groups) + { + CreateGroup(menu, group); + } + GameSettingMenuPatches.AddGameSettings( + MenuCategory.Modifiers, + menu.Children, + menu.settingsContainer + .GetComponentsInChildren() + .ToList()); } } @@ -217,12 +259,20 @@ internal static class GameOptionsMenuPatch return false; } - var filteredGroups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups - .Where(x => x.OptionableType == null && !x.ShowInModifiersMenu && x.ParentMenu == MenuCategory.Roles) ?? []; - - foreach (var group in filteredGroups) + var existing = GameSettingMenuPatches.CachedGameSettings.FirstOrDefault(x => x.ModId == GameSettingMenuPatches.SelectedModIdx && x.Category == MenuCategory.Game); + if (existing != null) { - CreateGroup(__instance, group); + GameSettingMenuPatches.ShowGameOptions(existing, __instance); + } + else + { + var filteredGroups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups + .Where(x => x.OptionableType == null && !x.ShowInModifiersMenu && x.ParentMenu == MenuCategory.Roles) ?? []; + + foreach (var group in filteredGroups) + { + CreateGroup(__instance, group); + } } return false; diff --git a/MiraAPI/Patches/Options/GamePresetsTabPatches.cs b/MiraAPI/Patches/Options/GamePresetsTabPatches.cs index bae9b8b..a4b6cca 100644 --- a/MiraAPI/Patches/Options/GamePresetsTabPatches.cs +++ b/MiraAPI/Patches/Options/GamePresetsTabPatches.cs @@ -262,7 +262,7 @@ internal static class GamePresetsTabPatches iconSpriteRend.sprite = MiraAssets.RefreshIcon.LoadAsset(); Helpers.DivideSize(refreshButton.gameObject, 2f); - refreshButton.buttonText.gameObject.FakeDestroy(); + refreshButton.buttonText.gameObject.DeepDestroy(); refreshButton.OnClick = new Button.ButtonClickedEvent(); refreshButton.OnClick.AddListener((UnityAction)Refresh); diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs index 89e6f57..478a945 100644 --- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs @@ -6,6 +6,7 @@ using MiraAPI.GameOptions; using MiraAPI.Modifiers; using MiraAPI.PluginLoading; using MiraAPI.Presets; +using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Utilities.Extensions; using TMPro; @@ -40,6 +41,140 @@ internal static class GameSettingMenuPatches private static PassiveButton? _customTwoButton; private static GameObject? _nextModButton; private static GameObject? _previousModButton; + public static List CachedGameSettings { get; } = []; + public static List CachedRoleSettings { get; } = []; + + public sealed class GameSettingsCache + { + public int ModId { get; set; } + public MenuCategory Category { get; set; } = MenuCategory.Game; + public Il2CppSystem.Collections.Generic.List Options { get; set; } + public List CategoryHeaders { get; set; } + public GameObject CacheHolder { get; set; } + } + public static GameSettingsCache GetCurrentSettings(MenuCategory category) + { + var existing = CachedGameSettings.FirstOrDefault(x => x.ModId == SelectedModIdx && x.Category == category); + if (existing != null) + { + return existing; + } + + var menu = category switch + { + MenuCategory.Modifiers => _modifiersTab!, + MenuCategory.CustomOne => _customOneTab!, + MenuCategory.CustomTwo => _customTwoTab!, + _ => GameSettingMenu.Instance.GameSettingsTab, + }; + return AddGameSettings( + category, + menu.Children, + menu.settingsContainer + .GetComponentsInChildren() + .ToList()); + } + public static GameSettingsCache AddGameSettings(MenuCategory category, Il2CppSystem.Collections.Generic.List options, List categoryHeaders) + { + var modName = SelectedMod?.MiraPlugin.OptionsTitleText ?? "Vanilla"; + var newHolder = new GameObject($"{modName} {category}"); + newHolder.DontUnload().DontDestroy(); + newHolder.SetActive(false); + var newCache = new GameSettingsCache + { + ModId = SelectedModIdx, + Category = category, + Options = options, + CategoryHeaders = categoryHeaders, + CacheHolder = newHolder, + }; + CachedGameSettings.Add(newCache); + return newCache; + } + public static void HideGameOptions(GameSettingsCache cached, GameOptionsMenu gameOptMenu) + { + foreach (var obj in gameOptMenu.scrollBar.Inner.GetComponentsInChildren()) + { + if (obj.gameObject.name == "MapPicker" || !obj.gameObject) + { + continue; + } + obj.SetParent(cached.CacheHolder.transform); + } + } + public static void ShowGameOptions(GameSettingsCache cached, GameOptionsMenu gameOptMenu) + { + foreach (var obj in cached.CacheHolder.GetComponentsInChildren()) + { + if (!obj.gameObject) + { + continue; + } + obj.SetParent(gameOptMenu.scrollBar.Inner); + } + } + public sealed class RoleSettingsCache + { + public int ModId { get; set; } + public Il2CppSystem.Collections.Generic.List Options { get; set; } + public List RoleHeaders { get; set; } + public List RoleOptions { get; set; } + public List CategoryHeaders { get; set; } + public GameObject CacheHolder { get; set; } + } + public static RoleSettingsCache GetCurrentRoleSettings() + { + var existing = CachedRoleSettings.FirstOrDefault(x => x.ModId == SelectedModIdx); + if (existing != null) + { + return existing; + } + + var menu = GameSettingMenu.Instance.RoleSettingsTab; + return AddRoleSettings( + menu.advancedSettingChildren, + menu.RoleChancesSettings + .GetComponentsInChildren() + .ToList()); + } + public static RoleSettingsCache AddRoleSettings(Il2CppSystem.Collections.Generic.List options, List categoryHeaders) + { + var modName = SelectedMod?.MiraPlugin.OptionsTitleText ?? "Vanilla"; + var newHolder = new GameObject($"{modName} Roles"); + newHolder.DontUnload().DontDestroy(); + newHolder.SetActive(false); + var newCache = new RoleSettingsCache + { + ModId = SelectedModIdx, + Options = options, + CategoryHeaders = categoryHeaders, + CacheHolder = newHolder, + }; + CachedRoleSettings.Add(newCache); + return newCache; + } + public static void HideRoleOptions(RoleSettingsCache cached, RolesSettingsMenu gameOptMenu) + { + foreach (var obj in gameOptMenu.scrollBar.Inner.GetComponentsInChildren()) + { + if (obj.gameObject.name == "MapPicker" || !obj.gameObject) + { + continue; + } + obj.SetParent(cached.CacheHolder.transform); + } + } + public static void ShowRoleOptions(RoleSettingsCache cached, RolesSettingsMenu gameOptMenu) + { + foreach (var obj in cached.CacheHolder.GetComponentsInChildren()) + { + if (!obj.gameObject) + { + continue; + } + obj.SetParent(gameOptMenu.scrollBar.Inner); + } + } private static Dictionary OptionsPositions { get; } = []; private static Dictionary ModifiersPositions { get; } = []; @@ -564,21 +699,6 @@ internal static class GameSettingMenuPatches CleanupTab(settings, roles); } - private static void ClearOptions(Il2CppSystem.Collections.Generic.List options) - { - foreach (var child in options) - { - if (child.TryCast() || !child.gameObject) - { - continue; - } - - child.gameObject.DestroyImmediate(); - } - - options.Clear(); - } - private static void CleanupTab(GameOptionsMenu settings, RolesSettingsMenu roles) { if (roles.roleChances != null) @@ -608,24 +728,10 @@ internal static class GameSettingMenuPatches void CleanupRoleSettings(RolesSettingsMenu rolesMenu) { - if (rolesMenu.advancedSettingChildren != null) - { - ClearOptions(rolesMenu.advancedSettingChildren); - rolesMenu.advancedSettingChildren = null; - } + HideRoleOptions(GetCurrentRoleSettings(), rolesMenu); + rolesMenu.advancedSettingChildren = null; - rolesMenu.RoleChancesSettings - .transform - .GetComponentsInChildren() - .ToList() - .ForEach(header => header.gameObject.DestroyImmediate()); - - foreach (var role in rolesMenu.roleChances) - { - role.gameObject.DestroyImmediate(); - } - - rolesMenu.roleChances?.Clear(); + rolesMenu.roleChances.Clear(); rolesMenu.AdvancedRolesSettings.gameObject.SetActive(false); rolesMenu.RoleChancesSettings.gameObject.SetActive(true); @@ -643,14 +749,16 @@ internal static class GameSettingMenuPatches void CleanupSettings(GameOptionsMenu gameOptMenu, MenuCategory menuCategory = MenuCategory.Roles) { - ClearOptions(gameOptMenu.Children); + if (menuCategory is MenuCategory.Roles) + { + HideRoleOptions(GetCurrentRoleSettings(), gameOptMenu.RolesMenu); + } + else + { + HideGameOptions(GetCurrentSettings(menuCategory), gameOptMenu); + } gameOptMenu.Children = null; - gameOptMenu.settingsContainer - .GetComponentsInChildren() - .ToList() - .ForEach(header => header.gameObject.DestroyImmediate()); - gameOptMenu.Initialize(); var positions = OptionsPositions; switch (menuCategory) diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 6e28cd9..18e73eb 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -53,23 +53,11 @@ public static class RoleSettingMenuPatches [HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))] public static bool SetQuotaTabPatch(RolesSettingsMenu __instance) { - foreach (var obj in Headers) - { - if (!obj) - { - continue; - } - obj.FakeDestroy(); - } + GameSettingMenuPatches.HideRoleOptions(GameSettingMenuPatches.GetCurrentRoleSettings(), __instance); + __instance.advancedSettingChildren = null; + Headers.Clear(); - foreach (var obj in RoleOptionSettings) - { - if (!obj) - { - continue; - } - obj.gameObject.FakeDestroy(); - } + __instance.roleChances.Clear(); RoleOptionSettings.Clear(); CurrentRole = null; CurrentRoleOptions = null; @@ -147,6 +135,7 @@ public static class RoleSettingMenuPatches num3++; } + Utilities.Extensions.ClearGarbageCollector(); return false; } @@ -168,6 +157,7 @@ public static class RoleSettingMenuPatches if (roleGroups is null) { + Utilities.Extensions.ClearGarbageCollector(); return false; } @@ -218,9 +208,9 @@ public static class RoleSettingMenuPatches var blankLabel = quotaInst.transform.FindChild("BlankLabel").gameObject; var chanceLabel = quotaInst.transform.FindChild("Chance Label").gameObject; var countLabel = quotaInst.transform.FindChild("# Label").gameObject; - blankLabel.FakeDestroy(); - chanceLabel.FakeDestroy(); - countLabel.FakeDestroy(); + blankLabel.DeepDestroy(false); + chanceLabel.DeepDestroy(false); + countLabel.DeepDestroy(false); categoryHeaderMasked.Background.sprite = MiraAssets.CategoryHeader.LoadAsset(); categoryHeaderMasked.Background.sprite.texture.filterMode = FilterMode.Bilinear; @@ -303,23 +293,10 @@ public static class RoleSettingMenuPatches { RoleGroupHidden[group] = !groupHidden; } - foreach (var obj in Headers) - { - if (!obj) - { - continue; - } - obj.FakeDestroy(); - } + GameSettingMenuPatches.HideRoleOptions(GameSettingMenuPatches.GetCurrentRoleSettings(), __instance); + __instance.advancedSettingChildren = null; Headers.Clear(); - foreach (var obj in RoleOptionSettings) - { - if (!obj) - { - continue; - } - obj.gameObject.FakeDestroy(); - } + __instance.roleChances.Clear(); RoleOptionSettings.Clear(); __instance.SetQuotaTab(); })); @@ -330,6 +307,7 @@ public static class RoleSettingMenuPatches ScrollerNum -= 0.4f; } } + Utilities.Extensions.ClearGarbageCollector(); __instance.scrollBar.SetScrollBounds(__instance); return false; @@ -444,8 +422,9 @@ public static class RoleSettingMenuPatches { foreach (var optBehaviour in __instance.AdvancedRolesSettings.GetComponentsInChildren()) { - optBehaviour.gameObject.FakeDestroy(); + optBehaviour.gameObject.DeepDestroy(false); } + Utilities.Extensions.ClearGarbageCollector(); CurrentRole = role; __instance.advancedSettingChildren.Clear(); @@ -640,7 +619,7 @@ public static class RoleSettingMenuPatches var newButton = Object.Instantiate(roleOptionSetting.buttons[0], roleOptionSetting.transform); newButton.name = "ConfigButton"; newButton.transform.localPosition = new Vector3(0.4473f, -0.3f, -2f); - newButton.transform.FindChild("Text_TMP").gameObject.FakeDestroy(); + newButton.transform.FindChild("Text_TMP").gameObject.DeepDestroy(); newButton.activeSprites.Destroy(); var btnRend = newButton.transform.FindChild("ButtonSprite").GetComponent(); diff --git a/MiraAPI/Patches/Roles/HudManagerPatches.cs b/MiraAPI/Patches/Roles/HudManagerPatches.cs index cc46c13..cb415b2 100644 --- a/MiraAPI/Patches/Roles/HudManagerPatches.cs +++ b/MiraAPI/Patches/Roles/HudManagerPatches.cs @@ -36,7 +36,7 @@ public static class HudManagerPatches if (role is ICustomRole customRole) { __instance.KillButton.ToggleVisible(isActive && customRole.Configuration.UseVanillaKillButton && !flag); - __instance.ImpostorVentButton.ToggleVisible(isActive && (customRole.Configuration.CanUseVent) && !flag); + __instance.ImpostorVentButton.ToggleVisible(isActive && customRole.Configuration.CanUseVent && !flag); __instance.SabotageButton.gameObject.SetActive(isActive && customRole.Configuration.CanUseSabotage); } @@ -73,8 +73,7 @@ public static class HudManagerPatches } else if (RoleTab != null) { - RoleTab.gameObject.FakeDestroy(); - RoleTab = null; + RoleTab.gameObject.DeepDestroy(); } } } diff --git a/MiraAPI/Patches/Roles/RoleManagerPatches.cs b/MiraAPI/Patches/Roles/RoleManagerPatches.cs index 713ffdf..b9dcd4b 100644 --- a/MiraAPI/Patches/Roles/RoleManagerPatches.cs +++ b/MiraAPI/Patches/Roles/RoleManagerPatches.cs @@ -6,6 +6,7 @@ using MiraAPI.Events; using MiraAPI.Events.Vanilla.Gameplay; using MiraAPI.Modifiers; using MiraAPI.Roles; +using MiraAPI.Utilities; using UnityEngine; namespace MiraAPI.Patches.Roles; @@ -32,7 +33,7 @@ public static class RoleManagerPatches if (data.Role) { data.Role.Deinitialize(targetPlayer); - Object.Destroy(data.Role.gameObject); + data.Role.gameObject.DeepDestroy(); } var roleBehaviour = Object.Instantiate(__instance.AllRoles.ToArray().First(r => r.Role == roleType), data.gameObject.transform); roleBehaviour.Initialize(targetPlayer); diff --git a/MiraAPI/Presets/PresetManager.cs b/MiraAPI/Presets/PresetManager.cs index 2ef0c55..97561fe 100644 --- a/MiraAPI/Presets/PresetManager.cs +++ b/MiraAPI/Presets/PresetManager.cs @@ -62,9 +62,10 @@ public static class PresetManager { if (btn != null) { - btn.FakeDestroy(); + btn.DeepDestroy(false); } } + Utilities.Extensions.ClearGarbageCollector(); plugin.InternalPresets.Clear(); if (!Directory.Exists(PresetDirectory)) diff --git a/MiraAPI/Presets/SavePresetPopup.cs b/MiraAPI/Presets/SavePresetPopup.cs index 721f46c..23ce48b 100644 --- a/MiraAPI/Presets/SavePresetPopup.cs +++ b/MiraAPI/Presets/SavePresetPopup.cs @@ -26,7 +26,7 @@ public class SavePresetPopup(nint cppPtr) : Minigame(cppPtr) // Cleanup holder object public void OnDestroy() { - transform.parent.gameObject.FakeDestroy(); + transform.parent.gameObject.DeepDestroy(); } public override void Close() diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index f306c68..9a4dfee 100644 --- a/MiraAPI/Roles/CustomRoleManager.cs +++ b/MiraAPI/Roles/CustomRoleManager.cs @@ -123,7 +123,7 @@ public static class CustomRoleManager if (roleBehaviour is not ICustomRole customRole) { - roleBehaviour.gameObject.Destroy(); + roleBehaviour.gameObject.DeepDestroy(); return null; } diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index b594598..231051e 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -53,7 +54,7 @@ public static class Extensions bool isComplete; if (self.amClosing == Minigame.CloseState.Closing) { - self.gameObject.FakeDestroy(); + self.gameObject.DeepDestroy(); return; } if (self.CloseSound && Constants.ShouldPlaySfx()) @@ -220,22 +221,119 @@ public static class Extensions } /// - /// Hides the object away rather than destroy it to prevent memory leaks. + /// Destroys the object properly. /// - /// The object to hide. - public static void FakeDestroy(this GameObject obj) + /// The object to destroy. + /// Whether to run the garbage collector immediately. + public static void DeepDestroy(this GameObject? obj, bool clearGc = true) { - obj.hideFlags = HideFlags.HideAndDontSave; - if (!toDestroyHolder) - { - toDestroyHolder = new GameObject("ToDestroyHolder"); - toDestroyHolder.gameObject.SetActive(false); - } - obj.transform.SetParent(toDestroyHolder.transform); - obj.SetActive(false); + Coroutines.Start(Nuke(obj, clearGc)); } - private static GameObject toDestroyHolder; + private static IEnumerator Nuke(GameObject? go, bool clearGc) + { + if (go == null) + yield break; + + try + { + go.transform.SetParent(null, false); + } + catch + { + // ignored + } + + try + { + go.SetActive(false); + } + catch + { + // ignored + } + + foreach (var mb in go.GetComponentsInChildren(true)) + { + if (mb == null) + continue; + + try + { + mb.StopAllCoroutines(); + } + catch + { + // ignored + } + + try + { + mb.enabled = false; + } + catch + { + // ignored + } + } + + foreach (var renderer in go.GetComponentsInChildren(true)) + { + if (renderer == null) + continue; + + try + { + foreach (var mat in renderer.materials) + { + if (mat != null) + UnityEngine.Object.Destroy(mat); + } + } + catch + { + // ignored + } + } + + foreach (var filter in go.GetComponentsInChildren(true)) + { + if (filter == null) + continue; + + try + { + var mesh = filter.mesh; + if (mesh != null) + UnityEngine.Object.Destroy(mesh); + } + catch + { + // ignored + } + } + + UnityEngine.Object.Destroy(go); + yield return null; + if (clearGc) + { + yield return CoFreeResources(); + } + } + + public static void ClearGarbageCollector() + { + Coroutines.Start(CoFreeResources()); + } + + private static IEnumerator CoFreeResources() + { + yield return Resources.UnloadUnusedAssets(); + + GC.Collect(0, GCCollectionMode.Forced, blocking: true); + GC.WaitForPendingFinalizers(); + GC.Collect(0, GCCollectionMode.Forced, blocking: true); + } /// /// Gets a cache of player's vote data components to improve performance. diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index b537ef0..83efd35 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -539,7 +539,7 @@ public static class Helpers } /// - /// Returns whether the role is blacklisted from appearing and spawning. (Only applicable to vanilla roles) + /// Returns whether the role is blacklisted from appearing and spawning. Only applicable to vanilla roles. /// /// The role to check. /// The role's blacklist status.