From 7cf04dcb201e1a615e4a175ff929cca9554134d2 Mon Sep 17 00:00:00 2001 From: drakewill-CRL <46307022+drakewill-CRL@users.noreply.github.com> Date: Mon, 30 Sep 2024 01:14:07 -0400 Subject: [PATCH] Use archived gas mixture in gas exchange comparison (#32088) The comparison for doing gas exchange used current and not archived moles. This could lead to update order-dependent gas spreading effects. To fix this, convert TileAtmosphere's MolesArchived and TemperatureArchived to a AirArchived, and use that in the comparison method. --------- Co-authored-by: PraxisMapper Co-authored-by: Kevin Zheng --- .../EntitySystems/AtmosphereSystem.Gases.cs | 11 +++++++ .../AtmosphereSystem.GridAtmosphere.cs | 2 +- .../EntitySystems/AtmosphereSystem.LINDA.cs | 29 ++++++++++--------- .../AtmosphereSystem.Monstermos.cs | 2 +- .../AtmosphereSystem.Processing.cs | 4 +-- .../AtmosphereSystem.Superconductivity.cs | 10 +++++-- Content.Server/Atmos/TileAtmosphere.cs | 16 +++++----- 7 files changed, 47 insertions(+), 27 deletions(-) diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs index 70c4639e48..f38ec3fef6 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Gases.cs @@ -281,6 +281,17 @@ namespace Content.Server.Atmos.EntitySystems return true; } + /// + /// Compares two TileAtmospheres to see if they are within acceptable ranges for group processing to be enabled. + /// + public GasCompareResult CompareExchange(TileAtmosphere sample, TileAtmosphere otherSample) + { + if (sample.AirArchived == null || otherSample.AirArchived == null) + return GasCompareResult.NoExchange; + + return CompareExchange(sample.AirArchived, otherSample.AirArchived); + } + /// /// Compares two gas mixtures to see if they are within acceptable ranges for group processing to be enabled. /// diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs index 3983234dea..b11eb5dc3e 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs @@ -270,7 +270,7 @@ public sealed partial class AtmosphereSystem { DebugTools.AssertNotNull(tile.Air); DebugTools.Assert(tile.Air?.Immutable == false); - Array.Clear(tile.MolesArchived); + tile.AirArchived = null; tile.ArchivedCycle = 0; var count = 0; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs index fb2375899d..55b38924c0 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs @@ -54,7 +54,7 @@ namespace Content.Server.Atmos.EntitySystems } shouldShareAir = true; - } else if (CompareExchange(tile.Air, enemyTile.Air) != GasCompareResult.NoExchange) + } else if (CompareExchange(tile, enemyTile) != GasCompareResult.NoExchange) { AddActiveTile(gridAtmosphere, enemyTile); if (ExcitedGroups) @@ -117,9 +117,8 @@ namespace Content.Server.Atmos.EntitySystems private void Archive(TileAtmosphere tile, int fireCount) { if (tile.Air != null) - tile.Air.Moles.AsSpan().CopyTo(tile.MolesArchived.AsSpan()); + tile.AirArchived = new GasMixture(tile.Air); - tile.TemperatureArchived = tile.Temperature; tile.ArchivedCycle = fireCount; } @@ -184,10 +183,10 @@ namespace Content.Server.Atmos.EntitySystems /// public float GetHeatCapacityArchived(TileAtmosphere tile) { - if (tile.Air == null) + if (tile.AirArchived == null) return tile.HeatCapacity; - return GetHeatCapacityCalculation(tile.MolesArchived!, tile.Space); + return GetHeatCapacity(tile.AirArchived); } /// @@ -195,10 +194,11 @@ namespace Content.Server.Atmos.EntitySystems /// public float Share(TileAtmosphere tileReceiver, TileAtmosphere tileSharer, int atmosAdjacentTurfs) { - if (tileReceiver.Air is not {} receiver || tileSharer.Air is not {} sharer) + if (tileReceiver.Air is not {} receiver || tileSharer.Air is not {} sharer || + tileReceiver.AirArchived == null || tileSharer.AirArchived == null) return 0f; - var temperatureDelta = tileReceiver.TemperatureArchived - tileSharer.TemperatureArchived; + var temperatureDelta = tileReceiver.AirArchived.Temperature - tileSharer.AirArchived.Temperature; var absTemperatureDelta = Math.Abs(temperatureDelta); var oldHeatCapacity = 0f; var oldSharerHeatCapacity = 0f; @@ -249,12 +249,12 @@ namespace Content.Server.Atmos.EntitySystems // Transfer of thermal energy (via changed heat capacity) between self and sharer. if (!receiver.Immutable && newHeatCapacity > Atmospherics.MinimumHeatCapacity) { - receiver.Temperature = ((oldHeatCapacity * receiver.Temperature) - (heatCapacityToSharer * tileReceiver.TemperatureArchived) + (heatCapacitySharerToThis * tileSharer.TemperatureArchived)) / newHeatCapacity; + receiver.Temperature = ((oldHeatCapacity * receiver.Temperature) - (heatCapacityToSharer * tileReceiver.AirArchived.Temperature) + (heatCapacitySharerToThis * tileSharer.AirArchived.Temperature)) / newHeatCapacity; } if (!sharer.Immutable && newSharerHeatCapacity > Atmospherics.MinimumHeatCapacity) { - sharer.Temperature = ((oldSharerHeatCapacity * sharer.Temperature) - (heatCapacitySharerToThis * tileSharer.TemperatureArchived) + (heatCapacityToSharer * tileReceiver.TemperatureArchived)) / newSharerHeatCapacity; + sharer.Temperature = ((oldSharerHeatCapacity * sharer.Temperature) - (heatCapacitySharerToThis * tileSharer.AirArchived.Temperature) + (heatCapacityToSharer * tileReceiver.AirArchived.Temperature)) / newSharerHeatCapacity; } // Thermal energy of the system (self and sharer) is unchanged. @@ -273,7 +273,7 @@ namespace Content.Server.Atmos.EntitySystems var moles = receiver.TotalMoles; var theirMoles = sharer.TotalMoles; - return (tileReceiver.TemperatureArchived * (moles + movedMoles)) - (tileSharer.TemperatureArchived * (theirMoles - movedMoles)) * Atmospherics.R / receiver.Volume; + return (tileReceiver.AirArchived.Temperature * (moles + movedMoles)) - (tileSharer.AirArchived.Temperature * (theirMoles - movedMoles)) * Atmospherics.R / receiver.Volume; } /// @@ -281,10 +281,11 @@ namespace Content.Server.Atmos.EntitySystems /// public float TemperatureShare(TileAtmosphere tileReceiver, TileAtmosphere tileSharer, float conductionCoefficient) { - if (tileReceiver.Air is not { } receiver || tileSharer.Air is not { } sharer) + if (tileReceiver.Air is not { } receiver || tileSharer.Air is not { } sharer || + tileReceiver.AirArchived == null || tileSharer.AirArchived == null) return 0f; - var temperatureDelta = tileReceiver.TemperatureArchived - tileSharer.TemperatureArchived; + var temperatureDelta = tileReceiver.AirArchived.Temperature - tileSharer.AirArchived.Temperature; if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider) { var heatCapacity = GetHeatCapacityArchived(tileReceiver); @@ -310,10 +311,10 @@ namespace Content.Server.Atmos.EntitySystems /// public float TemperatureShare(TileAtmosphere tileReceiver, float conductionCoefficient, float sharerTemperature, float sharerHeatCapacity) { - if (tileReceiver.Air is not {} receiver) + if (tileReceiver.Air is not {} receiver || tileReceiver.AirArchived == null) return 0; - var temperatureDelta = tileReceiver.TemperatureArchived - sharerTemperature; + var temperatureDelta = tileReceiver.AirArchived.Temperature - sharerTemperature; if (MathF.Abs(temperatureDelta) > Atmospherics.MinimumTemperatureDeltaToConsider) { var heatCapacity = GetHeatCapacityArchived(tileReceiver); diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs index d703a98154..e6466c8157 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs @@ -355,7 +355,7 @@ namespace Content.Server.Atmos.EntitySystems continue; DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite())); - if (otherTile2.Air != null && CompareExchange(otherTile2.Air, tile.Air) == GasCompareResult.NoExchange) + if (otherTile2.Air != null && CompareExchange(otherTile2, tile) == GasCompareResult.NoExchange) continue; AddActiveTile(gridAtmosphere, otherTile2); diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs index 85b1a93e20..59320ba67b 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs @@ -231,7 +231,7 @@ namespace Content.Server.Atmos.EntitySystems tile.MapAtmosphere = false; atmos.MapTiles.Remove(tile); tile.Air = null; - Array.Clear(tile.MolesArchived); + tile.AirArchived = null; tile.ArchivedCycle = 0; tile.LastShare = 0f; tile.Space = false; @@ -261,7 +261,7 @@ namespace Content.Server.Atmos.EntitySystems return; tile.Air = null; - Array.Clear(tile.MolesArchived); + tile.AirArchived = null; tile.ArchivedCycle = 0; tile.LastShare = 0f; tile.Hotspot = new Hotspot(); diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs index 8ed92a9d0e..46a554054b 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Superconductivity.cs @@ -131,7 +131,10 @@ namespace Content.Server.Atmos.EntitySystems private void TemperatureShareMutualSolid(TileAtmosphere tile, TileAtmosphere other, float conductionCoefficient) { - var deltaTemperature = (tile.TemperatureArchived - other.TemperatureArchived); + if (tile.AirArchived == null || other.AirArchived == null) + return; + + var deltaTemperature = (tile.AirArchived.Temperature - other.AirArchived.Temperature); if (MathF.Abs(deltaTemperature) > Atmospherics.MinimumTemperatureDeltaToConsider && tile.HeatCapacity != 0f && other.HeatCapacity != 0f) { @@ -145,11 +148,14 @@ namespace Content.Server.Atmos.EntitySystems public void RadiateToSpace(TileAtmosphere tile) { + if (tile.AirArchived == null) + return; + // Considering 0ÂșC as the break even point for radiation in and out. if (tile.Temperature > Atmospherics.T0C) { // Hardcoded space temperature. - var deltaTemperature = (tile.TemperatureArchived - Atmospherics.TCMB); + var deltaTemperature = (tile.AirArchived.Temperature - Atmospherics.TCMB); if ((tile.HeatCapacity > 0) && (MathF.Abs(deltaTemperature) > Atmospherics.MinimumTemperatureDeltaToConsider)) { var heat = tile.ThermalConductivity * deltaTemperature * (tile.HeatCapacity * diff --git a/Content.Server/Atmos/TileAtmosphere.cs b/Content.Server/Atmos/TileAtmosphere.cs index 0026dbbf4f..46a85990fa 100644 --- a/Content.Server/Atmos/TileAtmosphere.cs +++ b/Content.Server/Atmos/TileAtmosphere.cs @@ -22,9 +22,6 @@ namespace Content.Server.Atmos [ViewVariables] public float Temperature { get; set; } = Atmospherics.T20C; - [ViewVariables] - public float TemperatureArchived { get; set; } = Atmospherics.T20C; - [ViewVariables] public TileAtmosphere? PressureSpecificTarget { get; set; } @@ -93,12 +90,16 @@ namespace Content.Server.Atmos [Access(typeof(AtmosphereSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends public GasMixture? Air { get; set; } + /// + /// Like Air, but a copy stored each atmos tick before tile processing takes place. This lets us update Air + /// in-place without affecting the results based on update order. + /// + [ViewVariables] + public GasMixture? AirArchived; + [DataField("lastShare")] public float LastShare; - [ViewVariables] - public readonly float[] MolesArchived = new float[Atmospherics.AdjustedNumberOfGases]; - GasMixture IGasMixtureHolder.Air { get => Air ?? new GasMixture(Atmospherics.CellVolume){ Temperature = Temperature }; @@ -139,6 +140,7 @@ namespace Content.Server.Atmos GridIndex = gridIndex; GridIndices = gridIndices; Air = mixture; + AirArchived = Air != null ? Air.Clone() : null; Space = space; if(immutable) @@ -153,7 +155,7 @@ namespace Content.Server.Atmos NoGridTile = other.NoGridTile; MapAtmosphere = other.MapAtmosphere; Air = other.Air?.Clone(); - Array.Copy(other.MolesArchived, MolesArchived, MolesArchived.Length); + AirArchived = Air != null ? Air.Clone() : null; } public TileAtmosphere()