From fce426066c84128944d21aeecfe9790765131e56 Mon Sep 17 00:00:00 2001
From: DRVeyl <DRVeyl@gmail.com>
Date: Sat, 29 Jan 2022 17:53:42 -0500
Subject: [PATCH 1/3] Tune ModuleEnginesSolver

Set engine temp string via guiUnits in Start instead of building every FixedUpdate (Unnecessary GC pressure)
Add profiling calls
Use nameof instead of hard-coded strings
Remove redundant KSPField (default) declarations
Tweak NK's caching of the unit strings!
---
 SolverEngines/EngineModule.cs | 77 +++++++++++++++++------------------
 1 file changed, 38 insertions(+), 39 deletions(-)

diff --git a/SolverEngines/EngineModule.cs b/SolverEngines/EngineModule.cs
index f05ff17..22e5075 100644
--- a/SolverEngines/EngineModule.cs
+++ b/SolverEngines/EngineModule.cs
@@ -3,6 +3,7 @@
 using UnityEngine;
 using KSP.UI.Screens;
 using SolverEngines.EngineFitting;
+using UnityEngine.Profiling;
 
 namespace SolverEngines
 {
@@ -14,19 +15,19 @@ public abstract class ModuleEnginesSolver : ModuleEnginesFX, IModuleInfo, IEngin
     {
         // base fields
 
-        [KSPField(isPersistant = false, guiActiveEditor = true, guiFormat = "F3")]
+        [KSPField(guiActiveEditor = true, guiFormat = "F3")]
         public float Need_Area;
 
-        [KSPField(isPersistant = false, guiActive = true, guiName = "Current Throttle", guiFormat = "N2", guiUnits = "%")]
+        [KSPField(guiActive = true, guiName = "Current Throttle", guiFormat = "N2", guiUnits = "%")]
         public float actualThrottle;
 
         [KSPField(guiActive = true, guiName = "Mass Flow", guiUnits = " kg/s", guiFormat = "F5")]
         public float massFlowGui;
 
-        [KSPField(isPersistant = false)]
+        [KSPField]
         public double thrustUpperLimit = double.MaxValue;
 
-        [KSPField(isPersistant = false)]
+        [KSPField]
         public bool multiplyThrustByFuelFrac = true;
 
         [KSPField]
@@ -44,15 +45,15 @@ public abstract class ModuleEnginesSolver : ModuleEnginesFX, IModuleInfo, IEngin
 
         // engine temp stuff
         // fields
-        [KSPField(isPersistant = false)]
+        [KSPField]
         public double maxEngineTemp;
-        [KSPField(isPersistant = false, guiActive = true, guiName = "Eng. Internal Temp")]
-        public string engineTempString;
+        [KSPField(guiActive = true, guiName = "Eng. Internal Temp", guiFormat = "N0")]
+        public double engineTemp = 288.15d;
         [KSPField]
         public double tempGaugeMin = 0.8d;
 
         // internals
-        protected double tempRatio = 0d, engineTemp = 288.15d;
+        protected double tempRatio = 0d;
 
         public double GetEngineTemp => engineTemp;
 
@@ -105,13 +106,14 @@ virtual public void Start()
         {
             CreateEngine();
             Need_Area = RequiredIntakeArea();
-            Fields["Need_Area"].guiActiveEditor = Need_Area > 0f;
+            Fields[nameof(Need_Area)].guiActiveEditor = Need_Area > 0f;
             currentThrottle = 0f;
             flameout = false;
             SetUnflameout();
-            Fields["fuelFlowGui"].guiActive = false;
-            Fields["massFlowGui"].guiUnits = " kg/s";
+            Fields[nameof(fuelFlowGui)].guiActive = false;
+            Fields[nameof(massFlowGui)].guiUnits = " kg/s";
             flowKG = true;
+            Fields[nameof(engineTemp)].guiUnits = $" K / {maxEngineTemp:N0} K";
         }
 
         public override void OnStart(PartModule.StartState state)
@@ -127,10 +129,9 @@ public override void OnStart(PartModule.StartState state)
 
             // Get emissives
             emissiveAnims = new List<ModuleAnimateHeat>();
-            int mCount = part.Modules.Count;
-            for (int i = 0; i < mCount; ++i)
-                if (part.Modules[i] is ModuleAnimateHeat)
-                    emissiveAnims.Add(part.Modules[i] as ModuleAnimateHeat);
+            foreach (var pm in part.Modules)
+                if (pm is ModuleAnimateHeat)
+                    emissiveAnims.Add(pm as ModuleAnimateHeat);
 
             CreateEngineIfNecessary();
         }
@@ -147,12 +148,9 @@ public override void OnLoad(ConfigNode node)
                 {
                     if (trfNode.name != "THRUST_TRANSFORM") continue;
 
-                    ThrustTransformInfo info;
-
                     try
                     {
-                        info = new ThrustTransformInfo(trfNode);
-                        thrustTransformInfos.Add(info);
+                        thrustTransformInfos.Add(new ThrustTransformInfo(trfNode));
                     }
                     catch (Exception e)
                     {
@@ -310,17 +308,21 @@ public override bool CanStart()
 
         public override void FXUpdate()
         {
+            Profiler.BeginSample("EngineSolver.FXUpdate");
             part.Effect(directThrottleEffectName, engineSolver.GetFXThrottle());
             part.Effect(spoolEffectName, engineSolver.GetFXSpool());
             part.Effect(runningEffectName, engineSolver.GetFXRunning());
+            Profiler.BeginSample("EngineSolver.FXUpdate.GetFXPower");
             part.Effect(powerEffectName, engineSolver.GetFXPower());
+            Profiler.EndSample();
+            Profiler.EndSample();
         }
 
         virtual protected void UpdateTemp()
         {
             if (tempRatio > 1d && !CheatOptions.IgnoreMaxTemperature)
             {
-                FlightLogger.eventLog.Add("[" + FormatTime(vessel.missionTime) + "] " + part.partInfo.title + " melted its internals from heat.");
+                FlightLogger.eventLog.Add($"[{FormatTime(vessel.missionTime)}] {part.partInfo.title} melted its internals from heat.");
                 part.explode();
             }
             else
@@ -338,7 +340,6 @@ virtual public void UpdateInletEffects(EngineThermodynamics inletTherm, double a
 
             this.inletTherm = inletTherm;
             this.areaRatio = areaRatio;
-
         }
 
         public override void UpdateThrottle()
@@ -355,20 +356,24 @@ virtual public void UpdateFlightCondition()
         virtual public void UpdateSolver(EngineThermodynamics ambientTherm, double altitude, Vector3d vel, double mach, bool ignited, bool oxygen, bool underwater)
         {
             // In flight, these are the same and this will just return
+            Profiler.BeginSample("EngineSolver.UpdateSolver");
             this.ambientTherm = ambientTherm;
 
             engineSolver.SetEngineState(ignited, lastPropellantFraction);
             engineSolver.SetFreestreamAndInlet(ambientTherm, inletTherm, altitude, mach, vel, oxygen, underwater);
+            Profiler.BeginSample("EngineSolver.UpdateSolver.CalculatePerformance");
             engineSolver.CalculatePerformance(areaRatio, currentThrottle, flowMult * multFlow, ispMult * multIsp);
+            Profiler.EndSample();
+            Profiler.EndSample();
         }
 
         virtual public void CalculateEngineParams()
         {
+            Profiler.BeginSample("EngineSolver.CalculateEngineParams");
             SetEmissive(engineSolver.GetEmissive());
             // Heat
             engineTemp = engineSolver.GetEngineTemp();
             tempRatio = engineTemp / maxEngineTemp;
-            engineTempString = engineTemp.ToString("N0") + " K / " + maxEngineTemp.ToString("n0") + " K";
 
             double thrustIn = engineSolver.GetThrust(); //in N
             double isp = engineSolver.GetIsp();
@@ -399,6 +404,7 @@ virtual public void CalculateEngineParams()
             }
             else
             {
+                Profiler.BeginSample("EngineSolver.CalculateEngineParams.RunningEngine");
                 // calc flow
                 double vesselValue = vessel.VesselValues.FuelUsage.value;
                 if (vesselValue == 0d)
@@ -416,7 +422,9 @@ virtual public void CalculateEngineParams()
                 {
                     if (massFlow > 0d)
                     {
+                        Profiler.BeginSample("EngineSolver.CalculateEngineParams.RunningEngine.RequestPropellant");
                         lastPropellantFraction = RequestPropellant(massFlow);
+                        Profiler.EndSample();
                     }
                     else
                     {
@@ -424,6 +432,7 @@ virtual public void CalculateEngineParams()
                     }
                 }
                 this.propellantReqMet = (float)this.lastPropellantFraction * 100;
+                Profiler.EndSample();
 
                 // set produced thrust
                 if (multiplyThrustByFuelFrac)
@@ -438,30 +447,20 @@ virtual public void CalculateEngineParams()
 
                 // set fuel flow
                 fuelFlowGui = (float)(fuelFlow * 0.001d * mixtureDensityRecip / ratioSum); // Also in tons
-                if (fuelFlow > 1000d)
-                {
-                    fuelFlow *= 0.001d;
-                    if (flowKG)
-                    {
-                        Fields["massFlowGui"].guiUnits = " ton/s";
-                        flowKG = false;
-                    }
-                }
-                else
+                // If we're displaying in the wrong mode, swap
+                if (flowKG != (fuelFlow <= 1000))
                 {
-                    if (!flowKG)
-                    {
-                        Fields["massFlowGui"].guiUnits = " kg/s";
-                        flowKG = true;
-                    }
+                    flowKG = fuelFlow <= 1000;
+                    Fields[nameof(massFlowGui)].guiUnits = flowKG ? " kg/s" : " ton/s";
                 }
+                if (fuelFlow > 1000d)
+                    fuelFlow *= 0.001d;
                 massFlowGui = (float)fuelFlow;
-
-
                 realIsp = (float)isp;
             }
 
             finalThrust = (float)producedThrust * vessel.VesselValues.EnginePower.value;
+            Profiler.EndSample();
         }
 
         virtual public bool PropellantAvailable()

From b3b7a95077913baa4f3795b5e6a77260aa3ad081 Mon Sep 17 00:00:00 2001
From: DRVeyl <DRVeyl@gmail.com>
Date: Sat, 5 Feb 2022 10:35:01 -0500
Subject: [PATCH 2/3] Defer Exhaust Damage Computation

Don't test exhaust damage for each engine & transform when operating.  Instead, queue the engine to a deferred calculator that runs after all PartModules and does a single batched raycast.
---
 SolverEngines/DeferredEngineExhaustDamage.cs | 90 ++++++++++++++++++++
 SolverEngines/EngineModule.cs                |  7 +-
 SolverEngines/SolverEngines.csproj           |  3 +-
 3 files changed, 98 insertions(+), 2 deletions(-)
 create mode 100644 SolverEngines/DeferredEngineExhaustDamage.cs

diff --git a/SolverEngines/DeferredEngineExhaustDamage.cs b/SolverEngines/DeferredEngineExhaustDamage.cs
new file mode 100644
index 0000000..ad3f76c
--- /dev/null
+++ b/SolverEngines/DeferredEngineExhaustDamage.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using Unity.Collections;
+using Unity.Jobs;
+using UnityEngine;
+
+namespace SolverEngines
+{
+    [DefaultExecutionOrder(1)]
+    public class DeferredEngineExhaustDamage : MonoBehaviour
+    {
+        private int layerMask;
+        private readonly List<ModuleEngines> engines = new List<ModuleEngines>(128);
+        private readonly List<Transform> thrustTransforms = new List<Transform>(128);
+        private readonly List<float> multipliers = new List<float>(128);
+        private readonly Dictionary<Part,Part> damagedParts = new Dictionary<Part,Part>(16);
+
+        public void Start()
+        {
+            layerMask = LayerUtil.DefaultEquivalent |  (1 << LayerMask.NameToLayer("Parts"));
+        }
+
+        public void AddEngine(ModuleEngines engine)
+        {
+            if (engine.exhaustDamage)
+            {
+                foreach (var thrustTransform in engine.thrustTransforms)
+                {
+                    engines.Add(engine);
+                    thrustTransforms.Add(thrustTransform);
+                }
+                multipliers.AddRange(engine.thrustTransformMultipliers);
+            }
+        }
+
+        public void FixedUpdate()
+        {
+            int raysCount = engines.Count;
+            if (raysCount == 0) return;
+
+            var results = new NativeArray<RaycastHit>(raysCount, Allocator.Temp);
+            var commands = new NativeArray<RaycastCommand>(raysCount, Allocator.Temp);
+
+            for (int index = 0; index < raysCount; index++)
+            {
+                Transform thrustTransform = thrustTransforms[index];
+                ModuleEngines engine = engines[index];
+                commands[index++] = new RaycastCommand(thrustTransform.position, thrustTransform.forward, engine.exhaustDamageMaxRange, layerMask, maxHits: 1);
+            }
+            RaycastCommand.ScheduleBatch(commands, results, 1).Complete();
+
+            for (int index = 0; index < raysCount; index++)
+            {
+                Transform thrustTransform = thrustTransforms[index];
+                ModuleEngines engine = engines[index];
+                RaycastHit hit = results[index];
+                double mult = multipliers[index];
+                if (hit.collider != null)
+                {
+                    Transform transform = hit.collider.transform;
+                    Part partUpwardsCached = FlightGlobals.GetPartUpwardsCached(transform.gameObject);
+                    if (partUpwardsCached != null && partUpwardsCached != engine.part && !transform.GetComponentInChildren<physicalObject>())
+                    {
+                        double flux = engine.finalThrust * mult * engine.exhaustDamageMultiplier;
+                        double x = Math.Max(0.001, hit.distance + engine.exhaustDamageDistanceOffset);
+                        double falloff = Math.Pow(x, -engine.exhaustDamageFalloffPower);
+                        double splashback = Math.Pow(x, -engine.exhaustDamageSplashbackFallofPower) * engine.exhaustDamageSplashbackMult;
+                        falloff = Math.Min(falloff, engine.exhaustDamageMaxMutliplier);
+                        splashback = Math.Min(splashback, engine.exhaustDamageSplashbackMaxMutliplier);
+                        partUpwardsCached.AddSkinThermalFlux(flux * falloff);
+                        engine.part.AddSkinThermalFlux(flux * splashback);
+                        partUpwardsCached.AddForceAtPosition(thrustTransform.forward * engine.finalThrust * multipliers[index], hit.point);
+                        if (engine.exhaustDamageLogEvent)
+                            damagedParts.Add(engine.part, partUpwardsCached);
+                    }
+                }
+            }
+            if (damagedParts.Count > 0)
+                foreach (var srcDest in damagedParts)
+                    GameEvents.onSplashDamage.Fire(new EventReport(FlightEvents.SPLASHDAMAGE, srcDest.Key, srcDest.Value.partInfo.title, srcDest.Key.partInfo.title));
+
+            results.Dispose();
+            commands.Dispose();
+            engines.Clear();
+            thrustTransforms.Clear();
+            multipliers.Clear();
+            damagedParts.Clear();
+        }
+    }
+}
diff --git a/SolverEngines/EngineModule.cs b/SolverEngines/EngineModule.cs
index 22e5075..4f851d2 100644
--- a/SolverEngines/EngineModule.cs
+++ b/SolverEngines/EngineModule.cs
@@ -67,6 +67,7 @@ public abstract class ModuleEnginesSolver : ModuleEnginesFX, IModuleInfo, IEngin
 
         // protected internals
         protected EngineSolver engineSolver = null;
+        protected DeferredEngineExhaustDamage exhaustDamager = null;
 
         protected EngineThermodynamics ambientTherm = new EngineThermodynamics();
         protected EngineThermodynamics inletTherm = new EngineThermodynamics();
@@ -134,6 +135,8 @@ public override void OnStart(PartModule.StartState state)
                     emissiveAnims.Add(pm as ModuleAnimateHeat);
 
             CreateEngineIfNecessary();
+            if (HighLogic.LoadedSceneIsFlight && !vessel.TryGetComponent<DeferredEngineExhaustDamage>(out exhaustDamager))
+                exhaustDamager = vessel.gameObject.AddComponent<DeferredEngineExhaustDamage>();
         }
 
         public override void OnLoad(ConfigNode node)
@@ -289,7 +292,7 @@ protected void InitializeThrustTransforms()
                         part.AddForceAtPosition(thrustRot * (axis * thrustTransformMultipliers[i] * finalThrust), t.position + t.rotation * thrustOffset);
                     }
                 }
