HeatExchangerSystem.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. using Content.Server.Atmos.EntitySystems;
  2. using Content.Server.Atmos.Piping.Components;
  3. using Content.Server.Atmos.Piping.Unary.Components;
  4. using Content.Server.Atmos;
  5. using Content.Server.Atmos.Components;
  6. using Content.Server.NodeContainer.EntitySystems;
  7. using Content.Server.NodeContainer.Nodes;
  8. using Content.Server.NodeContainer;
  9. using Content.Shared.Atmos.Piping;
  10. using Content.Shared.Atmos;
  11. using Content.Shared.CCVar;
  12. using Content.Shared.Interaction;
  13. using JetBrains.Annotations;
  14. using Robust.Shared.Configuration;
  15. namespace Content.Server.Atmos.EntitySystems;
  16. public sealed class HeatExchangerSystem : EntitySystem
  17. {
  18. [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
  19. [Dependency] private readonly IConfigurationManager _cfg = default!;
  20. [Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
  21. [Dependency] private readonly SharedTransformSystem _transform = default!;
  22. float tileLoss;
  23. public override void Initialize()
  24. {
  25. base.Initialize();
  26. SubscribeLocalEvent<HeatExchangerComponent, AtmosDeviceUpdateEvent>(OnAtmosUpdate);
  27. // Getting CVars is expensive, don't do it every tick
  28. Subs.CVar(_cfg, CCVars.SuperconductionTileLoss, CacheTileLoss, true);
  29. }
  30. private void CacheTileLoss(float val)
  31. {
  32. tileLoss = val;
  33. }
  34. private void OnAtmosUpdate(EntityUid uid, HeatExchangerComponent comp, ref AtmosDeviceUpdateEvent args)
  35. {
  36. // make sure that the tile the device is on isn't blocked by a wall or something similar.
  37. if (args.Grid is {} grid
  38. && _transform.TryGetGridTilePosition(uid, out var tile)
  39. && _atmosphereSystem.IsTileAirBlocked(grid, tile))
  40. {
  41. return;
  42. }
  43. if (!_nodeContainer.TryGetNodes(uid, comp.InletName, comp.OutletName, out PipeNode? inlet, out PipeNode? outlet))
  44. return;
  45. var dt = args.dt;
  46. // Let n = moles(inlet) - moles(outlet), really a Δn
  47. var P = inlet.Air.Pressure - outlet.Air.Pressure; // really a ΔP
  48. // Such that positive P causes flow from the inlet to the outlet.
  49. // We want moles transferred to be proportional to the pressure difference, i.e.
  50. // dn/dt = G*P
  51. // To solve this we need to write dn in terms of P. Since PV=nRT, dP/dn=RT/V.
  52. // This assumes that the temperature change from transferring dn moles is negligible.
  53. // Since we have P=Pi-Po, then dP/dn = dPi/dn-dPo/dn = R(Ti/Vi - To/Vo):
  54. float dPdn = Atmospherics.R * (outlet.Air.Temperature / outlet.Air.Volume + inlet.Air.Temperature / inlet.Air.Volume);
  55. // Multiplying both sides of the differential equation by dP/dn:
  56. // dn/dt * dP/dn = dP/dt = G*P * (dP/dn)
  57. // Which is a first-order linear differential equation with constant (heh...) coefficients:
  58. // dP/dt + kP = 0, where k = -G*(dP/dn).
  59. // This differential equation has a closed-form solution, namely:
  60. float Pfinal = P * MathF.Exp(-comp.G * dPdn * dt);
  61. // Finally, back out n, the moles transferred in this tick:
  62. float n = (P - Pfinal) / dPdn;
  63. GasMixture xfer;
  64. if (n > 0)
  65. xfer = inlet.Air.Remove(n);
  66. else
  67. xfer = outlet.Air.Remove(-n);
  68. float CXfer = _atmosphereSystem.GetHeatCapacity(xfer, true);
  69. if (CXfer < Atmospherics.MinimumHeatCapacity)
  70. return;
  71. var radTemp = Atmospherics.TCMB;
  72. var environment = _atmosphereSystem.GetContainingMixture(uid, true, true);
  73. bool hasEnv = false;
  74. float CEnv = 0f;
  75. if (environment != null)
  76. {
  77. CEnv = _atmosphereSystem.GetHeatCapacity(environment, true);
  78. hasEnv = CEnv >= Atmospherics.MinimumHeatCapacity && environment.TotalMoles > 0f;
  79. if (hasEnv)
  80. radTemp = environment.Temperature;
  81. }
  82. // How ΔT' scales in respect to heat transferred
  83. float TdivQ = 1f / CXfer;
  84. // Since it's ΔT, also account for the environment's temperature change
  85. if (hasEnv)
  86. TdivQ += 1f / CEnv;
  87. // Radiation
  88. float dTR = xfer.Temperature - radTemp;
  89. float dTRA = MathF.Abs(dTR);
  90. float a0 = tileLoss / MathF.Pow(Atmospherics.T20C, 4);
  91. // ΔT' = -kΔT^4, k = -ΔT'/ΔT^4
  92. float kR = comp.alpha * a0 * TdivQ;
  93. // Based on the fact that ((3t)^(-1/3))' = -(3t)^(-4/3) = -((3t)^(-1/3))^4, and ΔT' = -kΔT^4.
  94. float dT2R = dTR * MathF.Pow((1f + 3f * kR * dt * dTRA * dTRA * dTRA), -1f/3f);
  95. float dER = (dTR - dT2R) / TdivQ;
  96. _atmosphereSystem.AddHeat(xfer, -dER);
  97. if (hasEnv && environment != null)
  98. {
  99. _atmosphereSystem.AddHeat(environment, dER);
  100. // Convection
  101. // Positive dT is from pipe to surroundings
  102. float dT = xfer.Temperature - environment.Temperature;
  103. // ΔT' = -kΔT, k = -ΔT' / ΔT
  104. float k = comp.K * TdivQ;
  105. float dT2 = dT * MathF.Exp(-k * dt);
  106. float dE = (dT - dT2) / TdivQ;
  107. _atmosphereSystem.AddHeat(xfer, -dE);
  108. _atmosphereSystem.AddHeat(environment, dE);
  109. }
  110. if (n > 0)
  111. _atmosphereSystem.Merge(outlet.Air, xfer);
  112. else
  113. _atmosphereSystem.Merge(inlet.Air, xfer);
  114. }
  115. }