Partial atmos refactor (#22521)

* Reduce atmos component queries

* Remove method events

* Cache airtight data

* Make MolesArchived nullable

* Fix airtight cache

* only get tile def once

* Immutable mixtures

* firelock queries

* misc

* misc cleanup

* Trim disconnected tiles

* Fix merge issues and bugs

* Why does the PR keep increasing in scope

* debug overlay

* Fix bugs

* Fix test, remove unused events

* Add setmapatmos command

* Fix overlays

* Add map check

* A

* Resolve conflicts with #26102

* Remove some obsolete methods
This commit is contained in:
Leon Friedrich
2024-03-24 03:34:56 +11:00
committed by GitHub
parent 05f282f5ce
commit 18a35e7e83
43 changed files with 922 additions and 666 deletions

View File

@@ -0,0 +1,94 @@
using Content.Server.Administration;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Administration;
using Content.Shared.Atmos;
using Robust.Shared.Console;
using Robust.Shared.Map;
namespace Content.Server.Atmos.Commands;
[AdminCommand(AdminFlags.Admin)]
public sealed class AddMapAtmosCommand : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entities = default!;
[Dependency] private readonly IMapManager _map = default!;
private const string _cmd = "cmd-set-map-atmos";
public override string Command => "setmapatmos";
public override string Description => Loc.GetString($"{_cmd}-desc");
public override string Help => Loc.GetString($"{_cmd}-help");
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length < 2)
{
shell.WriteLine(Help);
return;
}
int.TryParse(args[0], out var id);
var map = _map.GetMapEntityId(new MapId(id));
if (!map.IsValid())
{
shell.WriteError(Loc.GetString("cmd-parse-failure-mapid", ("arg", args[0])));
return;
}
if (!bool.TryParse(args[1], out var space))
{
shell.WriteError(Loc.GetString("cmd-parse-failure-bool", ("arg", args[1])));
return;
}
if (space || args.Length < 4)
{
_entities.RemoveComponent<MapAtmosphereComponent>(map);
shell.WriteLine(Loc.GetString($"{_cmd}-removed", ("map", id)));
return;
}
if (!float.TryParse(args[2], out var temp))
{
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[2])));
return;
}
var mix = new GasMixture(Atmospherics.CellVolume) {Temperature = Math.Min(temp, Atmospherics.TCMB)};
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
{
if (args.Length == 3 + i)
break;
if (!float.TryParse(args[3+i], out var moles))
{
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[3+i])));
return;
}
mix.AdjustMoles(i, moles);
}
var atmos = _entities.EntitySysManager.GetEntitySystem<AtmosphereSystem>();
atmos.SetMapAtmosphere(map, space, mix);
shell.WriteLine(Loc.GetString($"{_cmd}-updated", ("map", id)));
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length == 1)
return CompletionResult.FromHintOptions(CompletionHelper.MapIds(_entities), Loc.GetString($"{_cmd}-hint-map"));
if (args.Length == 2)
return CompletionResult.FromHintOptions(new[]{ "false", "true"}, Loc.GetString($"{_cmd}-hint-space"));
if (!bool.TryParse(args[1], out var space) || space)
return CompletionResult.Empty;
if (args.Length == 3)
return CompletionResult.FromHint(Loc.GetString($"{_cmd}-hint-temp"));
var gas = (Gas) args.Length - 4;
return CompletionResult.FromHint(Loc.GetString($"{_cmd}-hint-gas" , ("gas", gas.ToString())));
}
}

View File

@@ -1,9 +1,10 @@
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.Atmos.Components
{
[RegisterComponent]
[RegisterComponent, Access(typeof(AirtightSystem))]
public sealed partial class AirtightComponent : Component
{
public (EntityUid Grid, Vector2i Tile) LastPosition { get; set; }
@@ -29,6 +30,7 @@ namespace Content.Server.Atmos.Components
[DataField("noAirWhenFullyAirBlocked")]
public bool NoAirWhenFullyAirBlocked { get; set; } = true;
[Access(Other = AccessPermissions.ReadWriteExecute)]
public AtmosDirection AirBlockedDirection => (AtmosDirection)CurrentAirBlockedDirection;
}
}

View File

@@ -28,6 +28,9 @@ namespace Content.Server.Atmos.Components
[IncludeDataField(customTypeSerializer:typeof(TileAtmosCollectionSerializer))]
public Dictionary<Vector2i, TileAtmosphere> Tiles = new(1000);
[ViewVariables]
public HashSet<TileAtmosphere> MapTiles = new(1000);
[ViewVariables]
public readonly HashSet<TileAtmosphere> ActiveTiles = new(1000);
@@ -80,7 +83,10 @@ namespace Content.Server.Atmos.Components
public readonly HashSet<Vector2i> InvalidatedCoords = new(1000);
[ViewVariables]
public readonly Queue<Vector2i> CurrentRunInvalidatedCoordinates = new();
public readonly Queue<TileAtmosphere> CurrentRunInvalidatedTiles = new();
[ViewVariables]
public readonly List<TileAtmosphere> PossiblyDisconnectedTiles = new(100);
[ViewVariables]
public int InvalidatedCoordsCount => InvalidatedCoords.Count;

View File

@@ -12,12 +12,14 @@ public sealed partial class MapAtmosphereComponent : SharedMapAtmosphereComponen
/// <summary>
/// The default GasMixture a map will have. Space mixture by default.
/// </summary>
[DataField("mixture"), ViewVariables(VVAccess.ReadWrite)]
public GasMixture? Mixture = GasMixture.SpaceGas;
[DataField, ViewVariables(VVAccess.ReadWrite)]
public GasMixture Mixture = GasMixture.SpaceGas;
/// <summary>
/// Whether empty tiles will be considered space or not.
/// </summary>
[DataField("space"), ViewVariables(VVAccess.ReadWrite)]
[DataField, ViewVariables(VVAccess.ReadWrite)]
public bool Space = true;
public SharedGasTileOverlaySystem.GasOverlayData Overlay;
}

View File

@@ -2,7 +2,6 @@ using Content.Server.Atmos.Components;
using Content.Server.Explosion.EntitySystems;
using Content.Shared.Atmos;
using JetBrains.Annotations;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
namespace Content.Server.Atmos.EntitySystems
@@ -10,7 +9,7 @@ namespace Content.Server.Atmos.EntitySystems
[UsedImplicitly]
public sealed class AirtightSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly ExplosionSystem _explosionSystem = default!;
@@ -121,19 +120,16 @@ namespace Content.Server.Atmos.EntitySystems
if (!xform.Anchored || !TryComp(xform.GridUid, out MapGridComponent? grid))
return;
airtight.LastPosition = (xform.GridUid.Value, grid.TileIndicesFor(xform.Coordinates));
InvalidatePosition(airtight.LastPosition.Item1, airtight.LastPosition.Item2, airtight.FixVacuum && !airtight.AirBlocked);
var indices = _transform.GetGridTilePositionOrDefault((ent, xform), grid);
airtight.LastPosition = (xform.GridUid.Value, indices);
InvalidatePosition((xform.GridUid.Value, grid), indices);
}
public void InvalidatePosition(EntityUid gridId, Vector2i pos, bool fixVacuum = false)
public void InvalidatePosition(Entity<MapGridComponent?> grid, Vector2i pos)
{
if (!TryComp(gridId, out MapGridComponent? grid))
return;
var query = EntityManager.GetEntityQuery<AirtightComponent>();
_explosionSystem.UpdateAirtightMap(gridId, pos, grid, query);
// TODO make atmos system use query
_atmosphereSystem.InvalidateTile(gridId, pos);
_explosionSystem.UpdateAirtightMap(grid, pos, grid, query);
_atmosphereSystem.InvalidateTile(grid.Owner, pos);
}
private AtmosDirection Rotate(AtmosDirection myDirection, Angle myAngle)

View File

@@ -97,22 +97,19 @@ namespace Content.Server.Atmos.EntitySystems
}
}
private AtmosDebugOverlayData ConvertTileToData(TileAtmosphere? tile)
private AtmosDebugOverlayData? ConvertTileToData(TileAtmosphere tile)
{
if (tile == null)
return default;
return new AtmosDebugOverlayData(
tile.GridIndices,
tile.Air?.Temperature ?? default,
tile.Air?.Moles,
tile.PressureDirection,
tile.LastPressureDirection,
tile.BlockedAirflow,
tile.AirtightData.BlockedDirections,
tile.ExcitedGroup?.GetHashCode(),
tile.Space,
false,
false);
tile.MapAtmosphere,
tile.NoGridTile);
}
public override void Update(float frameTime)

View File

@@ -1,37 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using Content.Server.Atmos.Components;
using Robust.Shared.Map;
using Robust.Shared.Map.Enumerators;
namespace Content.Server.Atmos.EntitySystems;
public struct AtmosObstructionEnumerator
{
private AnchoredEntitiesEnumerator _enumerator;
private EntityQuery<AirtightComponent> _query;
public AtmosObstructionEnumerator(AnchoredEntitiesEnumerator enumerator, EntityQuery<AirtightComponent> query)
{
_enumerator = enumerator;
_query = query;
}
public bool MoveNext([NotNullWhen(true)] out AirtightComponent? airtight)
{
if (!_enumerator.MoveNext(out var uid))
{
airtight = null;
return false;
}
// No rider, it makes it uglier.
// ReSharper disable once ConvertIfStatementToReturnStatement
if (!_query.TryGetComponent(uid.Value, out airtight))
{
// ReSharper disable once TailRecursiveCall
return MoveNext(out airtight);
}
return true;
}
}

View File

