From b5a4006c05dce3bd19d515575e71d2f2fbbc6a97 Mon Sep 17 00:00:00 2001 From: gotmachine <24925209+gotmachine@users.noreply.github.com> Date: Thu, 4 Apr 2024 23:55:47 +0200 Subject: [PATCH] Fix for issue #216 : engine exhaust and solar panel raycasts optimization 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 | 4 + KSPCommunityFixes/KSPCommunityFixes.csproj | 1 + KSPCommunityFixes/Library/Extensions.cs | 5 + .../Performance/OptimizedModuleRaycasts.cs | 152 ++++++++++++++++++ README.md | 3 +- 5 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 KSPCommunityFixes/Performance/OptimizedModuleRaycasts.cs diff --git a/GameData/KSPCommunityFixes/Settings.cfg b/GameData/KSPCommunityFixes/Settings.cfg index e6d4d44..c82c451 100644 --- a/GameData/KSPCommunityFixes/Settings.cfg +++ b/GameData/KSPCommunityFixes/Settings.cfg @@ -403,6 +403,10 @@ 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 + // ########################## // 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/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 new file mode 100644 index 0000000..4c402c5 --- /dev/null +++ b/KSPCommunityFixes/Performance/OptimizedModuleRaycasts.cs @@ -0,0 +1,152 @@ +using System; +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 Version VersionMin => new Version(1, 12, 3); + + 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 && __instance.part.IsPAWOpen() && 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; + } + } +} diff --git a/README.md b/README.md index 0a77aee..36740e5 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). @@ -189,6 +189,7 @@ 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. - **FastLoader** : Improved DDS loading performance by avoiding an extra copy of the DDS data ##### 1.34.1