Skip to content

Commit 2358f2f

Browse files
committed
Add cache, reduce allocations
1 parent c8f3f2a commit 2358f2f

3 files changed

Lines changed: 121 additions & 65 deletions

File tree

Source/RealFuels.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
<Compile Include="EntryCosts\EntryCostDatabase.cs" />
102102
<Compile Include="EntryCosts\PartEntryCostHolder.cs" />
103103
<Compile Include="Pumps\RefuelingPump.cs" />
104+
<Compile Include="Tanks\BgBoiloffCache.cs" />
104105
<Compile Include="Tanks\EditorPartSetMaintainer.cs" />
105106
<Compile Include="Tanks\FuelInfo.cs" />
106107
<Compile Include="Tanks\FuelTank.cs" />

Source/Tanks/BgBoiloffCache.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace RealFuels.Tanks
8+
{
9+
internal sealed class BgBoiloffCache
10+
{
11+
internal readonly string DataVersion;
12+
internal readonly int MliLayers;
13+
internal readonly double TankAreaM2;
14+
// Arrays are paired up: VspParams[i] and VspInfo[i]; DewarParams[i] and DewarInfo[i]
15+
internal readonly (double conductW, double coldK)[] VspParams;
16+
internal readonly (string name, double vsp, double density)[] VspInfo;
17+
internal readonly (double dewarArea, double coldK)[] DewarParams;
18+
internal readonly (string name, double vsp, double density)[] DewarInfo;
19+
20+
internal BgBoiloffCache(string dataVersion, int mliLayers, double tankAreaM2,
21+
(double conductW, double coldK)[] vspParams, (string name, double vsp, double density)[] vspInfo,
22+
(double dewarArea, double coldK)[] dewarParams, (string name, double vsp, double density)[] dewarInfo)
23+
{
24+
DataVersion = dataVersion;
25+
MliLayers = mliLayers;
26+
TankAreaM2 = tankAreaM2;
27+
VspParams = vspParams;
28+
VspInfo = vspInfo;
29+
DewarParams = dewarParams;
30+
DewarInfo = dewarInfo;
31+
}
32+
}
33+
}

Source/Tanks/ModuleFuelTanksRF.cs

Lines changed: 87 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System;
33
using System.Collections.Generic;
44
using System.Globalization;
5+
using System.Runtime.CompilerServices;
56
using UnityEngine;
67
using UnityEngine.Profiling;
78

@@ -80,6 +81,15 @@ public partial class ModuleFuelTanks : IAnalyticTemperatureModifier, IAnalyticPr
8081
private readonly List<double> lossInfo = new List<double>();
8182
private readonly List<double> fluxInfo = new List<double>();
8283

84+
// Cached solver params for ComputeMLIEquilibriumTemp (values are fixed for a given tank definition).
85+
private (double conductW, double coldK)[] _mliTankParamsCache;
86+
private (double dewarArea, double coldK)[] _mliDewarParamsCache;
87+
88+
// Pre-parsed tank boiloff data for background processing.
89+
// Keys naturally expire when the vessel loads and KSP releases the snapshot, so no manual cleanup is needed.
90+
private static readonly ConditionalWeakTable<ProtoPartModuleSnapshot, BgBoiloffCache> _bgCache
91+
= new ConditionalWeakTable<ProtoPartModuleSnapshot, BgBoiloffCache>();
92+
8393
private FlightIntegrator _flightIntegrator;
8494

8595
double lowestTankTemperature = 300d;
@@ -626,8 +636,18 @@ private double ComputeMLIEquilibriumTemp(double skinTemp)
626636
if (totalMLILayers <= 0 || totalTankArea <= 0)
627637
return skinTemp;
628638

639+
if (_mliTankParamsCache == null)
640+
BuildMLISolverParams();
641+
642+
return _mliTankParamsCache.Length > 0 || _mliDewarParamsCache.Length > 0
643+
? SolveMLIEquilibrium(skinTemp, totalTankArea, totalMLILayers, _mliTankParamsCache, _mliDewarParamsCache)
644+
: skinTemp;
645+
}
646+
647+
private void BuildMLISolverParams()
648+
{
629649
var tankParams = new List<(double conductW, double coldK)>(cryoTanks.Count);
630-
var dewarParams = new List<(double area, double coldK)>();
650+
var dewarParams = new List<(double dewarArea, double coldK)>();
631651
foreach (var tank in cryoTanks)
632652
{
633653
if (tank.vsp <= 0) continue;
@@ -641,14 +661,11 @@ private double ComputeMLIEquilibriumTemp(double skinTemp)
641661
double wallF = tank.wallConduction > 0 ? tank.wallThickness / tank.wallConduction : 0;
642662
double insulF = tank.insulationConduction > 0 ? tank.insulationThickness / tank.insulationConduction : 0;
643663
double resF = tank.resourceConductivity > 0 ? 0.01 / tank.resourceConductivity : 0;
644-
double conductW = tank.totalArea / Math.Max(double.Epsilon, wallF + insulF + resF);
645-
tankParams.Add((conductW, tank.temperature));
664+
tankParams.Add((tank.totalArea / Math.Max(double.Epsilon, wallF + insulF + resF), tank.temperature));
646665
}
647666
}
648-
649-
return tankParams.Count > 0 || dewarParams.Count > 0
650-
? SolveMLIEquilibrium(skinTemp, totalTankArea, totalMLILayers, tankParams, dewarParams)
651-
: skinTemp;
667+
_mliTankParamsCache = tankParams.ToArray();
668+
_mliDewarParamsCache = dewarParams.ToArray();
652669
}
653670