@@ -85,10 +85,10 @@ public partial class AtmosphereSystem
return ev.Mixtures!;
}
public void InvalidateTile(EntityUid gridUid, Vector2i tile)
public void InvalidateTile(Entity<GridAtmosphereComponent?> entity, Vector2i tile)
{
var ev = new InvalidateTileMethodEvent(gridUid, tile);
RaiseLocalEvent(gridUid, ref ev);
if (_atmosQuery.Resolve(entity.Owner, ref entity.Comp, false))
entity.Comp.InvalidatedCoords.Add(tile);
}
public GasMixture?[]? GetTileMixtures(EntityUid? gridUid, EntityUid? mapUid, List<Vector2i> tiles, bool excite = false)
@@ -176,11 +176,11 @@ public partial class AtmosphereSystem
public bool IsTileAirBlocked(EntityUid gridUid, Vector2i tile, AtmosDirection directions = AtmosDirection.All, MapGridComponent? mapGridComp = null)
{
var ev = new IsTileAirBlockedMethodEvent(gridUid, tile, directions, mapGridComp);
RaiseLocalEvent(gridUid, ref ev);
if (!Resolve(gridUid, ref mapGridComp))
return false;
// If nothing handled the event, it'll default to true.
return ev.Result;
var data = GetAirtightData(gridUid, mapGridComp, tile);
return data.BlockedDirections.IsFlagSet(directions);
}
public bool IsTileSpace(EntityUid? gridUid, EntityUid? mapUid, Vector2i tile, MapGridComponent? mapGridComp = null)
@@ -231,12 +231,6 @@ public partial class AtmosphereSystem
return ev.Result ?? Enumerable.Empty<GasMixture>();
}
public void UpdateAdjacent(EntityUid gridUid, Vector2i tile, MapGridComponent? mapGridComp = null)
{
var ev = new UpdateAdjacentMethodEvent(gridUid, tile, mapGridComp);
RaiseLocalEvent(gridUid, ref ev);
}
public void HotspotExpose(EntityUid gridUid, Vector2i tile, float exposedTemperature, float exposedVolume,
EntityUid? sparkSourceUid = null, bool soh = false)
{
@@ -259,12 +253,6 @@ public partial class AtmosphereSystem
return ev.Result;
}
public void FixTileVacuum(EntityUid gridUid, Vector2i tile)
{
var ev = new FixTileVacuumMethodEvent(gridUid, tile);
RaiseLocalEvent(gridUid, ref ev);
}
public void AddPipeNet(EntityUid gridUid, PipeNet pipeNet)
{
var ev = new AddPipeNetMethodEvent(gridUid, pipeNet);
@@ -307,9 +295,6 @@ public partial class AtmosphereSystem
[ByRefEvent] private record struct GetAllMixturesMethodEvent
(EntityUid Grid, bool Excite = false, IEnumerable<GasMixture>? Mixtures = null, bool Handled = false);
[ByRefEvent] private record struct InvalidateTileMethodEvent
(EntityUid Grid, Vector2i Tile, bool Handled = false);
[ByRefEvent] private record struct GetTileMixturesMethodEvent
(EntityUid? GridUid, EntityUid? MapUid, List<Vector2i> Tiles, bool Excite = false, GasMixture?[]? Mixtures = null, bool Handled = false);
@@ -319,16 +304,6 @@ public partial class AtmosphereSystem
[ByRefEvent] private record struct ReactTileMethodEvent
(EntityUid GridId, Vector2i Tile, ReactionResult Result = default, bool Handled = false);
[ByRefEvent] private record struct IsTileAirBlockedMethodEvent
(EntityUid Grid, Vector2i Tile, AtmosDirection Direction = AtmosDirection.All, MapGridComponent? MapGridComponent = null, bool Result = false, bool Handled = false)
{
/// <summary>
/// True if one of the enabled blockers has <see cref="AirtightComponent.NoAirWhenFullyAirBlocked"/>. Note
/// that this does not actually check if all directions are blocked.
/// </summary>
public bool NoAir = false;
}
[ByRefEvent] private record struct IsTileSpaceMethodEvent
(EntityUid? Grid, EntityUid? Map, Vector2i Tile, MapGridComponent? MapGridComponent = null, bool Result = true, bool Handled = false);
@@ -339,9 +314,6 @@ public partial class AtmosphereSystem
(EntityUid Grid, Vector2i Tile, bool IncludeBlocked, bool Excite,
IEnumerable<GasMixture>? Result = null, bool Handled = false);
[ByRefEvent] private record struct UpdateAdjacentMethodEvent
(EntityUid Grid, Vector2i Tile, MapGridComponent? MapGridComponent = null, bool Handled = false);
[ByRefEvent] private record struct HotspotExposeMethodEvent
(EntityUid Grid, EntityUid? SparkSourceUid, Vector2i Tile, float ExposedTemperature, float ExposedVolume, bool soh, bool Handled = false);
@@ -351,9 +323,6 @@ public partial class AtmosphereSystem
[ByRefEvent] private record struct IsHotspotActiveMethodEvent
(EntityUid Grid, Vector2i Tile, bool Result = false, bool Handled = false);
[ByRefEvent] private record struct FixTileVacuumMethodEvent
(EntityUid Grid, Vector2i Tile, bool Handled = false);
[ByRefEvent] private record struct AddPipeNetMethodEvent
(EntityUid Grid, PipeNet PipeNet, bool Handled = false);

View File

@@ -72,7 +72,8 @@ namespace Content.Server.Atmos.EntitySystems
var tileSize = excitedGroup.Tiles.Count;
if (excitedGroup.Disposed) return;
if (excitedGroup.Disposed)
return;
if (tileSize == 0)
{
@@ -98,7 +99,9 @@ namespace Content.Server.Atmos.EntitySystems
foreach (var tile in excitedGroup.Tiles)
{
if (tile?.Air == null) continue;
if (tile?.Air == null)
continue;
tile.Air.CopyFromMutable(combined);
InvalidateVisuals(tile.GridIndex, tile.GridIndices);
}
@@ -106,21 +109,23 @@ namespace Content.Server.Atmos.EntitySystems
excitedGroup.BreakdownCooldown = 0;
}
private void ExcitedGroupDismantle(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup, bool unexcite = true)
/// <summary>
/// This de-activates and removes all tiles in an excited group.
/// </summary>
private void DeactivateGroupTiles(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup)
{
foreach (var tile in excitedGroup.Tiles)
{
tile.ExcitedGroup = null;
if (!unexcite)
continue;
RemoveActiveTile(gridAtmosphere, tile);
}
excitedGroup.Tiles.Clear();
}
/// <summary>
/// This removes an excited group without de-activating its tiles.
/// </summary>
private void ExcitedGroupDispose(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup)
{
if (excitedGroup.Disposed)
@@ -129,9 +134,14 @@ namespace Content.Server.Atmos.EntitySystems
DebugTools.Assert(gridAtmosphere.ExcitedGroups.Contains(excitedGroup), "Grid Atmosphere does not contain Excited Group!");
excitedGroup.Disposed = true;
gridAtmosphere.ExcitedGroups.Remove(excitedGroup);
ExcitedGroupDismantle(gridAtmosphere, excitedGroup, false);
foreach (var tile in excitedGroup.Tiles)
{
tile.ExcitedGroup = null;
}
excitedGroup.Tiles.Clear();
}
}
}

View File

