From e663e185f76d2753a9dd6dc2684b203f6e2872f6 Mon Sep 17 00:00:00 2001 From: gotmachine <24925209+gotmachine@users.noreply.github.com> Date: Sun, 21 Aug 2022 15:55:13 +0200 Subject: [PATCH] - Bumped version to 1.21 - New performance / KSP bugfix patch : [PQSCoroutineLeak](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/85) (issue discovered by @Gameslinx) - Fixed the ToolbarShowHide patch partially failing due to an ambigious match exception when searching the no args `ApplicationLauncher.ShouldItHide()` method overload. --- .../KSPCommunityFixes.version | 2 +- GameData/KSPCommunityFixes/Settings.cfg | 4 + KSPCommunityFixes/KSPCommunityFixes.csproj | 1 + .../Performance/PQSCoroutineLeak.cs | 129 ++++++++++++++++++ KSPCommunityFixes/QoL/ToolbarShowHide.cs | 2 +- README.md | 5 + 6 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 KSPCommunityFixes/Performance/PQSCoroutineLeak.cs diff --git a/GameData/KSPCommunityFixes/KSPCommunityFixes.version b/GameData/KSPCommunityFixes/KSPCommunityFixes.version index 44207d8..9e67905 100644 --- a/GameData/KSPCommunityFixes/KSPCommunityFixes.version +++ b/GameData/KSPCommunityFixes/KSPCommunityFixes.version @@ -2,7 +2,7 @@ "NAME": "KSPCommunityFixes", "URL": "https://raw.githubusercontent.com/KSPModdingLibs/KSPCommunityFixes/master/GameData/KSPCommunityFixes/KSPCommunityFixes.version", "DOWNLOAD": "https://github.com/KSPModdingLibs/KSPCommunityFixes/releases", - "VERSION": {"MAJOR": 1, "MINOR": 20, "PATCH": 4, "BUILD": 0}, + "VERSION": {"MAJOR": 1, "MINOR": 21, "PATCH": 0, "BUILD": 0}, "KSP_VERSION": {"MAJOR": 1, "MINOR": 12, "PATCH": 3}, "KSP_VERSION_MIN": {"MAJOR": 1, "MINOR": 8, "PATCH": 0}, "KSP_VERSION_MAX": {"MAJOR": 1, "MINOR": 12, "PATCH": 3} diff --git a/GameData/KSPCommunityFixes/Settings.cfg b/GameData/KSPCommunityFixes/Settings.cfg index bf1b60d..81bec14 100644 --- a/GameData/KSPCommunityFixes/Settings.cfg +++ b/GameData/KSPCommunityFixes/Settings.cfg @@ -221,6 +221,10 @@ KSP_COMMUNITY_FIXES // when moving around near a body surface PQSUpdateNoMemoryAlloc = true + // Prevent KSP from spawning multiple PQS update coroutines for the same PQS after scene switches and + // and on other occasions, wasting a ton of CPU processing time. + PQSCoroutineLeak = true + // Remove unused ProgressTracking update handlers. Provides a very noticeable performance uplift in // career games having a large amount of celestial bodies and/or vessels. ProgressTrackingSpeedBoost = true diff --git a/KSPCommunityFixes/KSPCommunityFixes.csproj b/KSPCommunityFixes/KSPCommunityFixes.csproj index 64f1d49..6b10612 100644 --- a/KSPCommunityFixes/KSPCommunityFixes.csproj +++ b/KSPCommunityFixes/KSPCommunityFixes.csproj @@ -129,6 +129,7 @@ + diff --git a/KSPCommunityFixes/Performance/PQSCoroutineLeak.cs b/KSPCommunityFixes/Performance/PQSCoroutineLeak.cs new file mode 100644 index 0000000..6b155ae --- /dev/null +++ b/KSPCommunityFixes/Performance/PQSCoroutineLeak.cs @@ -0,0 +1,129 @@ +// See https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/85 + +using HarmonyLib; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using UnityEngine; + +namespace KSPCommunityFixes.Performance +{ + class PQSCoroutineLeak : BasePatch + { + protected override Version VersionMin => new Version(1, 8, 0); + + protected override void ApplyPatches(List patches) + { + patches.Add(new PatchInfo( + PatchMethodType.Transpiler, + AccessTools.Method(typeof(PQS), nameof(PQS.StartSphere)), + this)); + + patches.Add(new PatchInfo( + PatchMethodType.Transpiler, + AccessTools.Method(typeof(PQS), nameof(PQS.ResetAndWait)), + this)); + } + + // StartCoroutine(UpdateSphere()); + //IL_0391: ldarg.0 + //IL_0392: ldarg.0 + //IL_0393: call instance class [mscorlib] System.Collections.IEnumerator PQS::UpdateSphere() + //IL_0398: dup + //IL_0399: pop + //IL_039a: call instance class [UnityEngine.CoreModule] UnityEngine.Coroutine[UnityEngine.CoreModule] UnityEngine.MonoBehaviour::StartCoroutine(class [mscorlib] System.Collections.IEnumerator) + //IL_039f: dup + //IL_03a0: pop + + static IEnumerable PQS_StartSphere_Transpiler(IEnumerable instructions) + { + MethodInfo m_UpdateSphere = AccessTools.Method(typeof(PQS), nameof(PQS.UpdateSphere)); + MethodInfo m_StartCoroutine_String = AccessTools.Method(typeof(MonoBehaviour), nameof(MonoBehaviour.StartCoroutine), new Type[] {typeof(string)}); + MethodInfo m_StartCoroutine_IEnumerator = AccessTools.Method(typeof(MonoBehaviour), nameof(MonoBehaviour.StartCoroutine), new Type[] {typeof(IEnumerator)}); + MethodInfo m_StopCoroutine_String = AccessTools.Method(typeof(MonoBehaviour), nameof(MonoBehaviour.StopCoroutine), new Type[] { typeof(string) }); + + List code = new List(instructions); + + for (int i = code.Count - 1; i >= 0; i--) + { + if (code[i].opcode == OpCodes.Call && ReferenceEquals(code[i].operand, m_StartCoroutine_IEnumerator)) + { + code[i].operand = m_StartCoroutine_String; + int j; + bool found = false; + for (j = i - 1; j >= i - 4; j--) + { + if (code[j].opcode == OpCodes.Call && ReferenceEquals(code[j].operand, m_UpdateSphere)) + { + code[j].opcode = OpCodes.Ldstr; + code[j].operand = nameof(PQS.UpdateSphere); + found = true; + break; + } + } + + if (!found) + throw new Exception("PQS.StartSphere transpiler patch failed, UpdateSphere() call not found"); + + if (code[j - 1].opcode == OpCodes.Ldarg_0 && code[j - 2].opcode == OpCodes.Ldarg_0) + { + int k = j - 1; + code.Insert(k, new CodeInstruction(OpCodes.Ldstr, nameof(PQS.UpdateSphere))); + code.Insert(k + 1, new CodeInstruction(OpCodes.Call, m_StopCoroutine_String)); + } + else + { + throw new Exception("PQS.StartSphere transpiler patch failed, unexpected IL pattern"); + } + + break; + } + } + + return code; + } + + static IEnumerable PQS_ResetAndWait_Transpiler(IEnumerable instructions) + { + MethodInfo m_UpdateSphere = AccessTools.Method(typeof(PQS), nameof(PQS.ResetAndWaitCoroutine)); + MethodInfo m_StartCoroutine_String = AccessTools.Method(typeof(MonoBehaviour), nameof(MonoBehaviour.StartCoroutine), new Type[] { typeof(string) }); + MethodInfo m_StartCoroutine_IEnumerator = AccessTools.Method(typeof(MonoBehaviour), nameof(MonoBehaviour.StartCoroutine), new Type[] { typeof(IEnumerator) }); + + List code = new List(instructions); + + for (int i = code.Count - 1; i >= 0; i--) + { + if (code[i].opcode == OpCodes.Call && ReferenceEquals(code[i].operand, m_StartCoroutine_IEnumerator)) + { + code[i].operand = m_StartCoroutine_String; + int j; + bool found = false; + for (j = i - 1; j >= i - 4; j--) + { + if (code[j].opcode == OpCodes.Call && ReferenceEquals(code[j].operand, m_UpdateSphere)) + { + code[j].opcode = OpCodes.Ldstr; + code[j].operand = nameof(PQS.ResetAndWaitCoroutine); + found = true; + break; + } + } + + if (!found) + throw new Exception("PQS.ResetAndWait transpiler patch failed, UpdateSphere() call not found"); + + if (code[j - 1].opcode == OpCodes.Ldarg_0 && code[j - 2].opcode == OpCodes.Ldarg_0) + code[j - 2].opcode = OpCodes.Nop; + else + throw new Exception("PQS.ResetAndWait transpiler patch failed, unexpected IL pattern"); + + break; + } + } + + return code; + } + } +} diff --git a/KSPCommunityFixes/QoL/ToolbarShowHide.cs b/KSPCommunityFixes/QoL/ToolbarShowHide.cs index f52ed2a..1dfacf7 100644 --- a/KSPCommunityFixes/QoL/ToolbarShowHide.cs +++ b/KSPCommunityFixes/QoL/ToolbarShowHide.cs @@ -46,7 +46,7 @@ protected override void ApplyPatches(List patches) patches.Add(new PatchInfo( PatchMethodType.Prefix, - AccessTools.Method(typeof(ApplicationLauncher), nameof(ApplicationLauncher.ShouldItHide)), + AccessTools.Method(typeof(ApplicationLauncher), nameof(ApplicationLauncher.ShouldItHide), Type.EmptyTypes), this)); patches.Add(new PatchInfo( diff --git a/README.md b/README.md index a88765f..d8b2617 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ User options are available from the "ESC" in-game settings menu :