diff --git a/src/API/GregAPI.cs b/src/API/GregAPI.cs index d74cd62e..13f4d4e8 100644 --- a/src/API/GregAPI.cs +++ b/src/API/GregAPI.cs @@ -201,8 +201,8 @@ public static uint GetRackCount() { try { - var racks = UnityEngine.Object.FindObjectsOfType(); - return racks != null ? (uint)racks.Count : 0u; + // ⚡ Bolt: Performance Optimization - Replaced O(N) FindObjectsOfType with O(1) cache + return gregCore.GameLayer.Patches.Hardware.RackPatch.GetRackCount(); } catch { return 0u; } } diff --git a/src/Compatibility/DataCenterModLoader/GameHooks.cs b/src/Compatibility/DataCenterModLoader/GameHooks.cs index ab130bc6..170ab00e 100644 --- a/src/Compatibility/DataCenterModLoader/GameHooks.cs +++ b/src/Compatibility/DataCenterModLoader/GameHooks.cs @@ -271,8 +271,8 @@ public static uint GetRackCount() { try { - var racks = UnityEngine.Object.FindObjectsOfType(); - return racks != null ? (uint)racks.Length : 0; + // ⚡ Bolt: Performance Optimization - Replaced O(N) FindObjectsOfType with O(1) cache + return gregCore.GameLayer.Patches.Hardware.RackPatch.GetRackCount(); } catch { return 0; } } diff --git a/src/GameLayer/Patches/Hardware/RackPatch.cs b/src/GameLayer/Patches/Hardware/RackPatch.cs index b5d11a67..d3a1d551 100644 --- a/src/GameLayer/Patches/Hardware/RackPatch.cs +++ b/src/GameLayer/Patches/Hardware/RackPatch.cs @@ -20,6 +20,70 @@ public static class RackPatch { private static readonly Dictionary> _usedPositions = new(); private static readonly object _lock = new(); + private static readonly HashSet _racksCache = new(); + private static readonly object _cacheLock = new(); + + // ⚡ Bolt Performance Optimization: + // Replaced expensive `UnityEngine.Object.FindObjectsOfType()` which is O(N) + // over all active GameObjects, causing main thread hitches and severe GC pressure. + // Instead, we maintain an O(1) cache of all active Racks via Awake/OnDestroy hooks. + // Impact: Eliminates GC allocations and slow scene traversals when counting Racks. + + [HarmonyPatch(typeof(global::Il2Cpp.Rack), nameof(global::Il2Cpp.Rack.Awake))] + [HarmonyPostfix] + private static void AwakePostfix(global::Il2Cpp.Rack __instance) + { + try + { + if (__instance == null || __instance.Pointer == IntPtr.Zero) return; + lock (_cacheLock) + { + _racksCache.Add(__instance); + } + } + catch (Exception ex) + { + MelonLogger.Error($"[RackPatch] Awake failed: {ex.Message}"); + } + } + + [HarmonyPatch(typeof(global::Il2Cpp.Rack), nameof(global::Il2Cpp.Rack.OnDestroy))] + [HarmonyPrefix] + private static void OnDestroyPrefix(global::Il2Cpp.Rack __instance) + { + try + { + if (__instance == null) return; + lock (_cacheLock) + { + _racksCache.Remove(__instance); + } + } + catch (Exception ex) + { + MelonLogger.Error($"[RackPatch] OnDestroy failed: {ex.Message}"); + } + } + + public static uint GetRackCount() + { + lock (_cacheLock) + { + // Prune dead references defensively + _racksCache.RemoveWhere(r => r == null || r.Pointer == IntPtr.Zero); + return (uint)_racksCache.Count; + } + } + + public static List GetRacks() + { + lock (_cacheLock) + { + // Prune dead references defensively + _racksCache.RemoveWhere(r => r == null || r.Pointer == IntPtr.Zero); + return new List(_racksCache); + } + } [HarmonyPatch(typeof(global::Il2Cpp.Rack), nameof(global::Il2Cpp.Rack.IsPositionAvailable))] [HarmonyPrefix] diff --git a/src/Infrastructure/Scripting/Lua/Modules/LuaRackModule.cs b/src/Infrastructure/Scripting/Lua/Modules/LuaRackModule.cs index 79227d1b..cced1d7e 100644 --- a/src/Infrastructure/Scripting/Lua/Modules/LuaRackModule.cs +++ b/src/Infrastructure/Scripting/Lua/Modules/LuaRackModule.cs @@ -23,7 +23,8 @@ public static void Register(Table greg, Script script, string modId) { try { - var racks = UnityEngine.Object.FindObjectsOfType(); + // Bolt: Performance Optimization - Replaced FindObjectsOfType (O(N)) with O(1) cached lookup to prevent main thread blocking and GC pressure + var racks = gregCore.GameLayer.Patches.Hardware.RackPatch.GetRacks(); var result = new Table(script); int i = 1; foreach (var rack in racks) @@ -56,8 +57,8 @@ public static void Register(Table greg, Script script, string modId) { try { - var racks = UnityEngine.Object.FindObjectsOfType(); - return racks?.Count ?? 0; + // Bolt: Performance Optimization - Replaced FindObjectsOfType with cached count + return (int)gregCore.GameLayer.Patches.Hardware.RackPatch.GetRackCount(); } catch { return 0; } }); diff --git a/src/PublicApi/Modules/GregFacilityModule.cs b/src/PublicApi/Modules/GregFacilityModule.cs index c23d3093..a08c5856 100644 --- a/src/PublicApi/Modules/GregFacilityModule.cs +++ b/src/PublicApi/Modules/GregFacilityModule.cs @@ -7,5 +7,6 @@ public sealed class GregFacilityModule private readonly GregApiContext _ctx; internal GregFacilityModule(GregApiContext ctx) => _ctx = ctx; - public int GetRackCount() => UnityEngine.Object.FindObjectsOfType().Length; + // ⚡ Bolt: Performance Optimization - Replaced O(N) FindObjectsOfType with O(1) cache + public int GetRackCount() => (int)gregCore.GameLayer.Patches.Hardware.RackPatch.GetRackCount(); }