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 :