-                EngineExhaustDamage();
+                DeferredEngineExhaustDamage();
 
                 double thermalFlux = tempRatio * tempRatio * heatProduction * vessel.VesselValues.HeatProduction.value * PhysicsGlobals.InternalHeatProductionFactor * part.thermalMass;
                 part.AddThermalFlux(thermalFlux);
@@ -301,6 +304,8 @@ protected void InitializeThrustTransforms()
             }
         }
 
+        public virtual void DeferredEngineExhaustDamage() => exhaustDamager?.AddEngine(this);
+
         public override bool CanStart()
         {
             return base.CanStart() || flameout;
diff --git a/SolverEngines/SolverEngines.csproj b/SolverEngines/SolverEngines.csproj
index 13fc280..f193fee 100644
--- a/SolverEngines/SolverEngines.csproj
+++ b/SolverEngines/SolverEngines.csproj
@@ -18,7 +18,7 @@
     <DebugType>portable</DebugType>
     <Optimize>false</Optimize>
     <OutputPath>..\GameData\SolverEngines\Plugins\</OutputPath>
-    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <DefineConstants>TRACE;DEBUG;ENABLE_PROFILER</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <Prefer32Bit>false</Prefer32Bit>
@@ -37,6 +37,7 @@
   <ItemGroup>
     <Compile Include="AJEInlet.cs" />
     <Compile Include="AssemblyExtensions.cs" />
+    <Compile Include="DeferredEngineExhaustDamage.cs" />
     <Compile Include="EngineAnimation.cs" />
     <Compile Include="EngineFitting\AssemblyChecksumCache.cs" />
     <Compile Include="EngineFitting\EngineDatabase.cs" />

From 34525f6ff5903e756ee2cb7da40619d525e2df50 Mon Sep 17 00:00:00 2001
From: DRVeyl <DRVeyl@gmail.com>
Date: Sun, 6 Feb 2022 14:28:59 -0500
Subject: [PATCH 3/3] Bump version to 3.13

Breaking change necessitates new version: removal and addition of KSPFields
---
 SolverEngines/Properties/AssemblyInfo.cs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/SolverEngines/Properties/AssemblyInfo.cs b/SolverEngines/Properties/AssemblyInfo.cs
index 4f7439b..c7121db 100644
--- a/SolverEngines/Properties/AssemblyInfo.cs
+++ b/SolverEngines/Properties/AssemblyInfo.cs
@@ -32,7 +32,7 @@
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("3.3.0.0")] // Don't change until breaking changes occur
-[assembly: AssemblyFileVersion("3.12.0.0")]
+[assembly: AssemblyVersion("3.13.0.0")] // Don't change until breaking changes occur
+[assembly: AssemblyFileVersion("3.13.0.0")]
 
-[assembly: KSPAssembly("SolverEngines", 3, 12, 0)]
+[assembly: KSPAssembly("SolverEngines", 3, 13, 0)]