From e093fbf340d8f2314479aaa69adf91b8b933eac8 Mon Sep 17 00:00:00 2001 From: Phantomical Date: Sun, 10 May 2026 00:50:09 -0700 Subject: [PATCH 1/2] perf: Optimize ModuleSystemHeatHarvester SystemHeat is actually pretty good here, and most of the overhead is in the stock module. The main thing we can optimize is to entirely disable the module when it is not active. Other than that I have gone through and changed stuff to make better use of the hooks that stock provides: * systemEfficency is used to replace GetHeatThrottle. This makes it so that it actually changes the converter rate like it is supposed to. Before the changes written to outputList and inputList were stomped over in PrepareRecipe. * We do UpdateFlux in PostProcess and reuse the TimeFactor. --- Source/Modules/ModuleSystemHeatHarvester.cs | 171 +++++++++++--------- 1 file changed, 93 insertions(+), 78 deletions(-) diff --git a/Source/Modules/ModuleSystemHeatHarvester.cs b/Source/Modules/ModuleSystemHeatHarvester.cs index 26c231c..52567ef 100644 --- a/Source/Modules/ModuleSystemHeatHarvester.cs +++ b/Source/Modules/ModuleSystemHeatHarvester.cs @@ -4,6 +4,7 @@ using System.Text; using UnityEngine; using KSP.Localization; +using Unity.Profiling; namespace SystemHeat { @@ -49,10 +50,10 @@ public void ToggleEditorThermalSim() [KSPField(isPersistant = false, guiActive = true, guiActiveEditor = true, guiName = "Harvester Efficiency")] public string HarvesterEfficiency = "-1%"; - // base paramters - private List inputs; - private List outputs; protected ModuleSystemHeat heatModule; + + private static readonly ProfilerMarker BaseFixedUpdateMarker = new("ModuleResourceHarvester.FixedUpdate"); + public override string GetInfo() { string info = base.GetInfo(); @@ -60,117 +61,131 @@ public override string GetInfo() int pos = info.IndexOf("\n\n"); if (pos < 0) return info; - else - return info.Substring(0, pos) + Localizer.Format("#LOC_SystemHeat_ModuleSystemHeatHarvester_PartInfoAdd", - Utils.ToSI(systemPower, "F0"), - systemOutletTemperature.ToString("F0"), - shutdownTemperature.ToString("F0") - ) + info.Substring(pos); + + var extraInfo = Localizer.Format("#LOC_SystemHeat_ModuleSystemHeatHarvester_PartInfoAdd", + Utils.ToSI(systemPower, "F0"), + systemOutletTemperature.ToString("F0"), + shutdownTemperature.ToString("F0") + ); + return info.Substring(0, pos) + extraInfo + info.Substring(pos); } + public void Start() { + heatModule = ModuleUtils.FindHeatModule(part, systemHeatModuleID); - heatModule = ModuleUtils.FindHeatModule(this.part, systemHeatModuleID); + Utils.Log("[ModuleSystemHeatHarvester] Setup completed", LogType.Modules); + Events["ToggleEditorThermalSim"].guiName = Localizer.Format("#LOC_SystemHeat_ModuleSystemHeatHarvester_Field_SimulateEditor", ConverterName); + Fields["HarvesterEfficiency"].guiName = Localizer.Format("#LOC_SystemHeat_ModuleSystemHeatHarvester_Field_Efficiency", ConverterName); + } + public override void FixedUpdate() + { if (HighLogic.LoadedSceneIsFlight) { - SetupResourceRatios(); + FixedUpdateFlight(); } else { - SetupResourceRatios(); + UpdateFlux(); + Fields["HarvesterEfficiency"].guiActiveEditor = editorThermalSim; } + } - Utils.Log("[ModuleSystemHeatHarvester] Setup completed", LogType.Modules); - Events["ToggleEditorThermalSim"].guiName = Localizer.Format("#LOC_SystemHeat_ModuleSystemHeatHarvester_Field_SimulateEditor", base.ConverterName); - Fields["HarvesterEfficiency"].guiName = Localizer.Format("#LOC_SystemHeat_ModuleSystemHeatHarvester_Field_Efficiency", base.ConverterName); + void Update() + { + if (!part.IsPAWVisible()) + return; + + HarvesterEfficiency = Localizer.Format( + "#LOC_SystemHeat_ModuleSystemHeatHarvester_Field_Efficiency_Value", + (GetHeatThrottle() * 100f).ToString("F1") + ); } - public override void FixedUpdate() + + void OnDisable() { - base.FixedUpdate(); - if (heatModule != null) - { - if (HighLogic.LoadedSceneIsFlight) - { - GenerateHeatFlight(); - UpdateSystemHeatFlight(); - } - else if (HighLogic.LoadedSceneIsEditor) - { - GenerateHeatEditor(); - - Fields["HarvesterEfficiency"].guiActiveEditor = editorThermalSim; - } - } + heatModule?.AddFlux(moduleID, 0f, 0f, false); + HarvesterEfficiency = "-"; } - void Update() + void FixedUpdateFlight() { - if (heatModule != null && part.IsPAWVisible()) + if (heatModule == null) { - HarvesterEfficiency = Localizer.Format("#LOC_SystemHeat_ModuleSystemHeatHarvester_Field_Efficiency_Value", (systemEfficiency.Evaluate(heatModule.currentLoopTemperature) * 100f).ToString("F1")); + // This disables this module entirely, so it won't be called every frame. + enabled = false; + return; } - } - protected void GenerateHeatEditor() - { - if (base.IsActivated) - heatModule.AddFlux(moduleID, systemOutletTemperature, systemPower, true); - else - heatModule.AddFlux(moduleID, 0f, 0f, false); + CheckOverheat(); + + if (!IsActivated) + enabled = false; + + using (BaseFixedUpdateMarker.ConditionalAuto()) + base.FixedUpdate(); } - protected void GenerateHeatFlight() + void UpdateFlux() => UpdateFlux(lastTimeFactor); + void UpdateFlux(double timeFactor) { - if (base.ModuleIsActive()) + if (heatModule == null) + return; + + if (ModuleIsActive()) { - float fluxScale = 1f; - if (base.lastTimeFactor == 0d) - { - fluxScale = 0f; - } - heatModule.AddFlux(moduleID, systemOutletTemperature, systemPower * fluxScale, true); + float scale = timeFactor != 0.0 ? 1f : 0f; + if (HighLogic.LoadedSceneIsEditor) + scale = 1f; + + heatModule.AddFlux(moduleID, systemOutletTemperature, systemPower * scale, true); } else { heatModule.AddFlux(moduleID, 0f, 0f, false); } } - protected void UpdateSystemHeatFlight() + + void CheckOverheat() { - if (base.ModuleIsActive()) - { - if (heatModule.currentLoopTemperature > shutdownTemperature) - { - ScreenMessages.PostScreenMessage( - new ScreenMessage( - Localizer.Format("#LOC_SystemHeat_ModuleSystemHeatHarvester_Message_Shutdown", - part.partInfo.title), - 3.0f, - ScreenMessageStyle.UPPER_CENTER)); - ToggleResourceConverterAction(new KSPActionParam(0, KSPActionType.Activate)); - - Utils.Log("[ModuleSystemHeatConverter]: Overheated, shutdown fired", LogType.Modules); - - } - base.recipe = ModuleUtils.RecalculateRatios(systemEfficiency.Evaluate(heatModule.currentLoopTemperature), inputs, outputs, inputList, outputList, base.recipe); - } + if (!ModuleIsActive()) + return; + if (heatModule.currentLoopTemperature <= shutdownTemperature) + return; + + ScreenMessages.PostScreenMessage( + new ScreenMessage( + Localizer.Format( + "#LOC_SystemHeat_ModuleSystemHeatHarvester_Message_Shutdown", + part.partInfo.title), + 3.0f, + ScreenMessageStyle.UPPER_CENTER)); + StopResourceConverter(); + + Utils.Log("[ModuleSystemHeatConverter]: Overheated, shutdown fired", LogType.Modules); } - private void SetupResourceRatios() + public override void StartResourceConverter() { + enabled = true; + base.StartResourceConverter(); + } - inputs = new List(); - outputs = new List(); + // In stock this would use the ModuleCoreHeat on the same part. We don't + // want that, and just override it to point to our own efficiency multiplier. + public override float GetHeatThrottle() + { + if (heatModule == null) + return 1f; - for (int i = 0; i < inputList.Count; i++) - { - inputs.Add(new ResourceBaseRatio(inputList[i].ResourceName, inputList[i].Ratio)); - } - for (int i = 0; i < outputList.Count; i++) - { - outputs.Add(new ResourceBaseRatio(outputList[i].ResourceName, outputList[i].Ratio)); - } + return systemEfficiency.Evaluate(heatModule.currentLoopTemperature); + } + + protected override void PostProcess(ConverterResults result, double deltaTime) + { + base.PostProcess(result, deltaTime); + UpdateFlux(result.TimeFactor); } } } From 6bfb48a2c9c6dd4ed1c033fef0c53992ec7dcd7b Mon Sep 17 00:00:00 2001 From: Phantomical Date: Sun, 10 May 2026 11:34:15 -0700 Subject: [PATCH 2/2] Properly handle AlwaysActive --- Source/Modules/ModuleSystemHeatHarvester.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Modules/ModuleSystemHeatHarvester.cs b/Source/Modules/ModuleSystemHeatHarvester.cs index 52567ef..42d40ee 100644 --- a/Source/Modules/ModuleSystemHeatHarvester.cs +++ b/Source/Modules/ModuleSystemHeatHarvester.cs @@ -120,7 +120,7 @@ void FixedUpdateFlight() CheckOverheat(); - if (!IsActivated) + if (!IsActivated && !AlwaysActive) enabled = false; using (BaseFixedUpdateMarker.ConditionalAuto())