@@ -14,6 +14,7 @@ public sealed partial class AtmosphereSystem
private void InitializeGridAtmosphere()
{
SubscribeLocalEvent<GridAtmosphereComponent, ComponentInit>(OnGridAtmosphereInit);
SubscribeLocalEvent<GridAtmosphereComponent, ComponentStartup>(OnGridAtmosphereStartup);
SubscribeLocalEvent<GridAtmosphereComponent, ComponentRemove>(OnAtmosphereRemove);
SubscribeLocalEvent<GridAtmosphereComponent, GridSplitEvent>(OnGridSplit);
@@ -22,19 +23,15 @@ public sealed partial class AtmosphereSystem
SubscribeLocalEvent<GridAtmosphereComponent, HasAtmosphereMethodEvent>(GridHasAtmosphere);
SubscribeLocalEvent<GridAtmosphereComponent, IsSimulatedGridMethodEvent>(GridIsSimulated);
SubscribeLocalEvent<GridAtmosphereComponent, GetAllMixturesMethodEvent>(GridGetAllMixtures);
SubscribeLocalEvent<GridAtmosphereComponent, InvalidateTileMethodEvent>(GridInvalidateTile);
SubscribeLocalEvent<GridAtmosphereComponent, GetTileMixtureMethodEvent>(GridGetTileMixture);
SubscribeLocalEvent<GridAtmosphereComponent, GetTileMixturesMethodEvent>(GridGetTileMixtures);
SubscribeLocalEvent<GridAtmosphereComponent, ReactTileMethodEvent>(GridReactTile);
SubscribeLocalEvent<GridAtmosphereComponent, IsTileAirBlockedMethodEvent>(GridIsTileAirBlocked);
SubscribeLocalEvent<GridAtmosphereComponent, IsTileSpaceMethodEvent>(GridIsTileSpace);
SubscribeLocalEvent<GridAtmosphereComponent, GetAdjacentTilesMethodEvent>(GridGetAdjacentTiles);
SubscribeLocalEvent<GridAtmosphereComponent, GetAdjacentTileMixturesMethodEvent>(GridGetAdjacentTileMixtures);
SubscribeLocalEvent<GridAtmosphereComponent, UpdateAdjacentMethodEvent>(GridUpdateAdjacent);
SubscribeLocalEvent<GridAtmosphereComponent, HotspotExposeMethodEvent>(GridHotspotExpose);
SubscribeLocalEvent<GridAtmosphereComponent, HotspotExtinguishMethodEvent>(GridHotspotExtinguish);
SubscribeLocalEvent<GridAtmosphereComponent, IsHotspotActiveMethodEvent>(GridIsHotspotActive);
SubscribeLocalEvent<GridAtmosphereComponent, FixTileVacuumMethodEvent>(GridFixTileVacuum);
SubscribeLocalEvent<GridAtmosphereComponent, AddPipeNetMethodEvent>(GridAddPipeNet);
SubscribeLocalEvent<GridAtmosphereComponent, RemovePipeNetMethodEvent>(GridRemovePipeNet);
SubscribeLocalEvent<GridAtmosphereComponent, AddAtmosDeviceMethodEvent>(GridAddAtmosDevice);
@@ -56,22 +53,23 @@ public sealed partial class AtmosphereSystem
}
}
private void OnGridAtmosphereInit(EntityUid uid, GridAtmosphereComponent gridAtmosphere, ComponentInit args)
private void OnGridAtmosphereInit(EntityUid uid, GridAtmosphereComponent component, ComponentInit args)
{
base.Initialize();
EnsureComp<GasTileOverlayComponent>(uid);
foreach (var tile in component.Tiles.Values)
{
tile.GridIndex = uid;
}
}
private void OnGridAtmosphereStartup(EntityUid uid, GridAtmosphereComponent component, ComponentStartup args)
{
if (!TryComp(uid, out MapGridComponent? mapGrid))
return;
EnsureComp<GasTileOverlayComponent>(uid);
foreach (var (indices, tile) in gridAtmosphere.Tiles)
{
gridAtmosphere.InvalidatedCoords.Add(indices);
tile.GridIndex = uid;
}
GridRepopulateTiles((uid, mapGrid, gridAtmosphere));
InvalidateAllTiles((uid, mapGrid, component));
}
private void OnGridSplit(EntityUid uid, GridAtmosphereComponent originalGridAtmos, ref GridSplitEvent args)
@@ -104,8 +102,7 @@ public sealed partial class AtmosphereSystem
continue;
// Copy a bunch of data over... Not great, maybe put this in TileAtmosphere?
newTileAtmosphere.Air = tileAtmosphere.Air?.Clone() ?? null;
newTileAtmosphere.MolesArchived = newTileAtmosphere.Air == null ? null : new float[Atmospherics.AdjustedNumberOfGases];
newTileAtmosphere.Air = tileAtmosphere.Air?.Clone();
newTileAtmosphere.Hotspot = tileAtmosphere.Hotspot;
newTileAtmosphere.HeatCapacity = tileAtmosphere.HeatCapacity;
newTileAtmosphere.Temperature = tileAtmosphere.Temperature;
@@ -170,15 +167,6 @@ public sealed partial class AtmosphereSystem
args.Handled = true;
}
private void GridInvalidateTile(EntityUid uid, GridAtmosphereComponent component, ref InvalidateTileMethodEvent args)
{
if (args.Handled)
return;
component.InvalidatedCoords.Add(args.Tile);
args.Handled = true;
}
private void GridGetTileMixture(EntityUid uid, GridAtmosphereComponent component,
ref GetTileMixtureMethodEvent args)
{
@@ -233,43 +221,6 @@ public sealed partial class AtmosphereSystem
args.Handled = true;
}
private void GridIsTileAirBlocked(EntityUid uid, GridAtmosphereComponent component,
ref IsTileAirBlockedMethodEvent args)
{
if (args.Handled)
return;
var mapGridComp = args.MapGridComponent;
if (!Resolve(uid, ref mapGridComp))
return;
var directions = AtmosDirection.Invalid;
var enumerator = GetObstructingComponentsEnumerator(mapGridComp, args.Tile);
while (enumerator.MoveNext(out var obstructingComponent))
{
if (!obstructingComponent.AirBlocked)
continue;
// We set the directions that are air-blocked so far,
// as you could have a full obstruction with only 4 directional air blockers.
directions |= obstructingComponent.AirBlockedDirection;
args.NoAir |= obstructingComponent.NoAirWhenFullyAirBlocked;
if (directions.IsFlagSet(args.Direction))
{
args.Result = true;
args.Handled = true;
return;
}
}
args.Result = false;
args.Handled = true;
}
private void GridIsTileSpace(EntityUid uid, GridAtmosphereComponent component, ref IsTileSpaceMethodEvent args)
{
if (args.Handled)
@@ -331,71 +282,58 @@ public sealed partial class AtmosphereSystem
args.Handled = true;
}
private void GridUpdateAdjacent(EntityUid uid, GridAtmosphereComponent component,
ref UpdateAdjacentMethodEvent args)
/// <summary>
/// Update array of adjacent tiles and the adjacency flags. Optionally activates all tiles with modified adjacencies.
/// </summary>
private void UpdateAdjacentTiles(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
TileAtmosphere tile,
bool activate = false)
{
if (args.Handled)
return;
var mapGridComp = args.MapGridComponent;
if (!Resolve(uid, ref mapGridComp))
return;
var xform = Transform(uid);
EntityUid? mapUid = _mapManager.MapExists(xform.MapID) ? _mapManager.GetMapEntityId(xform.MapID) : null;
if (!component.Tiles.TryGetValue(args.Tile, out var tile))
return;
var uid = ent.Owner;
var atmos = ent.Comp1;
var blockedDirs = tile.AirtightData.BlockedDirections;
if (activate)
AddActiveTile(atmos, tile);
tile.AdjacentBits = AtmosDirection.Invalid;
tile.BlockedAirflow = GetBlockedDirections(mapGridComp, tile.GridIndices);
for (var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
var adjacentIndices = tile.GridIndices.Offset(direction);
var otherIndices = tile.GridIndices.Offset(direction);
if (!component.Tiles.TryGetValue(otherIndices, out var adjacent))
TileAtmosphere? adjacent;
if (!tile.NoGridTile)
{
adjacent = new TileAtmosphere(tile.GridIndex, otherIndices,
GetTileMixture(null, mapUid, otherIndices),
space: IsTileSpace(null, mapUid, otherIndices, mapGridComp));
adjacent = GetOrNewTile(uid, atmos, adjacentIndices);
}
var oppositeDirection = direction.GetOpposite();
adjacent.BlockedAirflow = GetBlockedDirections(mapGridComp, adjacent.GridIndices);
// Pass in MapGridComponent so we don't have to resolve it for every adjacent direction.
var tileBlockedEv = new IsTileAirBlockedMethodEvent(uid, tile.GridIndices, direction, mapGridComp);
GridIsTileAirBlocked(uid, component, ref tileBlockedEv);
var adjacentBlockedEv =
new IsTileAirBlockedMethodEvent(uid, adjacent.GridIndices, oppositeDirection, mapGridComp);
GridIsTileAirBlocked(uid, component, ref adjacentBlockedEv);
if (!adjacent.BlockedAirflow.IsFlagSet(oppositeDirection) && !tileBlockedEv.Result)
{
adjacent.AdjacentBits |= oppositeDirection;
adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = tile;
}
else
{
adjacent.AdjacentBits &= ~oppositeDirection;
adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = null;
}
if (!tile.BlockedAirflow.IsFlagSet(direction) && !adjacentBlockedEv.Result)
{
tile.AdjacentBits |= direction;
tile.AdjacentTiles[direction.ToIndex()] = adjacent;
}
else
else if (!atmos.Tiles.TryGetValue(adjacentIndices, out adjacent))
{
tile.AdjacentBits &= ~direction;
tile.AdjacentTiles[direction.ToIndex()] = null;
tile.AdjacentTiles[i] = null;
continue;
}
var adjBlockDirs = adjacent.AirtightData.BlockedDirections;
if (activate)
AddActiveTile(atmos, adjacent);
var oppositeDirection = direction.GetOpposite();
if (adjBlockDirs.IsFlagSet(oppositeDirection) || blockedDirs.IsFlagSet(direction))
{
// Adjacency is blocked by some airtight entity.
tile.AdjacentBits &= ~direction;
adjacent.AdjacentBits &= ~oppositeDirection;
tile.AdjacentTiles[i] = null;
adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = null;
}
else
{
// No airtight entity in the way.
tile.AdjacentBits |= direction;
adjacent.AdjacentBits |= oppositeDirection;
tile.AdjacentTiles[i] = adjacent;
adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = tile;
}
DebugTools.Assert(!(tile.AdjacentBits.IsFlagSet(direction) ^
@@ -409,6 +347,16 @@ public sealed partial class AtmosphereSystem
tile.MonstermosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
}
private (GasMixture Air, bool IsSpace) GetDefaultMapAtmosphere(MapAtmosphereComponent? map)
{
if (map == null)
return (GasMixture.SpaceGas, true);
var air = map.Mixture;
DebugTools.Assert(air.Immutable);
return (air, map.Space);
}
private void GridHotspotExpose(EntityUid uid, GridAtmosphereComponent component, ref HotspotExposeMethodEvent args)
{
if (args.Handled)
@@ -451,54 +399,50 @@ public sealed partial class AtmosphereSystem
args.Handled = true;
}
private void GridFixTileVacuum(EntityUid uid, GridAtmosphereComponent component, ref FixTileVacuumMethodEvent args)
private void GridFixTileVacuum(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
TileAtmosphere tile,
float volume)
{
if (args.Handled)
return;
var adjEv = new GetAdjacentTileMixturesMethodEvent(uid, args.Tile, false, true);
GridGetAdjacentTileMixtures(uid, component, ref adjEv);
if (!adjEv.Handled || !component.Tiles.TryGetValue(args.Tile, out var tile))
return;
if (!TryComp<MapGridComponent>(uid, out var mapGridComp))
return;
var adjacent = adjEv.Result!.ToArray();
// Return early, let's not cause any funny NaNs or needless vacuums.
if (adjacent.Length == 0)
return;
tile.Air = new GasMixture
{
Volume = GetVolumeForTiles(mapGridComp, 1),
Temperature = Atmospherics.T20C
};
tile.MolesArchived = new float[Atmospherics.AdjustedNumberOfGases];
DebugTools.AssertNotNull(tile.Air);
DebugTools.Assert(tile.Air?.Immutable == false );
Array.Clear(tile.MolesArchived);
tile.ArchivedCycle = 0;
var ratio = 1f / adjacent.Length;
var count = 0;
foreach (var adj in tile.AdjacentTiles)
{
if (adj?.Air != null)
count++;
}
var ratio = 1f / count;
var totalTemperature = 0f;
foreach (var adj in adjacent)
foreach (var adj in tile.AdjacentTiles)
{
if (adj?.Air == null)
continue;
totalTemperature += adj.Temperature;
// TODO ATMOS. Why is this removing and then re-adding air to the neighbouring tiles?
// Is it some rounding issue to do with Atmospherics.GasMinMoles? because otherwise this is just unnecessary.
// if we get rid of this, then this could also just add moles and then multiply by ratio at the end, rather
// than having to iterate over adjacent tiles twice.
// Remove a bit of gas from the adjacent ratio...
var mix = adj.RemoveRatio(ratio);
var mix = adj.Air.RemoveRatio(ratio);
// And merge it to the new tile air.
Merge(tile.Air, mix);
// Return removed gas to its original mixture.
Merge(adj, mix);
Merge(adj.Air, mix);
}
// New temperature is the arithmetic mean of the sum of the adjacent temperatures...
tile.Air.Temperature = totalTemperature / adjacent.Length;
tile.Air.Temperature = totalTemperature / count;
}
private void GridAddPipeNet(EntityUid uid, GridAtmosphereComponent component, ref AddPipeNetMethodEvent args)
@@ -547,30 +491,21 @@ public sealed partial class AtmosphereSystem
/// <summary>
/// Repopulates all tiles on a grid atmosphere.
/// </summary>
/// <param name="mapGrid">The grid where to get all valid tiles from.</param>
/// <param name="gridAtmosphere">The grid atmosphere where the tiles will be repopulated.</param>
private void GridRepopulateTiles(Entity<MapGridComponent, GridAtmosphereComponent> grid)
public void InvalidateAllTiles(Entity<MapGridComponent?, GridAtmosphereComponent?> entity)
{
var (uid, mapGrid, gridAtmosphere) = grid;
var volume = GetVolumeForTiles(mapGrid, 1);
var (uid, grid, atmos) = entity;
if (!Resolve(uid, ref grid, ref atmos))
return;
foreach (var tile in mapGrid.GetAllTiles())
foreach (var indices in atmos.Tiles.Keys)
{
if (!gridAtmosphere.Tiles.ContainsKey(tile.GridIndices))
gridAtmosphere.Tiles[tile.GridIndices] = new TileAtmosphere(tile.GridUid, tile.GridIndices,
new GasMixture(volume) { Temperature = Atmospherics.T20C });
gridAtmosphere.InvalidatedCoords.Add(tile.GridIndices);
atmos.InvalidatedCoords.Add(indices);
}
TryComp(uid, out GasTileOverlayComponent? overlay);
// Gotta do this afterwards so we can properly update adjacent tiles.
foreach (var (position, _) in gridAtmosphere.Tiles.ToArray())
var enumerator = _map.GetAllTilesEnumerator(uid, grid);
while (enumerator.MoveNext(out var tile))
{
var ev = new UpdateAdjacentMethodEvent(uid, position);
GridUpdateAdjacent(uid, gridAtmosphere, ref ev);
InvalidateVisuals(uid, position, overlay);
atmos.InvalidatedCoords.Add(tile.Value.GridIndices);
}
}

View File

@@ -1,13 +1,11 @@
using Content.Server.Atmos.Components;
using Content.Shared.Atmos;
using Content.Shared.Audio;
using Content.Shared.Mobs.Components;
using Content.Shared.Physics;
using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Utility;

View File

@@ -1,12 +1,13 @@
using Content.Server.Atmos.Components;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Robust.Shared.Utility;
namespace Content.Server.Atmos.EntitySystems
{
public sealed partial class AtmosphereSystem
{
private void ProcessCell(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int fireCount, GasTileOverlayComponent? visuals)
private void ProcessCell(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int fireCount, GasTileOverlayComponent visuals)
{
// Can't process a tile without air
if (tile.Air == null)
@@ -116,15 +117,9 @@ 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.TemperatureArchived = tile.Air.Temperature;
}
else
{
tile.TemperatureArchived = tile.Temperature;
}
tile.TemperatureArchived = tile.Temperature;
tile.ArchivedCycle = fireCount;
}
@@ -166,6 +161,12 @@ namespace Content.Server.Atmos.EntitySystems
/// <param name="disposeExcitedGroup">Whether to dispose of the tile's <see cref="ExcitedGroup"/></param>
private void RemoveActiveTile(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, bool disposeExcitedGroup = true)
{
DebugTools.Assert(tile.Excited == gridAtmosphere.ActiveTiles.Contains(tile));
DebugTools.Assert(tile.Excited || tile.ExcitedGroup == null);
if (!tile.Excited)
return;
tile.Excited = false;
gridAtmosphere.ActiveTiles.Remove(tile);
@@ -186,7 +187,6 @@ namespace Content.Server.Atmos.EntitySystems
if (tile.Air == null)
return tile.HeatCapacity;
// Moles archived is not null if air is not null.
return GetHeatCapacityCalculation(tile.MolesArchived!, tile.Space);
}

View File

@@ -1,6 +1,8 @@
using Content.Server.Atmos.Components;
using Content.Shared.Atmos.Components;
using Robust.Shared.GameStates;
using Robust.Shared.Map.Components;
using Robust.Shared.Utility;
namespace Content.Server.Atmos.EntitySystems;
@@ -8,10 +10,25 @@ public partial class AtmosphereSystem
{
private void InitializeMap()
{
SubscribeLocalEvent<MapAtmosphereComponent, ComponentInit>(OnMapStartup);
SubscribeLocalEvent<MapAtmosphereComponent, ComponentRemove>(OnMapRemove);
SubscribeLocalEvent<MapAtmosphereComponent, IsTileSpaceMethodEvent>(MapIsTileSpace);
SubscribeLocalEvent<MapAtmosphereComponent, GetTileMixtureMethodEvent>(MapGetTileMixture);
SubscribeLocalEvent<MapAtmosphereComponent, GetTileMixturesMethodEvent>(MapGetTileMixtures);
SubscribeLocalEvent<MapAtmosphereComponent, ComponentGetState>(OnMapGetState);
SubscribeLocalEvent<GridAtmosphereComponent, EntParentChangedMessage>(OnGridParentChanged);
}
private void OnMapStartup(EntityUid uid, MapAtmosphereComponent component, ComponentInit args)
{
component.Mixture.MarkImmutable();
component.Overlay = _gasTileOverlaySystem.GetOverlayData(component.Mixture);
}
private void OnMapRemove(EntityUid uid, MapAtmosphereComponent component, ComponentRemove args)
{
if (!TerminatingOrDeleted(uid))
RefreshAllGridMapAtmospheres(uid);
}
private void MapIsTileSpace(EntityUid uid, MapAtmosphereComponent component, ref IsTileSpaceMethodEvent args)
@@ -28,54 +45,115 @@ public partial class AtmosphereSystem
if (args.Handled)
return;
// Clone the mixture, if possible.
args.Mixture = component.Mixture?.Clone();
args.Mixture = component.Mixture;
args.Handled = true;
}
private void MapGetTileMixtures(EntityUid uid, MapAtmosphereComponent component, ref GetTileMixturesMethodEvent args)
{
if (args.Handled || component.Mixture == null)
if (args.Handled)
return;
args.Handled = true;
args.Mixtures ??= new GasMixture?[args.Tiles.Count];
for (var i = 0; i < args.Tiles.Count; i++)
{
args.Mixtures[i] ??= component.Mixture.Clone();
args.Mixtures[i] ??= component.Mixture;
}
}
private void OnMapGetState(EntityUid uid, MapAtmosphereComponent component, ref ComponentGetState args)
{
args.State = new MapAtmosphereComponentState(_gasTileOverlaySystem.GetOverlayData(component.Mixture));
args.State = new MapAtmosphereComponentState(component.Overlay);
}
public void SetMapAtmosphere(EntityUid uid, bool space, GasMixture mixture, MapAtmosphereComponent? component = null)
public void SetMapAtmosphere(EntityUid uid, bool space, GasMixture mixture)
{
DebugTools.Assert(HasComp<MapComponent>(uid));
var component = EnsureComp<MapAtmosphereComponent>(uid);
SetMapGasMixture(uid, mixture, component, false);
SetMapSpace(uid, space, component, false);
RefreshAllGridMapAtmospheres(uid);
}
public void SetMapGasMixture(EntityUid uid, GasMixture mixture, MapAtmosphereComponent? component = null, bool updateTiles = true)
{
if (!Resolve(uid, ref component))
return;
if (!mixture.Immutable)
{
mixture = mixture.Clone();
mixture.MarkImmutable();
}
component.Mixture = mixture;
component.Overlay = _gasTileOverlaySystem.GetOverlayData(component.Mixture);
Dirty(uid, component);
if (updateTiles)
RefreshAllGridMapAtmospheres(uid);
}
public void SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null, bool updateTiles = true)
{
if (!Resolve(uid, ref component))
return;
if (component.Space == space)
return;
component.Space = space;
component.Mixture = mixture;
Dirty(uid, component);
if (updateTiles)
RefreshAllGridMapAtmospheres(uid);
}
public void SetMapGasMixture(EntityUid uid, GasMixture? mixture, MapAtmosphereComponent? component = null)
/// <summary>
/// Forces a refresh of all MapAtmosphere tiles on every grid on a map.
/// </summary>
public void RefreshAllGridMapAtmospheres(EntityUid map)
{
if (!Resolve(uid, ref component))
return;
component.Mixture = mixture;
Dirty(uid, component);
DebugTools.Assert(HasComp<MapComponent>(map));
var enumerator = AllEntityQuery<GridAtmosphereComponent, TransformComponent>();
while (enumerator.MoveNext(out var grid, out var atmos, out var xform))
{
if (xform.MapUid == map)
RefreshMapAtmosphereTiles((grid, atmos));
}
}
public void SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null)
/// <summary>
/// Forces a refresh of all MapAtmosphere tiles on a given grid.
/// </summary>
private void RefreshMapAtmosphereTiles(Entity<GridAtmosphereComponent?> grid)
{
if (!Resolve(uid, ref component))
if (!Resolve(grid.Owner, ref grid.Comp))
return;
component.Space = space;
Dirty(uid, component);
var atmos = grid.Comp;
foreach (var tile in atmos.MapTiles)
{
RemoveMapAtmos(atmos, tile);
atmos.InvalidatedCoords.Add(tile.GridIndices);
}
atmos.MapTiles.Clear();
}
/// <summary>
/// Handles updating map-atmospheres when grids move across maps.
/// </summary>
private void OnGridParentChanged(Entity<GridAtmosphereComponent> grid, ref EntParentChangedMessage args)
{
// Do nothing if detaching to nullspace
if (!args.Transform.ParentUid.IsValid())
return;
// Avoid doing work if moving from a space-map to another space-map.
if (args.OldParent == null
|| HasComp<MapAtmosphereComponent>(args.OldParent)
|| HasComp<MapAtmosphereComponent>(args.Transform.ParentUid))
{
RefreshMapAtmosphereTiles((grid, grid));
}
}
}

View File

@@ -5,7 +5,6 @@ using Content.Server.Doors.Systems;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Database;
using Content.Shared.Doors.Components;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Random;
@@ -27,7 +26,10 @@ namespace Content.Server.Atmos.EntitySystems
private readonly TileAtmosphere[] _depressurizeSpaceTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit];
private readonly TileAtmosphere[] _depressurizeProgressionOrder = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit * 2];
private void EqualizePressureInZone(Entity<MapGridComponent, GridAtmosphereComponent> ent, TileAtmosphere tile, int cycleNum, GasTileOverlayComponent? visuals)
private void EqualizePressureInZone(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
TileAtmosphere tile,
int cycleNum)
{
if (tile.Air == null || (tile.MonstermosInfo.LastCycle >= cycleNum))
return; // Already done.
@@ -56,7 +58,7 @@ namespace Content.Server.Atmos.EntitySystems
return;
}
var (_, mapGrid, gridAtmosphere) = ent;
var gridAtmosphere = ent.Comp1;
var queueCycle = ++gridAtmosphere.EqualizationQueueCycleControl;
var totalMoles = 0f;
_equalizeTiles[0] = tile;
@@ -91,7 +93,7 @@ namespace Content.Server.Atmos.EntitySystems
{
// Looks like someone opened an airlock to space!
ExplosivelyDepressurize(ent, tile, cycleNum, visuals);
ExplosivelyDepressurize(ent, tile, cycleNum);
return;
}
}
@@ -216,9 +218,13 @@ namespace Content.Server.Atmos.EntitySystems
for (var k = 0; k < Atmospherics.Directions; k++)
{
var direction = (AtmosDirection) (1 << k);
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
if (!otherTile.AdjacentBits.IsFlagSet(direction))
continue;
if (giver.MonstermosInfo.MoleDelta <= 0)
break; // We're done here now. Let's not do more work than needed.
var otherTile2 = otherTile.AdjacentTiles[k];
if (giver.MonstermosInfo.MoleDelta <= 0) break; // We're done here now. Let's not do more work than needed.
if (otherTile2 == null || otherTile2.MonstermosInfo.LastQueueCycle != queueCycle) continue;
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
if (otherTile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
@@ -332,7 +338,7 @@ namespace Content.Server.Atmos.EntitySystems
for (var i = 0; i < tileCount; i++)
{
var otherTile = _equalizeTiles[i]!;
FinalizeEq(gridAtmosphere, otherTile, visuals);
FinalizeEq(gridAtmosphere, otherTile, ent);
}
for (var i = 0; i < tileCount; i++)
@@ -341,12 +347,17 @@ namespace Content.Server.Atmos.EntitySystems
for (var j = 0; j < Atmospherics.Directions; j++)
{
var direction = (AtmosDirection) (1 << j);
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
if (!otherTile.AdjacentBits.IsFlagSet(direction))
continue;
var otherTile2 = otherTile.AdjacentTiles[j]!;
if (otherTile2.AdjacentBits == 0)
continue;
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
if (otherTile2.Air != null && CompareExchange(otherTile2.Air, tile.Air) == GasCompareResult.NoExchange) continue;
if (otherTile2.Air != null && CompareExchange(otherTile2.Air, tile.Air) == GasCompareResult.NoExchange)
continue;
AddActiveTile(gridAtmosphere, otherTile2);
break;
}
@@ -359,7 +370,10 @@ namespace Content.Server.Atmos.EntitySystems
Array.Clear(_equalizeQueue, 0, Atmospherics.MonstermosTileLimit);
}
private void ExplosivelyDepressurize(Entity<MapGridComponent, GridAtmosphereComponent> ent, TileAtmosphere tile, int cycleNum, GasTileOverlayComponent? visuals)
private void ExplosivelyDepressurize(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
TileAtmosphere tile,
int cycleNum)
{
// Check if explosive depressurization is enabled and if the tile is valid.
if (!MonstermosDepressurization || tile.Air == null)
@@ -368,7 +382,7 @@ namespace Content.Server.Atmos.EntitySystems
const int limit = Atmospherics.MonstermosHardTileLimit;
var totalMolesRemoved = 0f;
var (owner, mapGrid, gridAtmosphere) = ent;
var (owner, gridAtmosphere, visuals, mapGrid, _) = ent;
var queueCycle = ++gridAtmosphere.EqualizationQueueCycleControl;
var tileCount = 0;
@@ -388,20 +402,27 @@ namespace Content.Server.Atmos.EntitySystems
{
for (var j = 0; j < Atmospherics.Directions; j++)
{
var direction = (AtmosDirection) (1 << j);
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
var otherTile2 = otherTile.AdjacentTiles[j];
if (otherTile2?.Air == null) continue;
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle) continue;
if (otherTile2?.Air == null)
continue;
ConsiderFirelocks((owner, gridAtmosphere), otherTile, otherTile2, visuals, mapGrid);
if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle)
continue;
var direction = (AtmosDirection) (1 << j);
DebugTools.Assert(otherTile.AdjacentBits.IsFlagSet(direction));
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
ConsiderFirelocks(ent, otherTile, otherTile2);
// The firelocks might have closed on us.
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
if (!otherTile.AdjacentBits.IsFlagSet(direction))
continue;
otherTile2.MonstermosInfo = new MonstermosInfo { LastQueueCycle = queueCycle };
_depressurizeTiles[tileCount++] = otherTile2;
if (tileCount >= limit) break;
if (tileCount >= limit)
break;
}
}
else
@@ -437,13 +458,21 @@ namespace Content.Server.Atmos.EntitySystems
// Flood fill into this new direction
var direction = (AtmosDirection) (1 << j);
// Tiles in _depressurizeProgressionOrder cannot have null air.
if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Space) continue;
if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Space)
continue;
var tile2 = otherTile.AdjacentTiles[j];
if (tile2?.MonstermosInfo.LastQueueCycle != queueCycle) continue;
if (tile2?.MonstermosInfo.LastQueueCycle != queueCycle)
continue;
DebugTools.Assert(tile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
// If flood fill has already reached this tile, continue.
if (tile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
if(tile2.Space) continue;
if (tile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow)
continue;
if(tile2.Space)
continue;
tile2.MonstermosInfo.CurrentTransferDirection = direction.GetOpposite();
tile2.MonstermosInfo.CurrentTransferAmount = 0.0f;
tile2.PressureSpecificTarget = otherTile.PressureSpecificTarget;
@@ -535,7 +564,7 @@ namespace Content.Server.Atmos.EntitySystems
_physics.ApplyAngularImpulse(owner, Vector2Helpers.Cross(tile.GridIndices - gridPhysics.LocalCenter, direction) * totalMolesRemoved, body: gridPhysics);
}
if(tileCount > 10 && (totalMolesRemoved / tileCount) > 10)
if (tileCount > 10 && (totalMolesRemoved / tileCount) > 10)
_adminLog.Add(LogType.ExplosiveDepressurization, LogImpact.High,
$"Explosive depressurization removed {totalMolesRemoved} moles from {tileCount} tiles starting from position {tile.GridIndices:position} on grid ID {tile.GridIndex:grid}");
@@ -544,36 +573,33 @@ namespace Content.Server.Atmos.EntitySystems
Array.Clear(_depressurizeProgressionOrder, 0, Atmospherics.MonstermosHardTileLimit * 2);
}
private void ConsiderFirelocks(Entity<GridAtmosphereComponent> ent, TileAtmosphere tile, TileAtmosphere other, GasTileOverlayComponent? visuals, MapGridComponent mapGrid)
private void ConsiderFirelocks(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
TileAtmosphere tile,
TileAtmosphere other)
{
var reconsiderAdjacent = false;
foreach (var entity in mapGrid.GetAnchoredEntities(tile.GridIndices))
var mapGrid = ent.Comp3;
foreach (var entity in _map.GetAnchoredEntities(ent.Owner, mapGrid, tile.GridIndices))
{
if (!TryComp(entity, out FirelockComponent? firelock))
continue;
reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
if (_firelockQuery.TryGetComponent(entity, out var firelock))
reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
}
foreach (var entity in mapGrid.GetAnchoredEntities(other.GridIndices))
foreach (var entity in _map.GetAnchoredEntities(ent.Owner, mapGrid, other.GridIndices))
{
if (!TryComp(entity, out FirelockComponent? firelock))
continue;
reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
if (_firelockQuery.TryGetComponent(entity, out var firelock))
reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
}
if (!reconsiderAdjacent)
return;
var (owner, gridAtmosphere) = ent;
var tileEv = new UpdateAdjacentMethodEvent(owner, tile.GridIndices);
var otherEv = new UpdateAdjacentMethodEvent(owner, other.GridIndices);
GridUpdateAdjacent(owner, gridAtmosphere, ref tileEv);
GridUpdateAdjacent(owner, gridAtmosphere, ref otherEv);
InvalidateVisuals(tile.GridIndex, tile.GridIndices, visuals);
InvalidateVisuals(other.GridIndex, other.GridIndices, visuals);
UpdateAdjacentTiles(ent, tile);
UpdateAdjacentTiles(ent, other);
InvalidateVisuals(tile.GridIndex, tile.GridIndices, ent);
InvalidateVisuals(other.GridIndex, other.GridIndices, ent);
}
private void FinalizeEq(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, GasTileOverlayComponent? visuals)
@@ -642,7 +668,7 @@ namespace Content.Server.Atmos.EntitySystems
if (adj == null)
{
var nonNull = tile.AdjacentTiles.Where(x => x != null).Count();
Log.Error($"Encountered null adjacent tile in {nameof(AdjustEqMovement)}. Dir: {direction}, Tile: {tile.Tile}, non-null adj count: {nonNull}, Trace: {Environment.StackTrace}");
Log.Error($"Encountered null adjacent tile in {nameof(AdjustEqMovement)}. Dir: {direction}, Tile: ({tile.GridIndex}, {tile.GridIndices}), non-null adj count: {nonNull}, Trace: {Environment.StackTrace}");
return;
}