654671
/// <summary>
@@ -658,8 +675,8 @@ private double ComputeMLIEquilibriumTemp(double skinTemp)
658675
/// </summary>
659676
private static double SolveMLIEquilibrium(
660677
double skinTemp, double tankAreaM2, int mliLayers,
661-
List<(double conductW, double coldK)> tanks,
662-
List<(double area, double coldK)> dewarTanks)
678+
IReadOnlyList<(double conductW, double coldK)> tanks,
679+
IReadOnlyList<(double area, double coldK)> dewarTanks)
663680
{
664681
double lo = double.MaxValue;
665682
foreach (var t in tanks)
@@ -725,81 +742,48 @@ public static string BackgroundUpdate(
725742

726743
bool hasGeometry = KerbalismInterface.TryGetThermalData(vessel, out double vesselTemp, out _);
727744

728-
// Read MLI geometry needed for the equilibrium solver.
729-
int.TryParse(proto_module.moduleValues.GetValue(nameof(totalMLILayers)), out int mliLayers);
730-
double.TryParse(proto_module.moduleValues.GetValue(nameof(totalTankArea)),
731-
NumberStyles.Float, CultureInfo.InvariantCulture, out double tankAreaM2);
732-
733-
// Parse all entries. Separate Dewar tanks (handled individually) from vsp tanks
734-
// (handled via joint MLI equilibrium).
735-
var vspTanks = new List<(string name, double coldK, double conductW, PartResourceDefinition resDef)>();
736-
var dewarTanks = new List<(string name, double coldK, double dewarArea, PartResourceDefinition resDef)>();
737-
738-
foreach (string entry in data.Split(';'))
745+
// bgBoiloffData, totalMLILayers, and totalTankArea are all static while a vessel is unloaded,
746+
// so parse them once and cache on the ProtoPartModuleSnapshot (automatically GC'd when the
747+
// vessel loads and the snapshot is released).
748+
if (!_bgCache.TryGetValue(proto_module, out BgBoiloffCache cache) || cache.DataVersion != data)
739749
{
740-
if (string.IsNullOrEmpty(entry)) continue;
741-
string[] f = entry.Split(',');
742-
if (f.Length != 4) continue;
743-
string resourceName = f[0];
744-
if (!double.TryParse(f[1], NumberStyles.Float, CultureInfo.InvariantCulture, out double coldK)) continue;
745-
if (!double.TryParse(f[2], NumberStyles.Float, CultureInfo.InvariantCulture, out double conductW)) continue;
746-
if (!double.TryParse(f[3], NumberStyles.Float, CultureInfo.InvariantCulture, out double dewarArea)) continue;
747-
748-
PartResourceDefinition resDef = PartResourceLibrary.Instance.GetDefinition(resourceName);
749-
if (resDef == null || resDef.density <= 0d) continue;
750-
751-
if (dewarArea >= 0)
752-
dewarTanks.Add((resourceName, coldK, dewarArea, resDef));
753-
else if (conductW > 0 && MFSSettings.resourceVsps.ContainsKey(resourceName))
754-
vspTanks.Add((resourceName, coldK, conductW, resDef));
750+
int.TryParse(proto_module.moduleValues.GetValue(nameof(totalMLILayers)), out int mliLayers);
751+
double.TryParse(proto_module.moduleValues.GetValue(nameof(totalTankArea)),
752+
NumberStyles.Float, CultureInfo.InvariantCulture, out double tankAreaM2);
753+
cache = BuildBgBoiloffCache(data, mliLayers, tankAreaM2);
754+
_bgCache.Remove(proto_module);
755+
_bgCache.Add(proto_module, cache);
755756
}
756757

757758
bool anyRequest = false;
758759

759-
// Solve the shared part-interior temperature, accounting for MLI and all cryo heat sinks.
760-
// Requires Kerbalism VesselTemperature; boiloff is skipped entirely if geometry data is unavailable.
761-
if (hasGeometry)
760+
if (hasGeometry && (cache.VspParams.Length > 0 || cache.DewarParams.Length > 0))
762761
{
763-
double interiorTemp;
764-
if (mliLayers > 0 && tankAreaM2 > 0 && (vspTanks.Count > 0 || dewarTanks.Count > 0))
765-
{
766-
var tankParams = new List<(double conductW, double coldK)>(vspTanks.Count);
767-
foreach (var t in vspTanks)
768-
{
769-
tankParams.Add((t.conductW, t.coldK));
770-
}
771-
772-
var dewarParams = new List<(double area, double coldK)>(dewarTanks.Count);
773-
foreach (var d in dewarTanks)
774-
{
775-
dewarParams.Add((d.dewarArea, d.coldK));
776-
}
762+
double interiorTemp = cache.MliLayers > 0 && cache.TankAreaM2 > 0
763+
? SolveMLIEquilibrium(vesselTemp, cache.TankAreaM2, cache.MliLayers, cache.VspParams, cache.DewarParams)
764+
: vesselTemp;
777765

778-
interiorTemp = SolveMLIEquilibrium(vesselTemp, tankAreaM2, mliLayers, tankParams, dewarParams);
779-
}
780-
else
781-
{
782-
interiorTemp = vesselTemp; // no MLI: interior equilibrates to skin temperature
783-
}
784-
785-
foreach (var (name, coldK, conductW, resDef) in vspTanks)
766+
for (int i = 0; i < cache.VspParams.Length; i++)
786767
{
787-
if (!MFSSettings.resourceVsps.TryGetValue(name, out double vsp) || vsp <= 0) continue;
768+
var (conductW, coldK) = cache.VspParams[i];
769+
var (name, vsp, density) = cache.VspInfo[i];
788770
double deltaTemp = interiorTemp - coldK;
789771
if (deltaTemp <= 0) continue;
790772
double rateKgS = conductW * deltaTemp * 0.001 / vsp;
791773
if (rateKgS <= 0) continue;
792-
resourceChangeRequest.Add(new KeyValuePair<string, double>(name, -rateKgS / resDef.density));
774+
resourceChangeRequest.Add(new KeyValuePair<string, double>(name, -rateKgS / density));
793775
anyRequest = true;
794776
}
795777

796-
foreach (var (name, coldK, dewarArea, resDef) in dewarTanks)
778+
for (int i = 0; i < cache.DewarParams.Length; i++)
797779
{
798-
if (interiorTemp <= coldK || !MFSSettings.resourceVsps.TryGetValue(name, out double vsp) || vsp <= 0) continue;
780+
var (dewarArea, coldK) = cache.DewarParams[i];
781+
var (name, vsp, density) = cache.DewarInfo[i];
782+
if (interiorTemp <= coldK) continue;
799783
double Q_kW = GetDewarTransferRate(interiorTemp, coldK, dewarArea) * 0.001;
800784
double rateKgS = Math.Max(0, Q_kW / vsp);
801785
if (rateKgS <= 0) continue;
802-
resourceChangeRequest.Add(new KeyValuePair<string, double>(name, -rateKgS / resDef.density));
786+
resourceChangeRequest.Add(new KeyValuePair<string, double>(name, -rateKgS / density));
803787
anyRequest = true;
804788
}
805789
}
@@ -810,6 +794,44 @@ public static string BackgroundUpdate(
810794
return anyRequest ? Localizer.GetStringByTag("#RF_FuelTankRF_kerbalismtips") : string.Empty;
811795
}
812796

797+
private static BgBoiloffCache BuildBgBoiloffCache(string data, int mliLayers, double tankAreaM2)
798+
{
799+
var vspParams = new List<(double conductW, double coldK)>();
800+
var vspInfo = new List<(string name, double vsp, double density)>();
801+
var dewarParams = new List<(double dewarArea, double coldK)>();
802+
var dewarInfo = new List<(string name, double vsp, double density)>();
803+
804+
foreach (string entry in data.Split(';'))
805+
{
806+
if (string.IsNullOrEmpty(entry)) continue;
807+
string[] split = entry.Split(',');
808+
if (split.Length != 4) continue;
809+
string resourceName = split[0];
810+
if (!double.TryParse(split[1], NumberStyles.Float, CultureInfo.InvariantCulture, out double coldK)) continue;
811+
if (!double.TryParse(split[2], NumberStyles.Float, CultureInfo.InvariantCulture, out double conductW)) continue;
812+
if (!double.TryParse(split[3], NumberStyles.Float, CultureInfo.InvariantCulture, out double dewarArea)) continue;
813+
814+
PartResourceDefinition resDef = PartResourceLibrary.Instance.GetDefinition(resourceName);
815+
if (resDef == null || resDef.density <= 0d) continue;
816+
if (!MFSSettings.resourceVsps.TryGetValue(resourceName, out double vsp) || vsp <= 0) continue;
817+
818+
if (dewarArea >= 0)
819+
{
820+
dewarParams.Add((dewarArea, coldK));
821+
dewarInfo.Add((resourceName, vsp, resDef.density));
822+
}
823+
else if (conductW > 0)
824+
{
825+
vspParams.Add((conductW, coldK));
826+
vspInfo.Add((resourceName, vsp, resDef.density));
827+
}
828+
}
829+
830+
return new BgBoiloffCache(data, mliLayers, tankAreaM2,
831+
vspParams.ToArray(), vspInfo.ToArray(),
832+
dewarParams.ToArray(), dewarInfo.ToArray());
833+
}
834+
813835
/// <summary>
814836
/// Called by Kerbalism every frame for loaded vessels. Uses their resource system when Kerbalism is installed.
815837
/// </summary>

0 commit comments

Comments
 (0)