From 84652dad079b8cb9812d38ccdf5be4da1068acfc Mon Sep 17 00:00:00 2001 From: gotmachine <24925209+gotmachine@users.noreply.github.com> Date: Mon, 25 Mar 2024 23:11:12 +0100 Subject: [PATCH 1/3] Potential fix for issue #216 Optimization of raycasts in ModuleEngines.EngineExhaustDamage() and ModuleDeployableSolarPanel.CalculateTrackingLOS() : - Only synchronize transforms on the first raycast from any module, mainly relevant when something else is moving transforms in between calls, which is often the case for active engines with gimbals. - Cached ScaledSpace raycast results for solar panels : call time is divided by between 4 (when blocked by a scaled space object) and 2 (when not blocked) --- GameData/KSPCommunityFixes/Settings.cfg | 2 + KSPCommunityFixes/KSPCommunityFixes.csproj | 1 + .../Performance/OptimizedModuleRaycasts.cs | 152 ++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 KSPCommunityFixes/Performance/OptimizedModuleRaycasts.cs diff --git a/GameData/KSPCommunityFixes/Settings.cfg b/GameData/KSPCommunityFixes/Settings.cfg index e6d4d44..9c6792c 100644 --- a/GameData/KSPCommunityFixes/Settings.cfg +++ b/GameData/KSPCommunityFixes/Settings.cfg @@ -403,6 +403,8 @@ KSP_COMMUNITY_FIXES // Allow a min value of 0.02 instead of 0.03 for the "Max Physics Delta-Time Per Frame" main menu setting. LowerMinPhysicsDTPerFrame = true + OptimizedModuleRaycasts = true + // ########################## // Modding // ########################## diff --git a/KSPCommunityFixes/KSPCommunityFixes.csproj b/KSPCommunityFixes/KSPCommunityFixes.csproj index 0b27596..7588997 100644 --- a/KSPCommunityFixes/KSPCommunityFixes.csproj +++ b/KSPCommunityFixes/KSPCommunityFixes.csproj @@ -169,6 +169,7 @@ + diff --git a/KSPCommunityFixes/Performance/OptimizedModuleRaycasts.cs b/KSPCommunityFixes/Performance/OptimizedModuleRaycasts.cs new file mode 100644 index 0000000..6584065 --- /dev/null +++ b/KSPCommunityFixes/Performance/OptimizedModuleRaycasts.cs @@ -0,0 +1,152 @@ +using HarmonyLib; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; + +namespace KSPCommunityFixes.Performance +{ + internal class OptimizedModuleRaycasts : BasePatch + { + private static readonly WaitForFixedUpdate waitForFixedUpdate = new WaitForFixedUpdate(); + private static bool partModulesSyncedOnceInFixedUpdate = false; + + protected override void ApplyPatches(List patches) + { + patches.Add( + new PatchInfo(PatchMethodType.Transpiler, + AccessTools.Method(typeof(ModuleEngines), nameof(ModuleEngines.EngineExhaustDamage)), + this)); + + patches.Add( + new PatchInfo(PatchMethodType.Prefix, + AccessTools.Method(typeof(ModuleDeployableSolarPanel), nameof(ModuleDeployableSolarPanel.CalculateTrackingLOS)), + this)); + + KSPCommunityFixes.Instance.StartCoroutine(ResetSyncOnFixedEnd()); + } + + static IEnumerator ResetSyncOnFixedEnd() + { + while (true) + { + partModulesSyncedOnceInFixedUpdate = false; + lastVesselId = 0; + lastTrackingTransformId = 0; + yield return waitForFixedUpdate; + } + } + + static IEnumerable ModuleEngines_EngineExhaustDamage_Transpiler(IEnumerable instructions) + { + MethodInfo m_Physics_RayCast = AccessTools.Method(typeof(Physics), nameof(Physics.Raycast), new[] { typeof(Vector3), typeof(Vector3), typeof(RaycastHit).MakeByRefType(), typeof(float), typeof(int) }); + MethodInfo m_RaycastNoSync = AccessTools.Method(typeof(OptimizedModuleRaycasts), nameof(RaycastNoSync)); + + foreach (CodeInstruction instruction in instructions) + { + if (instruction.Calls(m_Physics_RayCast)) + { + instruction.operand = m_RaycastNoSync; + } + + yield return instruction; + } + } + + private static int lastVesselId; + private static int lastTrackingTransformId; + private static bool lastHasLoS; + private static string lastBlocker; + + private static bool ModuleDeployableSolarPanel_CalculateTrackingLOS_Prefix(ModuleDeployableSolarPanel __instance, Vector3 trackingDirection, ref string blocker, out bool __result) + { + if (__instance.part.ShieldedFromAirstream && __instance.applyShielding) + { + blocker = "aero shielding"; + __result = false; + return false; + } + + int trackingTransformId = __instance.trackingTransformLocal.GetInstanceID(); + int vesselId = __instance.vessel.GetInstanceID(); + if (lastTrackingTransformId == trackingTransformId && lastVesselId == vesselId) + { + if (!lastHasLoS) + { + __result = false; + blocker = lastBlocker; + return false; + } + } + else + { + lastTrackingTransformId = trackingTransformId; + lastVesselId = vesselId; + + Vector3 scaledVesselPos = ScaledSpace.LocalToScaledSpace(__instance.vessel.transform.position); + Vector3 scaledDirection = (ScaledSpace.LocalToScaledSpace(__instance.trackingTransformLocal.position) - scaledVesselPos).normalized; + + if (Physics.Raycast(scaledVesselPos, scaledDirection, out RaycastHit scaledHit, float.MaxValue, __instance.planetLayerMask) && scaledHit.transform.NotDestroyedRefNotEquals(__instance.trackingTransformScaled)) + { + __instance.hit = scaledHit; // just to ensure this is populated + lastBlocker = scaledHit.transform.gameObject.name; // allocates a string + blocker = lastBlocker; + lastHasLoS = false; + __result = false; + return false; + } + + lastHasLoS = true; + lastBlocker = null; + } + + Vector3 localPanelPos = __instance.secondaryTransform.position + trackingDirection * __instance.raycastOffset; + __result = !RaycastNoSync(localPanelPos, trackingDirection, out RaycastHit localhit, float.MaxValue, __instance.defaultLayerMask); + __instance.hit = localhit; // just to ensure this is populated + + if (!__result + && UIPartActionController.Instance.IsNotNullOrDestroyed() + && UIPartActionController.Instance.ItemListContains(__instance.part, false) + && localhit.transform.gameObject.IsNotNullOrDestroyed()) + { + GameObject hitObject = localhit.transform.gameObject; + if (!ReferenceEquals(hitObject.GetComponent(), null)) + { + blocker = ModuleDeployableSolarPanel.cacheAutoLOC_438839; + } + else + { + Part partUpwardsCached = FlightGlobals.GetPartUpwardsCached(hitObject); + if (partUpwardsCached.IsNotNullOrDestroyed()) + { + blocker = partUpwardsCached.partInfo.title; + } + else + { + string tag = hitObject.tag; // allocates a string + if (tag.Contains("KSC")) + blocker = ResearchAndDevelopment.GetMiniBiomedisplayNameByUnityTag(tag, true); + else + blocker = hitObject.name; + } + } + } + + return false; + } + + public static bool RaycastNoSync(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask) + { + if (!partModulesSyncedOnceInFixedUpdate) + { + Physics.SyncTransforms(); + partModulesSyncedOnceInFixedUpdate = true; + } + + Physics.autoSyncTransforms = false; + bool result = Physics.defaultPhysicsScene.Raycast(origin, direction, out hitInfo, maxDistance, layerMask); + Physics.autoSyncTransforms = true; + return result; + } + } +} From f473e5e714d583dd0a898b81cf708b1573f85a72 Mon Sep 17 00:00:00 2001 From: gotmachine <24925209+gotmachine@users.noreply.github.com> Date: Wed, 27 Mar 2024 18:59:16 +0100 Subject: [PATCH 2/3] Faster PAW visibility check --- KSPCommunityFixes/Library/Extensions.cs | 5 +++++ KSPCommunityFixes/Performance/OptimizedModuleRaycasts.cs | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/KSPCommunityFixes/Library/Extensions.cs b/KSPCommunityFixes/Library/Extensions.cs index d3e4422..0553322 100644 --- a/KSPCommunityFixes/Library/Extensions.cs +++ b/KSPCommunityFixes/Library/Extensions.cs @@ -28,5 +28,10 @@ public static string AssemblyQualifiedName(this object obj) Type type = obj.GetType(); return $"{type.Assembly.GetName().Name}:{type.Name}"; } + + public static bool IsPAWOpen(this Part part) + { + return part.PartActionWindow.IsNotNullOrDestroyed() && part.PartActionWindow.isActiveAndEnabled; + } } } diff --git a/KSPCommunityFixes/Performance/OptimizedModuleRaycasts.cs b/KSPCommunityFixes/Performance/OptimizedModuleRaycasts.cs index 6584065..d95a26e 100644 --- a/KSPCommunityFixes/Performance/OptimizedModuleRaycasts.cs +++ b/KSPCommunityFixes/Performance/OptimizedModuleRaycasts.cs @@ -104,10 +104,7 @@ private static bool ModuleDeployableSolarPanel_CalculateTrackingLOS_Prefix(Modul __result = !RaycastNoSync(localPanelPos, trackingDirection, out RaycastHit localhit, float.MaxValue, __instance.defaultLayerMask); __instance.hit = localhit; // just to ensure this is populated - if (!__result - && UIPartActionController.Instance.IsNotNullOrDestroyed() - && UIPartActionController.Instance.ItemListContains(__instance.part, false) - && localhit.transform.gameObject.IsNotNullOrDestroyed()) + if (!__result && __instance.part.IsPAWOpen() && localhit.transform.gameObject.IsNotNullOrDestroyed()) { GameObject hitObject = localhit.transform.gameObject; if (!ReferenceEquals(hitObject.GetComponent(), null)) From b09e8cd70b5c3bab2d242228066f7af78b37f381 Mon Sep 17 00:00:00 2001 From: gotmachine <24925209+gotmachine@users.noreply.github.com> Date: Thu, 4 Apr 2024 23:52:50 +0200 Subject: [PATCH 3/3] LGTM --- GameData/KSPCommunityFixes/Settings.cfg | 2 ++ KSPCommunityFixes/Performance/OptimizedModuleRaycasts.cs | 5 ++++- README.md | 5 ++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/GameData/KSPCommunityFixes/Settings.cfg b/GameData/KSPCommunityFixes/Settings.cfg index 9c6792c..c82c451 100644 --- a/GameData/KSPCommunityFixes/Settings.cfg +++ b/GameData/KSPCommunityFixes/Settings.cfg @@ -403,6 +403,8 @@ KSP_COMMUNITY_FIXES // Allow a min value of 0.02 instead of 0.03 for the "Max Physics Delta-Time Per Frame" main menu setting. LowerMinPhysicsDTPerFrame = true + // Improve engine exhaust damage and solar panel line of sight raycasts performance by avoiding extra physics + // state synchronization and caching solar panels scaled space raycasts results. OptimizedModuleRaycasts = true // ########################## diff --git a/KSPCommunityFixes/Performance/OptimizedModuleRaycasts.cs b/KSPCommunityFixes/Performance/OptimizedModuleRaycasts.cs index d95a26e..4c402c5 100644 --- a/KSPCommunityFixes/Performance/OptimizedModuleRaycasts.cs +++ b/KSPCommunityFixes/Performance/OptimizedModuleRaycasts.cs @@ -1,4 +1,5 @@ -using HarmonyLib; +using System; +using HarmonyLib; using System.Collections; using System.Collections.Generic; using System.Reflection; @@ -11,6 +12,8 @@ internal class OptimizedModuleRaycasts : BasePatch private static readonly WaitForFixedUpdate waitForFixedUpdate = new WaitForFixedUpdate(); private static bool partModulesSyncedOnceInFixedUpdate = false; + protected override Version VersionMin => new Version(1, 12, 3); + protected override void ApplyPatches(List patches) { patches.Add( diff --git a/README.md b/README.md index 595aa4a..fc1c53f 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ User options are available from the "ESC" in-game settings menu :
Improve engine exhaust damage and solar panel line of sight raycasts performance by avoiding extra physics state synchronization and caching solar panels scaled space raycasts results. #### API and modding tools - **MultipleModuleInPartAPI** [KSP 1.8.0 - 1.12.5]
This API allow other plugins to implement PartModules that can exist in multiple occurrence in a single part and won't suffer "module indexing mismatch" persistent data losses following part configuration changes. [See documentation on the wiki](https://github.com/KSPModdingLibs/KSPCommunityFixes/wiki/MultipleModuleInPartAPI). @@ -188,6 +188,9 @@ If doing so in the `Debug` configuration and if your KSP install is modified to ### Changelog +##### 1.35.0 +- New KSP performance patch : [**OptimizedModuleRaycasts**](https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/216) : Improve engine exhaust damage and solar panel line of sight raycasts performance by avoiding extra physics state synchronization and caching solar panels scaled space raycasts results. + ##### 1.34.1 - Disable BetterEditorUndoRedo when TweakScale/L is installed due to introducing a bug with part attachments in the editor.