From e6bbd40e724bf4516d62b839a1b6dc6168b244dd Mon Sep 17 00:00:00 2001 From: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com> Date: Mon, 3 Nov 2025 03:49:36 -0800 Subject: [PATCH] Document Atmospherics ExcitedGroups (#41269) Document ExcitedGroups --- .../AtmosphereSystem.ExcitedGroup.cs | 273 +++++++++++------- .../EntitySystems/AtmosphereSystem.LINDA.cs | 7 + .../AtmosphereSystem.Processing.cs | 1 - Content.Server/Atmos/ExcitedGroup.cs | 47 ++- 4 files changed, 208 insertions(+), 120 deletions(-) diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs index 0d622f3067..6ae251dd29 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.ExcitedGroup.cs @@ -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. + */ + + /// + /// Adds a tile to an , resetting the group's cooldowns in the process. + /// + /// The to add the tile to. + /// The to add. + 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); - } + /// + /// Removes a tile from an . + /// + /// The to remove the tile from. + /// The to remove. + 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) + /// + /// Merges two , 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. + /// + /// The of the grid. + /// The first to merge. + /// The second to merge. + 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 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; - } + /// + /// Resets the cooldowns of an excited group. + /// + /// The to reset cooldowns for. + 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; + /// + /// Performs a final equalization on all tiles in an excited group before deactivating it. + /// + /// The grid. + /// The to equalize and dissolve. + private void ExcitedGroupSelfBreakdown( + Entity 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; } - /// - /// This de-activates and removes all tiles in an excited group. - /// - 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); } - /// - /// This removes an excited group without de-activating its tiles. - /// - private void ExcitedGroupDispose(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup) + excitedGroup.BreakdownCooldown = 0; + } + + /// + /// 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. + /// + /// The of the grid. + /// The to dissolve. + 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!"); + /// + /// Removes and disposes of an excited group without performing any final equalization + /// or deactivation of its tiles. + /// + 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(); } } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs index 55b38924c0..ad19770bfe 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs @@ -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; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs index 72e4b5f151..c0f081f9ba 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs @@ -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; diff --git a/Content.Server/Atmos/ExcitedGroup.cs b/Content.Server/Atmos/ExcitedGroup.cs index 73dc6c2329..1554318f65 100644 --- a/Content.Server/Atmos/ExcitedGroup.cs +++ b/Content.Server/Atmos/ExcitedGroup.cs @@ -1,13 +1,44 @@ -namespace Content.Server.Atmos +namespace Content.Server.Atmos; + +/// +/// Internal Atmospherics class that stores data about a group of s +/// that are excited and need to be processed. +/// +/// Excited Groups is an optimization routine executed during LINDA +/// that bunches small groups of active 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. +/// +public sealed class ExcitedGroup { - public sealed class ExcitedGroup - { - [ViewVariables] public bool Disposed = false; + /// + /// 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. + /// + [ViewVariables] + public bool Disposed = false; - [ViewVariables] public readonly List Tiles = new(100); + /// + /// List of tiles that belong to this excited group. + /// + [ViewVariables] + public readonly List Tiles = new(100); - [ViewVariables] public int DismantleCooldown { get; set; } = 0; + /// + /// 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. + /// + [ViewVariables] + public int DismantleCooldown = 0; - [ViewVariables] public int BreakdownCooldown { get; set; } = 0; - } + /// + /// 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. + /// + [ViewVariables] + public int BreakdownCooldown = 0; } -- 2.51.2