View File

@@ -1,6 +1,5 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.Piping.Components;
using Content.Server.NodeContainer.NodeGroups;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Maps;
@@ -8,6 +7,7 @@ using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.Atmos.EntitySystems
{
@@ -30,118 +30,63 @@ namespace Content.Server.Atmos.EntitySystems
private int _currentRunAtmosphereIndex;
private bool _simulationPaused;
private readonly List<Entity<GridAtmosphereComponent>> _currentRunAtmosphere = new();
private TileAtmosphere GetOrNewTile(EntityUid owner, GridAtmosphereComponent atmosphere, Vector2i index)
{
var tile = atmosphere.Tiles.GetOrNew(index, out var existing);
if (existing)
return tile;
atmosphere.InvalidatedCoords.Add(index);
tile.GridIndex = owner;
tile.GridIndices = index;
return tile;
}
private readonly List<Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>> _currentRunAtmosphere = new();
/// <summary>
/// Revalidates all invalid coordinates in a grid atmosphere.
/// I.e., process any tiles that have had their airtight blockers modified.
/// </summary>
/// <param name="ent">The grid atmosphere in question.</param>
/// <returns>Whether the process succeeded or got paused due to time constrains.</returns>
private bool ProcessRevalidate(Entity<GridAtmosphereComponent> ent, GasTileOverlayComponent? visuals)
private bool ProcessRevalidate(Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
{
var (owner, atmosphere) = ent;
if (!atmosphere.ProcessingPaused)
if (ent.Comp4.MapUid == null)
{
atmosphere.CurrentRunInvalidatedCoordinates.Clear();
atmosphere.CurrentRunInvalidatedCoordinates.EnsureCapacity(atmosphere.InvalidatedCoords.Count);
foreach (var tile in atmosphere.InvalidatedCoords)
{
atmosphere.CurrentRunInvalidatedCoordinates.Enqueue(tile);
}
atmosphere.InvalidatedCoords.Clear();
Log.Error($"Attempted to process atmosphere on a map-less grid? Grid: {ToPrettyString(ent)}");
return true;
}
if (!TryComp(owner, out MapGridComponent? mapGridComp))
return true;
var (uid, atmosphere, visuals, grid, xform) = ent;
var volume = GetVolumeForTiles(grid);
TryComp(xform.MapUid, out MapAtmosphereComponent? mapAtmos);
var mapUid = _mapManager.GetMapEntityIdOrThrow(Transform(owner).MapID);
if (!atmosphere.ProcessingPaused)
{
atmosphere.CurrentRunInvalidatedTiles.Clear();
atmosphere.CurrentRunInvalidatedTiles.EnsureCapacity(atmosphere.InvalidatedCoords.Count);
foreach (var indices in atmosphere.InvalidatedCoords)
{
var tile = GetOrNewTile(uid, atmosphere, indices);
atmosphere.CurrentRunInvalidatedTiles.Enqueue(tile);
var volume = GetVolumeForTiles(mapGridComp);
// Update tile.IsSpace and tile.MapAtmosphere, and tile.AirtightData.
UpdateTileData(ent, mapAtmos, tile);
}
atmosphere.InvalidatedCoords.Clear();
if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime)
return false;
}
var number = 0;
while (atmosphere.CurrentRunInvalidatedCoordinates.TryDequeue(out var indices))
while (atmosphere.CurrentRunInvalidatedTiles.TryDequeue(out var tile))
{
if (!atmosphere.Tiles.TryGetValue(indices, out var tile))
{
tile = new TileAtmosphere(owner, indices,
new GasMixture(volume) { Temperature = Atmospherics.T20C });
atmosphere.Tiles[indices] = tile;
}
var airBlockedEv = new IsTileAirBlockedMethodEvent(owner, indices, MapGridComponent:mapGridComp);
GridIsTileAirBlocked(owner, atmosphere, ref airBlockedEv);
var isAirBlocked = airBlockedEv.Result;
var oldBlocked = tile.BlockedAirflow;
var updateAdjacentEv = new UpdateAdjacentMethodEvent(owner, indices, mapGridComp);
GridUpdateAdjacent(owner, atmosphere, ref updateAdjacentEv);
// Blocked airflow changed, rebuild excited groups!
if (tile.Excited && tile.BlockedAirflow != oldBlocked)
{
RemoveActiveTile(atmosphere, tile);
}
// Call this instead of the grid method as the map has a say on whether the tile is space or not.
if ((!mapGridComp.TryGetTileRef(indices, out var t) || t.IsSpace(_tileDefinitionManager)) && !isAirBlocked)
{
tile.Air = GetTileMixture(null, mapUid, indices);
tile.MolesArchived = tile.Air != null ? new float[Atmospherics.AdjustedNumberOfGases] : null;
tile.Space = IsTileSpace(null, mapUid, indices, mapGridComp);
}
else if (isAirBlocked)
{
if (airBlockedEv.NoAir)
{
tile.Air = null;
tile.MolesArchived = null;
tile.ArchivedCycle = 0;
tile.LastShare = 0f;
tile.Hotspot = new Hotspot();
}
}
else
{
if (tile.Air == null && NeedsVacuumFixing(mapGridComp, indices))
{
var vacuumEv = new FixTileVacuumMethodEvent(owner, indices);
GridFixTileVacuum(owner, atmosphere, ref vacuumEv);
}
// Tile used to be space, but isn't anymore.
if (tile.Space || (tile.Air?.Immutable ?? false))
{
tile.Air = null;
tile.MolesArchived = null;
tile.ArchivedCycle = 0;
tile.LastShare = 0f;
tile.Space = false;
}
tile.Air ??= new GasMixture(volume){Temperature = Atmospherics.T20C};
tile.MolesArchived ??= new float[Atmospherics.AdjustedNumberOfGases];
}
// We activate the tile.
AddActiveTile(atmosphere, tile);
// TODO ATMOS: Query all the contents of this tile (like walls) and calculate the correct thermal conductivity and heat capacity
var tileDef = mapGridComp.TryGetTileRef(indices, out var tileRef)
? tileRef.GetContentTileDefinition(_tileDefinitionManager)
: null;
tile.ThermalConductivity = tileDef?.ThermalConductivity ?? 0.5f;
tile.HeatCapacity = tileDef?.HeatCapacity ?? float.PositiveInfinity;
InvalidateVisuals(owner, indices, visuals);
for (var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
var otherIndices = indices.Offset(direction);
if (atmosphere.Tiles.TryGetValue(otherIndices, out var otherTile))
AddActiveTile(atmosphere, otherTile);
}
DebugTools.Assert(atmosphere.Tiles.GetValueOrDefault(tile.GridIndices) == tile);
UpdateAdjacentTiles(ent, tile, activate: true);
UpdateTileAir(ent, tile, volume);
InvalidateVisuals(uid, tile.GridIndices, visuals);
if (number++ < InvalidCoordinatesLagCheckIterations)
continue;
@@ -149,12 +94,185 @@ namespace Content.Server.Atmos.EntitySystems
number = 0;
// Process the rest next time.
if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime)
{
return false;
}
TrimDisconnectedMapTiles(ent);
return true;
}
/// <summary>
/// This method queued a tile and all of its neighbours up for processing by <see cref="TrimDisconnectedMapTiles"/>.
/// </summary>
public void QueueTileTrim(GridAtmosphereComponent atmos, TileAtmosphere tile)
{
if (!tile.TrimQueued)
{
tile.TrimQueued = true;
atmos.PossiblyDisconnectedTiles.Add(tile);
}
for (var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
var indices = tile.GridIndices.Offset(direction);
if (atmos.Tiles.TryGetValue(indices, out var adj)
&& adj.NoGridTile
&& !adj.TrimQueued)
{
adj.TrimQueued = true;
atmos.PossiblyDisconnectedTiles.Add(adj);
}
}
}
/// <summary>
/// Tiles in a <see cref="GridAtmosphereComponent"/> are either grid-tiles, or they they should be are tiles
/// adjacent to grid-tiles that represent the map's atmosphere. This method trims any map-tiles that are no longer
/// adjacent to any grid-tiles.
/// </summary>
private void TrimDisconnectedMapTiles(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
{
var atmos = ent.Comp1;
foreach (var tile in atmos.PossiblyDisconnectedTiles)
{
tile.TrimQueued = false;
if (!tile.NoGridTile)
continue;
var connected = false;
for (var i = 0; i < Atmospherics.Directions; i++)
{
var indices = tile.GridIndices.Offset((AtmosDirection) (1 << i));
if (_map.TryGetTile(ent.Comp3, indices, out var gridTile) && !gridTile.IsEmpty)
{
connected = true;
break;
}
}
if (!connected)
{
RemoveActiveTile(atmos, tile);
atmos.Tiles.Remove(tile.GridIndices);
}
}
return true;
atmos.PossiblyDisconnectedTiles.Clear();
}
/// <summary>
/// Checks whether a tile has a corresponding grid-tile, or whether it is a "map" tile. Also checks whether the
/// tile should be considered "space"
/// </summary>
private void UpdateTileData(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
MapAtmosphereComponent? mapAtmos,
TileAtmosphere tile)
{
var idx = tile.GridIndices;
bool mapAtmosphere;
if (_map.TryGetTile(ent.Comp3, idx, out var gTile) && !gTile.IsEmpty)
{
var contentDef = (ContentTileDefinition) _tileDefinitionManager[gTile.TypeId];
mapAtmosphere = contentDef.MapAtmosphere;
tile.ThermalConductivity = contentDef.ThermalConductivity;
tile.HeatCapacity = contentDef.HeatCapacity;
tile.NoGridTile = false;
}
else
{
mapAtmosphere = true;
tile.ThermalConductivity = 0.5f;
tile.HeatCapacity = float.PositiveInfinity;
if (!tile.NoGridTile)
{
tile.NoGridTile = true;
// This tile just became a non-grid atmos tile.
// It, or one of its neighbours, might now be completely disconnected from the grid.
QueueTileTrim(ent.Comp1, tile);
}
}
UpdateAirtightData(ent.Owner, ent.Comp1, ent.Comp3, tile);
if (mapAtmosphere)
{
if (!tile.MapAtmosphere)
{
(tile.Air, tile.Space) = GetDefaultMapAtmosphere(mapAtmos);
tile.MapAtmosphere = true;
ent.Comp1.MapTiles.Add(tile);
}
DebugTools.AssertNotNull(tile.Air);
DebugTools.Assert(tile.Air?.Immutable ?? false);
return;
}
if (!tile.MapAtmosphere)
return;
// Tile used to be exposed to the map's atmosphere, but isn't anymore.
RemoveMapAtmos(ent.Comp1, tile);
}
private void RemoveMapAtmos(GridAtmosphereComponent atmos, TileAtmosphere tile)
{
DebugTools.Assert(tile.MapAtmosphere);
DebugTools.AssertNotNull(tile.Air);
DebugTools.Assert(tile.Air?.Immutable ?? false);
tile.MapAtmosphere = false;
atmos.MapTiles.Remove(tile);
tile.Air = null;
Array.Clear(tile.MolesArchived);
tile.ArchivedCycle = 0;
tile.LastShare = 0f;
tile.Space = false;
}
/// <summary>
/// Check whether a grid-tile should have an air mixture, and give it one if it doesn't already have one.
/// </summary>
private void UpdateTileAir(
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
TileAtmosphere tile,
float volume)
{
if (tile.MapAtmosphere)
{
DebugTools.AssertNotNull(tile.Air);
DebugTools.Assert(tile.Air?.Immutable ?? false);
return;
}
var data = tile.AirtightData;
var fullyBlocked = data.BlockedDirections == AtmosDirection.All;
if (fullyBlocked && data.NoAirWhenBlocked)
{
if (tile.Air == null)
return;
tile.Air = null;
Array.Clear(tile.MolesArchived);
tile.ArchivedCycle = 0;
tile.LastShare = 0f;
tile.Hotspot = new Hotspot();
return;
}
if (tile.Air != null)
return;
tile.Air = new GasMixture(volume){Temperature = Atmospherics.T20C};
if (data.FixVacuum)
GridFixTileVacuum(ent, tile, volume);
}
private void QueueRunTiles(
@@ -170,19 +288,16 @@ namespace Content.Server.Atmos.EntitySystems
}
}
private bool ProcessTileEqualize(Entity<GridAtmosphereComponent> ent, GasTileOverlayComponent? visuals)
private bool ProcessTileEqualize(Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
{
var (uid, atmosphere) = ent;
var atmosphere = ent.Comp1;
if (!atmosphere.ProcessingPaused)
QueueRunTiles(atmosphere.CurrentRunTiles, atmosphere.ActiveTiles);
if (!TryComp(uid, out MapGridComponent? mapGridComp))
throw new Exception("Tried to process a grid atmosphere on an entity that isn't a grid!");
var number = 0;
while (atmosphere.CurrentRunTiles.TryDequeue(out var tile))
{
EqualizePressureInZone((uid, mapGridComp, atmosphere), tile, atmosphere.UpdateCounter, visuals);
EqualizePressureInZone(ent, tile, atmosphere.UpdateCounter);
if (number++ < LagCheckIterations)
continue;
@@ -198,7 +313,7 @@ namespace Content.Server.Atmos.EntitySystems
return true;
}
private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere, GasTileOverlayComponent? visuals)
private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere, GasTileOverlayComponent visuals)
{
if(!atmosphere.ProcessingPaused)
QueueRunTiles(atmosphere.CurrentRunTiles, atmosphere.ActiveTiles);
@@ -240,11 +355,11 @@ namespace Content.Server.Atmos.EntitySystems
excitedGroup.BreakdownCooldown++;
excitedGroup.DismantleCooldown++;
if(excitedGroup.BreakdownCooldown > Atmospherics.ExcitedGroupBreakdownCycles)
if (excitedGroup.BreakdownCooldown > Atmospherics.ExcitedGroupBreakdownCycles)
ExcitedGroupSelfBreakdown(gridAtmosphere, excitedGroup);
else if(excitedGroup.DismantleCooldown > Atmospherics.ExcitedGroupsDismantleCycles)
ExcitedGroupDismantle(gridAtmosphere, 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;
@@ -435,10 +550,10 @@ namespace Content.Server.Atmos.EntitySystems
_currentRunAtmosphereIndex = 0;
_currentRunAtmosphere.Clear();
var query = EntityQueryEnumerator<GridAtmosphereComponent>();
while (query.MoveNext(out var uid, out var grid))
var query = EntityQueryEnumerator<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var atmos, out var overlay, out var grid, out var xform ))
{
_currentRunAtmosphere.Add((uid, grid));
_currentRunAtmosphere.Add((uid, atmos, overlay, grid, xform));
}
}
@@ -448,8 +563,7 @@ namespace Content.Server.Atmos.EntitySystems
for (; _currentRunAtmosphereIndex < _currentRunAtmosphere.Count; _currentRunAtmosphereIndex++)
{
var ent = _currentRunAtmosphere[_currentRunAtmosphereIndex];
var (owner, atmosphere) = ent;
TryComp(owner, out GasTileOverlayComponent? visuals);
var (owner, atmosphere, visuals, grid, xform) = ent;
if (!TryComp(owner, out TransformComponent? x)
|| x.MapUid == null
@@ -474,13 +588,14 @@ namespace Content.Server.Atmos.EntitySystems
switch (atmosphere.State)
{
case AtmosphereProcessingState.Revalidate:
if (!ProcessRevalidate(ent, visuals))
if (!ProcessRevalidate(ent))
{
atmosphere.ProcessingPaused = true;
return;
}
atmosphere.ProcessingPaused = false;
// Next state depends on whether monstermos equalization is enabled or not.
// Note: We do this here instead of on the tile equalization step to prevent ending it early.
// Therefore, a change to this CVar might only be applied after that step is over.
@@ -489,7 +604,7 @@ namespace Content.Server.Atmos.EntitySystems
: AtmosphereProcessingState.ActiveTiles;
continue;
case AtmosphereProcessingState.TileEqualize:
if (!ProcessTileEqualize(ent, visuals))
if (!ProcessTileEqualize(ent))
{
atmosphere.ProcessingPaused = true;
return;
@@ -499,7 +614,7 @@ namespace Content.Server.Atmos.EntitySystems
atmosphere.State = AtmosphereProcessingState.ActiveTiles;
continue;
case AtmosphereProcessingState.ActiveTiles:
if (!ProcessActiveTiles(atmosphere, visuals))
if (!ProcessActiveTiles(ent, ent))
{
atmosphere.ProcessingPaused = true;
return;
@@ -520,7 +635,7 @@ namespace Content.Server.Atmos.EntitySystems
atmosphere.State = AtmosphereProcessingState.HighPressureDelta;
continue;
case AtmosphereProcessingState.HighPressureDelta:
if (!ProcessHighPressureDelta(ent))
if (!ProcessHighPressureDelta((ent, ent)))
{
atmosphere.ProcessingPaused = true;
return;

View File

@@ -1,5 +1,6 @@
using Content.Server.Atmos.Components;
using Content.Shared.Atmos;
using Robust.Shared.Map.Components;
namespace Content.Server.Atmos.EntitySystems
{
@@ -12,7 +13,8 @@ namespace Content.Server.Atmos.EntitySystems
for(var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
if (!directions.IsFlagSet(direction)) continue;
if (!directions.IsFlagSet(direction))
continue;
var adjacent = tile.AdjacentTiles[direction.ToIndex()];
@@ -92,7 +94,9 @@ namespace Content.Server.Atmos.EntitySystems
{
if (tile.Air == null)
{
if (other.Tile != null)
// TODO ATMOS: why does this need to check if a tile exists if it doesn't use the tile?
if (TryComp<MapGridComponent>(other.GridIndex, out var grid)
&& _mapSystem.TryGetTileRef(other.GridIndex, grid, other.GridIndices, out var _))
{
TemperatureShareOpenToSolid(other, tile);
}

View File

@@ -41,20 +41,6 @@ public partial class AtmosphereSystem
_gasTileOverlaySystem.Invalidate(gridUid, tile, comp);
}
public bool NeedsVacuumFixing(MapGridComponent mapGrid, Vector2i indices)
{
var value = false;
var enumerator = GetObstructingComponentsEnumerator(mapGrid, indices);
while (enumerator.MoveNext(out var airtight))
{
value |= airtight.FixVacuum;
}
return value;
}
/// <summary>
/// Gets the volume in liters for a number of tiles, on a specific grid.
/// </summary>
@@ -66,34 +52,44 @@ public partial class AtmosphereSystem
return Atmospherics.CellVolume * mapGrid.TileSize * tiles;
}
/// <summary>
/// Gets all obstructing <see cref="AirtightComponent"/> instances in a specific tile.
/// </summary>
/// <param name="mapGrid">The grid where to get the tile.</param>
/// <param name="tile">The indices of the tile.</param>
/// <returns>The enumerator for the airtight components.</returns>
public AtmosObstructionEnumerator GetObstructingComponentsEnumerator(MapGridComponent mapGrid, Vector2i tile)
{
var ancEnumerator = mapGrid.GetAnchoredEntitiesEnumerator(tile);
var airQuery = GetEntityQuery<AirtightComponent>();
public readonly record struct AirtightData(AtmosDirection BlockedDirections, bool NoAirWhenBlocked,
bool FixVacuum);
var enumerator = new AtmosObstructionEnumerator(ancEnumerator, airQuery);
return enumerator;
private void UpdateAirtightData(EntityUid uid, GridAtmosphereComponent atmos, MapGridComponent grid, TileAtmosphere tile)
{
var oldBlocked = tile.AirtightData.BlockedDirections;
tile.AirtightData = tile.NoGridTile
? default
: GetAirtightData(uid, grid, tile.GridIndices);
if (tile.AirtightData.BlockedDirections != oldBlocked && tile.ExcitedGroup != null)
ExcitedGroupDispose(atmos, tile.ExcitedGroup);
}
private AtmosDirection GetBlockedDirections(MapGridComponent mapGrid, Vector2i indices)
private AirtightData GetAirtightData(EntityUid uid, MapGridComponent grid, Vector2i tile)
{
var value = AtmosDirection.Invalid;
var blockedDirs = AtmosDirection.Invalid;
var noAirWhenBlocked = false;
var fixVacuum = false;
var enumerator = GetObstructingComponentsEnumerator(mapGrid, indices);
while (enumerator.MoveNext(out var airtight))
foreach (var ent in _map.GetAnchoredEntities(uid, grid, tile))
{
if(airtight.AirBlocked)
value |= airtight.AirBlockedDirection;
if (!_airtightQuery.TryGetComponent(ent, out var airtight))
continue;
if(!airtight.AirBlocked)
continue;
blockedDirs |= airtight.AirBlockedDirection;
noAirWhenBlocked |= airtight.NoAirWhenFullyAirBlocked;
fixVacuum |= airtight.FixVacuum;
if (blockedDirs == AtmosDirection.All && noAirWhenBlocked && fixVacuum)
break;
}
return value;
return new AirtightData(blockedDirs, noAirWhenBlocked, fixVacuum);
}
/// <summary>

View File

@@ -4,6 +4,7 @@ using Content.Server.Body.Systems;
using Content.Server.Fluids.EntitySystems;
using Content.Server.NodeContainer.EntitySystems;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Doors.Components;
using Content.Shared.Maps;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
@@ -40,6 +41,9 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
private const float ExposedUpdateDelay = 1f;
private float _exposedTimer = 0f;
private EntityQuery<GridAtmosphereComponent> _atmosQuery;
private EntityQuery<AirtightComponent> _airtightQuery;
private EntityQuery<FirelockComponent> _firelockQuery;
private HashSet<EntityUid> _entSet = new();
public override void Initialize()
@@ -55,6 +59,9 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
InitializeGridAtmosphere();
InitializeMap();
_atmosQuery = GetEntityQuery<GridAtmosphereComponent>();
_airtightQuery = GetEntityQuery<AirtightComponent>();
_firelockQuery = GetEntityQuery<FirelockComponent>();
SubscribeLocalEvent<TileChangedEvent>(OnTileChanged);

View File

@@ -27,8 +27,12 @@ public sealed class AutomaticAtmosSystem : EntitySystem
// Also, these calls are surprisingly slow.
// TODO: Make tiledefmanager cache the IsSpace property, and turn this lookup-through-two-interfaces into
// TODO: a simple array lookup, as tile IDs are likely contiguous, and there's at most 2^16 possibilities anyway.
if (!((ev.OldTile.IsSpace(_tileDefinitionManager) && !ev.NewTile.IsSpace(_tileDefinitionManager)) ||
(!ev.OldTile.IsSpace(_tileDefinitionManager) && ev.NewTile.IsSpace(_tileDefinitionManager))) ||
var oldSpace = ev.OldTile.IsSpace(_tileDefinitionManager);
var newSpace = ev.NewTile.IsSpace(_tileDefinitionManager);
if (!(oldSpace && !newSpace ||
!oldSpace && newSpace) ||
_atmosphereSystem.HasAtmosphere(ev.Entity))
return;

View File

@@ -260,13 +260,13 @@ namespace Content.Server.Atmos.EntitySystems
{
var gas = _atmo.GetGas(i);
if (mixture?.Moles[i] <= UIMinMoles)
if (mixture?[i] <= UIMinMoles)
continue;
if (mixture != null)
{
var gasName = Loc.GetString(gas.Name);
gases.Add(new GasEntry(gasName, mixture.Moles[i], gas.Color));
gases.Add(new GasEntry(gasName, mixture[i], gas.Color));
}
}

View File

@@ -172,7 +172,7 @@ namespace Content.Server.Atmos.EntitySystems
{
var id = VisibleGasId[i];
var gas = _atmosphereSystem.GetGas(id);
var moles = mixture?.Moles[id] ?? 0f;
var moles = mixture?[id] ?? 0f;
ref var opacity = ref data.Opacity[i];
if (moles < gas.GasMolesVisible)
@@ -217,13 +217,13 @@ namespace Content.Server.Atmos.EntitySystems
oldData = new GasOverlayData(tile.Hotspot.State, oldData.Opacity);
}
if (tile.Air != null)
if (tile is {Air: not null, NoGridTile: false})
{
for (var i = 0; i < VisibleGasId.Length; i++)
{
var id = VisibleGasId[i];
var gas = _atmosphereSystem.GetGas(id);
var moles = tile.Air.Moles[id];
var moles = tile.Air[id];
ref var oldOpacity = ref oldData.Opacity[i];
if (moles < gas.GasMolesVisible)

View File

@@ -3,6 +3,7 @@ using System.Linq;
using System.Runtime.CompilerServices;
using Content.Server.Atmos.Reactions;
using Content.Shared.Atmos;
using Content.Shared.Atmos.EntitySystems;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
@@ -17,11 +18,13 @@ namespace Content.Server.Atmos
{
public static GasMixture SpaceGas => new() {Volume = Atmospherics.CellVolume, Temperature = Atmospherics.TCMB, Immutable = true};
// This must always have a length that is a multiple of 4 for SIMD acceleration.
[DataField("moles")]
[ViewVariables(VVAccess.ReadWrite)]
// No access, to ensure immutable mixtures are never accidentally mutated.
[Access(typeof(SharedAtmosphereSystem), typeof(SharedAtmosDebugOverlaySystem), Other = AccessPermissions.None)]
[DataField]
public float[] Moles = new float[Atmospherics.AdjustedNumberOfGases];
public float this[int gas] => Moles[gas];
[DataField("temperature")]
[ViewVariables(VVAccess.ReadWrite)]
private float _temperature = Atmospherics.TCMB;
@@ -80,6 +83,19 @@ namespace Content.Server.Atmos
Volume = volume;
}
public GasMixture(float[] moles, float temp, float volume = Atmospherics.CellVolume)
{
if (moles.Length != Atmospherics.AdjustedNumberOfGases)
throw new InvalidOperationException($"Invalid mole array length");
if (volume < 0)
volume = 0;
_temperature = temp;
Moles = moles;
Volume = volume;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void MarkImmutable()
{
@@ -117,15 +133,16 @@ namespace Content.Server.Atmos
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AdjustMoles(int gasId, float quantity)
{
if (!Immutable)
{
if (!float.IsFinite(quantity))
throw new ArgumentException($"Invalid quantity \"{quantity}\" specified!", nameof(quantity));
if (Immutable)
return;
// Clamping is needed because x - x can be negative with floating point numbers. If we don't
// clamp here, the caller always has to call GetMoles(), clamp, then SetMoles().
Moles[gasId] = MathF.Max(Moles[gasId] + quantity, 0);
}
if (!float.IsFinite(quantity))
throw new ArgumentException($"Invalid quantity \"{quantity}\" specified!", nameof(quantity));
// Clamping is needed because x - x can be negative with floating point numbers. If we don't
// clamp here, the caller always has to call GetMoles(), clamp, then SetMoles().
ref var moles = ref Moles[gasId];
moles = MathF.Max(moles + quantity, 0);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -163,7 +180,8 @@ namespace Content.Server.Atmos
{
var moles = Moles[i];
var otherMoles = removed.Moles[i];
if (moles < Atmospherics.GasMinMoles || float.IsNaN(moles))
if ((moles < Atmospherics.GasMinMoles || float.IsNaN(moles)) && !Immutable)
Moles[i] = 0;
if (otherMoles < Atmospherics.GasMinMoles || float.IsNaN(otherMoles))
@@ -202,6 +220,9 @@ namespace Content.Server.Atmos
void ISerializationHooks.AfterDeserialization()
{
// ISerializationHooks is obsolete.
// TODO add fixed-length-array serializer
// The arrays MUST have a specific length.
Array.Resize(ref Moles, Atmospherics.AdjustedNumberOfGases);
}
@@ -229,8 +250,12 @@ namespace Content.Server.Atmos
public bool Equals(GasMixture? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
if (ReferenceEquals(this, other))
return true;
if (ReferenceEquals(null, other))
return false;
return Moles.SequenceEqual(other.Moles)
&& _temperature.Equals(other._temperature)
&& ReactionResults.SequenceEqual(other.ReactionResults)
@@ -258,11 +283,13 @@ namespace Content.Server.Atmos
public GasMixture Clone()
{
if (Immutable)
return this;
var newMixture = new GasMixture()
{
Moles = (float[])Moles.Clone(),
_temperature = _temperature,
Immutable = Immutable,
Volume = Volume,
};
return newMixture;

View File

@@ -136,7 +136,7 @@ public sealed class GasCanisterSystem : EntitySystem
for (int i = 0; i < containedGasArray.Length; i++)
{
containedGasDict.Add((Gas)i, canister.Air.Moles[i]);
containedGasDict.Add((Gas)i, canister.Air[i]);
}
_adminLogger.Add(LogType.CanisterValve, impact, $"{ToPrettyString(args.Session.AttachedEntity.GetValueOrDefault()):player} set the valve on {ToPrettyString(uid):canister} to {args.Valve:valveState} while it contained [{string.Join(", ", containedGasDict)}]");

View File

@@ -45,7 +45,7 @@ public sealed class GasCondenserSystem : EntitySystem
var removed = inlet.Air.Remove(molesToConvert);
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
{
var moles = removed.Moles[i];
var moles = removed[i];
if (moles <= 0)
continue;

View File

@@ -183,13 +183,7 @@ public sealed partial class TileAtmosCollectionSerializer : ITypeSerializer<Dict
target.Clear();
foreach (var (key, val) in source)
{
target.Add(key,
new TileAtmosphere(
val.GridIndex,
val.GridIndices,
val.Air?.Clone(),
val.Air?.Immutable ?? false,
val.Space));
target.Add(key, new TileAtmosphere(val));
}
}
}

View File

@@ -1,3 +1,4 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos;
using Content.Shared.Maps;
@@ -51,6 +52,10 @@ namespace Content.Server.Atmos
[ViewVariables]
public readonly TileAtmosphere?[] AdjacentTiles = new TileAtmosphere[Atmospherics.Directions];
/// <summary>
/// Neighbouring tiles to which air can flow. This is a combination of this tile's unblocked direction, and the
/// unblocked directions on adjacent tiles.
/// </summary>
[ViewVariables]
public AtmosDirection AdjacentBits = AtmosDirection.Invalid;
@@ -72,10 +77,7 @@ namespace Content.Server.Atmos
public EntityUid GridIndex { get; set; }
[ViewVariables]
public TileRef? Tile => GridIndices.GetTileRef(GridIndex);
[ViewVariables]
public Vector2i GridIndices { get; }
public Vector2i GridIndices;
[ViewVariables]
public ExcitedGroup? ExcitedGroup { get; set; }
@@ -92,7 +94,7 @@ namespace Content.Server.Atmos
public float LastShare;
[ViewVariables]
public float[]? MolesArchived;
public readonly float[] MolesArchived = new float[Atmospherics.AdjustedNumberOfGases];
GasMixture IGasMixtureHolder.Air
{
@@ -103,8 +105,31 @@ namespace Content.Server.Atmos
[ViewVariables]
public float MaxFireTemperatureSustained { get; set; }
/// <summary>
/// If true, then this tile is directly exposed to the map's atmosphere, either because the grid has no tile at
/// this position, or because the tile type is not airtight.
/// </summary>
[ViewVariables]
public AtmosDirection BlockedAirflow { get; set; } = AtmosDirection.Invalid;
public bool MapAtmosphere;
/// <summary>
/// If true, this tile does not actually exist on the grid, it only exists to represent the map's atmosphere for
/// adjacent grid tiles.
/// </summary>
[ViewVariables]
public bool NoGridTile;
/// <summary>
/// If true, this tile is queued for processing in <see cref="GridAtmosphereComponent.PossiblyDisconnectedTiles"/>
/// </summary>
[ViewVariables]
public bool TrimQueued;
/// <summary>
/// Cached information about airtight entities on this tile. This gets updated anytime a tile gets invalidated
/// (i.e., gets added to <see cref="GridAtmosphereComponent.InvalidatedCoords"/>).
/// </summary>
public AtmosphereSystem.AirtightData AirtightData;
public TileAtmosphere(EntityUid gridIndex, Vector2i gridIndices, GasMixture? mixture = null, bool immutable = false, bool space = false)
{
@@ -112,10 +137,24 @@ namespace Content.Server.Atmos
GridIndices = gridIndices;
Air = mixture;
Space = space;
MolesArchived = Air != null ? new float[Atmospherics.AdjustedNumberOfGases] : null;
if(immutable)
Air?.MarkImmutable();
}
public TileAtmosphere(TileAtmosphere other)
{
GridIndex = other.GridIndex;
GridIndices = other.GridIndices;
Space = other.Space;
NoGridTile = other.NoGridTile;
MapAtmosphere = other.MapAtmosphere;
Air = other.Air?.Clone();
Array.Copy(other.MolesArchived, MolesArchived, MolesArchived.Length);
}
public TileAtmosphere()
{
}
}
}