]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Document Atmospherics ExcitedGroups (#41269)
authorArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
Mon, 3 Nov 2025 11:49:36 +0000 (03:49 -0800)
committerGitHub <noreply@github.com>
Mon, 3 Nov 2025 11:49:36 +0000 (11:49 +0000)
Document ExcitedGroups

Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs
Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs
Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs
Content.Server/Atmos/ExcitedGroup.cs

index 0d622f3067ad60ac96c69a3a87e5347d4a7be325..6ae251dd29b1d911fbe8fad458ceec0c830eae7c 100644 (file)
@@ -4,148 +4,199 @@ using Content.Shared.Atmos.Components;
 using Robust.Shared.Map.Components;
 using Robust.Shared.Utility;
 
-namespace Content.Server.Atmos.EntitySystems
+namespace Content.Server.Atmos.EntitySystems;
+
+public sealed partial class AtmosphereSystem
 {
-    public sealed partial class AtmosphereSystem
+    /*
+     Handles Excited Groups, an optimization routine executed during LINDA
+     that groups active tiles together.
+
+     Groups of active tiles that have very low mole deltas between them
+     are dissolved after a cooldown period, performing a final equalization
+     on all tiles in the group before deactivating them.
+
+     If tiles are so close together in pressure that the final equalization
+     would result in negligible gas transfer, the group is dissolved without
+     performing an equalization.
+
+     This prevents LINDA from constantly transferring tiny amounts of gas
+     between tiles that are already nearly equalized.
+     */
+
+    /// <summary>
+    /// Adds a tile to an <see cref="ExcitedGroups"/>, resetting the group's cooldowns in the process.
+    /// </summary>
+    /// <param name="excitedGroup">The <see cref="ExcitedGroups"/> to add the tile to.</param>
+    /// <param name="tile">The <see cref="TileAtmosphere"/> to add.</param>
+    private void ExcitedGroupAddTile(ExcitedGroup excitedGroup, TileAtmosphere tile)
     {
-        private void ExcitedGroupAddTile(ExcitedGroup excitedGroup, TileAtmosphere tile)
-        {
-            DebugTools.Assert(!excitedGroup.Disposed, "Excited group is disposed!");
-            DebugTools.Assert(tile.ExcitedGroup == null, "Tried to add a tile to an excited group when it's already in another one!");
-            excitedGroup.Tiles.Add(tile);
-            tile.ExcitedGroup = excitedGroup;
-            ExcitedGroupResetCooldowns(excitedGroup);
-        }
+        DebugTools.Assert(!excitedGroup.Disposed, "Excited group is disposed!");
+        DebugTools.Assert(tile.ExcitedGroup == null, "Tried to add a tile to an excited group when it's already in another one!");
+        excitedGroup.Tiles.Add(tile);
+        tile.ExcitedGroup = excitedGroup;
+        ExcitedGroupResetCooldowns(excitedGroup);
+    }
 
-        private void ExcitedGroupRemoveTile(ExcitedGroup excitedGroup, TileAtmosphere tile)
-        {
-            DebugTools.Assert(!excitedGroup.Disposed, "Excited group is disposed!");
-            DebugTools.Assert(tile.ExcitedGroup == excitedGroup, "Tried to remove a tile from an excited group it's not present in!");
-            tile.ExcitedGroup = null;
-            excitedGroup.Tiles.Remove(tile);
-        }
+    /// <summary>
+    /// Removes a tile from an <see cref="ExcitedGroups"/>.
+    /// </summary>
+    /// <param name="excitedGroup">The <see cref="ExcitedGroups"/> to remove the tile from.</param>
+    /// <param name="tile">The <see cref="TileAtmosphere"/> to remove.</param>
+    private void ExcitedGroupRemoveTile(ExcitedGroup excitedGroup, TileAtmosphere tile)
+    {
+        DebugTools.Assert(!excitedGroup.Disposed, "Excited group is disposed!");
+        DebugTools.Assert(tile.ExcitedGroup == excitedGroup, "Tried to remove a tile from an excited group it's not present in!");
+        tile.ExcitedGroup = null;
+        excitedGroup.Tiles.Remove(tile);
+    }
 
-        private void ExcitedGroupMerge(GridAtmosphereComponent gridAtmosphere, ExcitedGroup ourGroup, ExcitedGroup otherGroup)
+    /// <summary>
+    /// Merges two <see cref="ExcitedGroups"/>, transferring all tiles from one to the other.
+    /// The larger group receives the tiles of the smaller group.
+    /// The smaller group is then disposed of without deactivating its tiles.
+    /// </summary>
+    /// <param name="gridAtmosphere">The <see cref="GridAtmosphereComponent"/> of the grid.</param>
+    /// <param name="ourGroup">The first <see cref="ExcitedGroups"/> to merge.</param>
+    /// <param name="otherGroup">The second <see cref="ExcitedGroups"/> to merge.</param>
+    private void ExcitedGroupMerge(GridAtmosphereComponent gridAtmosphere, ExcitedGroup ourGroup, ExcitedGroup otherGroup)
+    {
+        DebugTools.Assert(!ourGroup.Disposed, "Excited group is disposed!");
+        DebugTools.Assert(!otherGroup.Disposed, "Excited group is disposed!");
+        DebugTools.Assert(gridAtmosphere.ExcitedGroups.Contains(ourGroup), "Grid Atmosphere does not contain Excited Group!");
+        DebugTools.Assert(gridAtmosphere.ExcitedGroups.Contains(otherGroup), "Grid Atmosphere does not contain Excited Group!");
+        var ourSize = ourGroup.Tiles.Count;
+        var otherSize = otherGroup.Tiles.Count;
+
+        ExcitedGroup winner;
+        ExcitedGroup loser;
+
+        if (ourSize > otherSize)
         {
-            DebugTools.Assert(!ourGroup.Disposed, "Excited group is disposed!");
-            DebugTools.Assert(!otherGroup.Disposed, "Excited group is disposed!");
-            DebugTools.Assert(gridAtmosphere.ExcitedGroups.Contains(ourGroup), "Grid Atmosphere does not contain Excited Group!");
-            DebugTools.Assert(gridAtmosphere.ExcitedGroups.Contains(otherGroup), "Grid Atmosphere does not contain Excited Group!");
-            var ourSize = ourGroup.Tiles.Count;
-            var otherSize = otherGroup.Tiles.Count;
-
-            ExcitedGroup winner;
-            ExcitedGroup loser;
-
-            if (ourSize > otherSize)
-            {
-                winner = ourGroup;
-                loser = otherGroup;
-            }
-            else
-            {
-                winner = otherGroup;
-                loser = ourGroup;
-            }
-
-            foreach (var tile in loser.Tiles)
-            {
-                tile.ExcitedGroup = winner;
-                winner.Tiles.Add(tile);
-            }
-
-            loser.Tiles.Clear();
-            ExcitedGroupDispose(gridAtmosphere, loser);
-            ExcitedGroupResetCooldowns(winner);
+            winner = ourGroup;
+            loser = otherGroup;
         }
-
-        private void ExcitedGroupResetCooldowns(ExcitedGroup excitedGroup)
+        else
         {
-            DebugTools.Assert(!excitedGroup.Disposed, "Excited group is disposed!");
-            excitedGroup.BreakdownCooldown = 0;
-            excitedGroup.DismantleCooldown = 0;
+            winner = otherGroup;
+            loser = ourGroup;
         }
 
-        private void ExcitedGroupSelfBreakdown(
-            Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
-            ExcitedGroup excitedGroup)
+        foreach (var tile in loser.Tiles)
         {
-            DebugTools.Assert(!excitedGroup.Disposed, "Excited group is disposed!");
-            DebugTools.Assert(ent.Comp1.ExcitedGroups.Contains(excitedGroup), "Grid Atmosphere does not contain Excited Group!");
-            var combined = new GasMixture(Atmospherics.CellVolume);
-
-            var tileSize = excitedGroup.Tiles.Count;
+            tile.ExcitedGroup = winner;
+            winner.Tiles.Add(tile);
+        }
 
-            if (excitedGroup.Disposed)
-                return;
+        loser.Tiles.Clear();
+        ExcitedGroupDispose(gridAtmosphere, loser);
+        ExcitedGroupResetCooldowns(winner);
+    }
 
-            if (tileSize == 0)
-            {
-                ExcitedGroupDispose(ent.Comp1, excitedGroup);
-                return;
-            }
+    /// <summary>
+    /// Resets the cooldowns of an excited group.
+    /// </summary>
+    /// <param name="excitedGroup">The <see cref="ExcitedGroups"/> to reset cooldowns for.</param>
+    private void ExcitedGroupResetCooldowns(ExcitedGroup excitedGroup)
+    {
+        DebugTools.Assert(!excitedGroup.Disposed, "Excited group is disposed!");
+        excitedGroup.BreakdownCooldown = 0;
+        excitedGroup.DismantleCooldown = 0;
+    }
 
-            foreach (var tile in excitedGroup.Tiles)
-            {
-                if (tile?.Air == null)
-                    continue;
+    /// <summary>
+    /// Performs a final equalization on all tiles in an excited group before deactivating it.
+    /// </summary>
+    /// <param name="ent">The grid.</param>
+    /// <param name="excitedGroup">The <see cref="ExcitedGroups"/> to equalize and dissolve.</param>
+    private void ExcitedGroupSelfBreakdown(
+        Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
+        ExcitedGroup excitedGroup)
+    {
+        DebugTools.Assert(!excitedGroup.Disposed, "Excited group is disposed!");
+        DebugTools.Assert(ent.Comp1.ExcitedGroups.Contains(excitedGroup), "Grid Atmosphere does not contain Excited Group!");
+        var combined = new GasMixture(Atmospherics.CellVolume);
 
-                Merge(combined, tile.Air);
+        var tileSize = excitedGroup.Tiles.Count;
 
-                if (!ExcitedGroupsSpaceIsAllConsuming || !tile.Space)
-                    continue;
+        if (excitedGroup.Disposed)
+            return;
 
-                combined.Clear();
-                break;
-            }
+        if (tileSize == 0)
+        {
+            ExcitedGroupDispose(ent.Comp1, excitedGroup);
+            return;
+        }
 
-            combined.Multiply(1 / (float)tileSize);
+        // Combine all gasses in the group into a single mixture
+        // for distribution into each individual tile.
+        foreach (var tile in excitedGroup.Tiles)
+        {
+            if (tile?.Air == null)
+                continue;
 
-            foreach (var tile in excitedGroup.Tiles)
-            {
-                if (tile?.Air == null)
-                    continue;
+            Merge(combined, tile.Air);
 
-                tile.Air.CopyFrom(combined);
-                InvalidateVisuals(ent, tile);
-            }
+            // If this tile is space and space is all-consuming, the final equalization
+            // will result in a vacuum, so we can skip the rest of the equalization.
+            if (!ExcitedGroupsSpaceIsAllConsuming || !tile.Space)
+                continue;
 
-            excitedGroup.BreakdownCooldown = 0;
+            combined.Clear();
+            break;
         }
 
-        /// <summary>
-        /// This de-activates and removes all tiles in an excited group.
-        /// </summary>
-        private void DeactivateGroupTiles(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup)
+        combined.Multiply(1 / (float)tileSize);
+
+        // Distribute the combined mixture evenly to all tiles in the group.
+        foreach (var tile in excitedGroup.Tiles)
         {
-            foreach (var tile in excitedGroup.Tiles)
-            {
-                tile.ExcitedGroup = null;
-                RemoveActiveTile(gridAtmosphere, tile);
-            }
+            if (tile?.Air == null)
+                continue;
 
-            excitedGroup.Tiles.Clear();
+            tile.Air.CopyFrom(combined);
+            InvalidateVisuals(ent, tile);
         }
 
-        /// <summary>
-        /// This removes an excited group without de-activating its tiles.
-        /// </summary>
-        private void ExcitedGroupDispose(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup)
+        excitedGroup.BreakdownCooldown = 0;
+    }
+
+    /// <summary>
+    /// Deactivates and removes all tiles from an excited group without performing a final equalization.
+    /// Used when an excited group is expected to be nearly equalized already to avoid unnecessary processing.
+    /// </summary>
+    /// <param name="gridAtmosphere">The <see cref="GridAtmosphereComponent"/> of the grid.</param>
+    /// <param name="excitedGroup">The <see cref="ExcitedGroups"/> to dissolve.</param>
+    private void DeactivateGroupTiles(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup)
+    {
+        foreach (var tile in excitedGroup.Tiles)
         {
-            if (excitedGroup.Disposed)
-                return;
+            tile.ExcitedGroup = null;
+            RemoveActiveTile(gridAtmosphere, tile);
+        }
+
+        excitedGroup.Tiles.Clear();
+    }
 
-            DebugTools.Assert(gridAtmosphere.ExcitedGroups.Contains(excitedGroup), "Grid Atmosphere does not contain Excited Group!");
+    /// <summary>
+    /// Removes and disposes of an excited group without performing any final equalization
+    /// or deactivation of its tiles.
+    /// </summary>
+    private void ExcitedGroupDispose(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup)
+    {
+        if (excitedGroup.Disposed)
+            return;
 
-            excitedGroup.Disposed = true;
-            gridAtmosphere.ExcitedGroups.Remove(excitedGroup);
+        DebugTools.Assert(gridAtmosphere.ExcitedGroups.Contains(excitedGroup), "Grid Atmosphere does not contain Excited Group!");
 
-            foreach (var tile in excitedGroup.Tiles)
-            {
-                tile.ExcitedGroup = null;
-            }
+        excitedGroup.Disposed = true;
+        gridAtmosphere.ExcitedGroups.Remove(excitedGroup);
 
-            excitedGroup.Tiles.Clear();
+        foreach (var tile in excitedGroup.Tiles)
+        {
+            tile.ExcitedGroup = null;
         }
+
+        excitedGroup.Tiles.Clear();
     }
 }
index 55b38924c0ee57e33603ed2a04dd2fc0408e350f..ad19770bfe637c969531ae245c094fd8cd230d10 100644 (file)
@@ -129,9 +129,16 @@ namespace Content.Server.Atmos.EntitySystems
 
             switch (tile.LastShare)
             {
+                // Refresh this tile's suspension cooldown if it had significant sharing.
                 case > Atmospherics.MinimumAirToSuspend:
                     ExcitedGroupResetCooldowns(tile.ExcitedGroup);
                     break;
+
+                // If this tile moved a very small amount of air, but not enough to matter,
+                // we set the dismantle cooldown to 0.
+                // This dissolves the group without performing an equalization as we expect
+                // the group to be mostly equalized already if we're moving around miniscule
+                // amounts of air.
                 case > Atmospherics.MinimumMolesDeltaToMove:
                     tile.ExcitedGroup.DismantleCooldown = 0;
                     break;
index 72e4b5f151131cd76315b40b893bb3a536139d74..c0f081f9ba9c4383ce1c26948b8c240bd1427300 100644 (file)
@@ -365,7 +365,6 @@ namespace Content.Server.Atmos.EntitySystems
                     ExcitedGroupSelfBreakdown(ent, excitedGroup);
                 else if (excitedGroup.DismantleCooldown > Atmospherics.ExcitedGroupsDismantleCycles)
                     DeactivateGroupTiles(gridAtmosphere, excitedGroup);
-                // TODO ATMOS. What is the point of this? why is this only de-exciting the group? Shouldn't it also dismantle it?
 
                 if (number++ < LagCheckIterations)
                     continue;
index 73dc6c23292fbb077f92b92838b5a060eac79f43..1554318f65486f4db1e9994820e546a95de4761f 100644 (file)
@@ -1,13 +1,44 @@
-namespace Content.Server.Atmos
+namespace Content.Server.Atmos;
+
+/// <summary>
+/// <para>Internal Atmospherics class that stores data about a group of <see cref="TileAtmosphere"/>s
+/// that are excited and need to be processed.</para>
+///
+/// <para>Excited Groups is an optimization routine executed during LINDA
+/// that bunches small groups of active <see cref="TileAtmosphere"/>s
+/// together and performs equalization processing on the entire group when the group dissolves.
+/// Dissolution happens when LINDA operations between the tiles decrease to very low mole deltas.</para>
+/// </summary>
+public sealed class ExcitedGroup
 {
-    public sealed class ExcitedGroup
-    {
-        [ViewVariables] public bool Disposed = false;
+    /// <summary>
+    /// Whether this Active Group has been disposed of.
+    /// Used to make sure we don't perform operations on active groups that
+    /// we've already dissolved.
+    /// </summary>
+    [ViewVariables]
+    public bool Disposed = false;
 
-        [ViewVariables] public readonly List<TileAtmosphere> Tiles = new(100);
+    /// <summary>
+    /// List of tiles that belong to this excited group.
+    /// </summary>
+    [ViewVariables]
+    public readonly List<TileAtmosphere> Tiles = new(100);
 
-        [ViewVariables] public int DismantleCooldown { get; set; } = 0;
+    /// <summary>
+    /// Cycles before this excited group will be queued for dismantling.
+    /// Dismantling is the process of equalizing the atmosphere
+    /// across all tiles in the excited group and removing the group.
+    /// </summary>
+    [ViewVariables]
+    public int DismantleCooldown = 0;
 
-        [ViewVariables] public int BreakdownCooldown { get; set; } = 0;
-    }
+    /// <summary>
+    /// Cycles before this excited group will be allowed to break down and deactivate.
+    /// Breakdown occurs when the excited group is small enough and inactive enough
+    /// to be safely removed without equalization. Used where the mole deltas across
+    /// the group are very low but not high enough for an equalization to occur.
+    /// </summary>
+    [ViewVariables]
+    public int BreakdownCooldown = 0;
 }