Gas tile overlay state handling changes (#12691)

This commit is contained in:
Leon Friedrich
2022-12-19 08:25:27 +13:00
committed by GitHub
parent 195bf86fe2
commit 2759ef009e
11 changed files with 268 additions and 108 deletions

View File

@@ -1,11 +1,11 @@
using Content.Client.Atmos.Overlays; using Content.Client.Atmos.Overlays;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems; using Content.Shared.Atmos.EntitySystems;
using Content.Shared.GameTicking;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.ResourceManagement; using Robust.Client.ResourceManagement;
using Robust.Shared.Utility; using Robust.Shared.GameStates;
namespace Content.Client.Atmos.EntitySystems namespace Content.Client.Atmos.EntitySystems
{ {
@@ -22,49 +22,71 @@ namespace Content.Client.Atmos.EntitySystems
{ {
base.Initialize(); base.Initialize();
SubscribeNetworkEvent<GasOverlayUpdateEvent>(HandleGasOverlayUpdate); SubscribeNetworkEvent<GasOverlayUpdateEvent>(HandleGasOverlayUpdate);
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoved); SubscribeLocalEvent<GasTileOverlayComponent, ComponentHandleState>(OnHandleState);
_overlay = new GasTileOverlay(this, EntityManager, _resourceCache, ProtoMan, _spriteSys); _overlay = new GasTileOverlay(this, EntityManager, _resourceCache, ProtoMan, _spriteSys);
_overlayMan.AddOverlay(_overlay); _overlayMan.AddOverlay(_overlay);
} }
public override void Reset(RoundRestartCleanupEvent ev)
{
_overlay.TileData.Clear();
}
public override void Shutdown() public override void Shutdown()
{ {
base.Shutdown(); base.Shutdown();
_overlayMan.RemoveOverlay(_overlay); _overlayMan.RemoveOverlay(_overlay);
} }
private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args)
{
if (args.Current is not GasTileOverlayState state)
return;
// is this a delta or full state?
if (!state.FullState)
{
foreach (var index in comp.Chunks.Keys)
{
if (!state.AllChunks!.Contains(index))
comp.Chunks.Remove(index);
}
}
else
{
foreach (var index in comp.Chunks.Keys)
{
if (!state.Chunks.ContainsKey(index))
comp.Chunks.Remove(index);
}
}
foreach (var (index, data) in state.Chunks)
{
comp.Chunks[index] = data;
}
}
private void HandleGasOverlayUpdate(GasOverlayUpdateEvent ev) private void HandleGasOverlayUpdate(GasOverlayUpdateEvent ev)
{ {
foreach (var (grid, removedIndicies) in ev.RemovedChunks) foreach (var (grid, removedIndicies) in ev.RemovedChunks)
{ {
if (!_overlay.TileData.TryGetValue(grid, out var chunks)) if (!TryComp(grid, out GasTileOverlayComponent? comp))
continue; continue;
foreach (var index in removedIndicies) foreach (var index in removedIndicies)
{ {
chunks.Remove(index); comp.Chunks.Remove(index);
} }
} }
foreach (var (grid, gridData) in ev.UpdatedChunks) foreach (var (grid, gridData) in ev.UpdatedChunks)
{ {
var chunks = _overlay.TileData.GetOrNew(grid); if (!TryComp(grid, out GasTileOverlayComponent? comp))
continue;
foreach (var chunkData in gridData) foreach (var chunkData in gridData)
{ {
chunks[chunkData.Index] = chunkData; comp.Chunks[chunkData.Index] = chunkData;
} }
} }
} }
private void OnGridRemoved(GridRemovalEvent ev)
{
_overlay.TileData.Remove(ev.EntityUid);
}
} }
} }

View File

@@ -1,5 +1,6 @@
using Content.Client.Atmos.EntitySystems; using Content.Client.Atmos.EntitySystems;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.Prototypes; using Content.Shared.Atmos.Prototypes;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
@@ -20,8 +21,6 @@ namespace Content.Client.Atmos.Overlays
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities; public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities;
private readonly ShaderInstance _shader; private readonly ShaderInstance _shader;
public readonly Dictionary<EntityUid, Dictionary<Vector2i, GasOverlayChunk>> TileData = new();
// Gas overlays // Gas overlays
private readonly float[] _timer; private readonly float[] _timer;
private readonly float[][] _frameDelays; private readonly float[][] _frameDelays;
@@ -139,12 +138,15 @@ namespace Content.Client.Atmos.Overlays
{ {
var drawHandle = args.WorldHandle; var drawHandle = args.WorldHandle;
var xformQuery = _entManager.GetEntityQuery<TransformComponent>(); var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
var overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
foreach (var mapGrid in _mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds)) foreach (var mapGrid in _mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds))
{ {
if (!TileData.TryGetValue(mapGrid.Owner, out var gridData) || if (!overlayQuery.TryGetComponent(mapGrid.Owner, out var comp) ||
!xformQuery.TryGetComponent(mapGrid.Owner, out var gridXform)) !xformQuery.TryGetComponent(mapGrid.Owner, out var gridXform))
{
continue; continue;
}
var (_, _, worldMatrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv(); var (_, _, worldMatrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv();
drawHandle.SetTransform(worldMatrix); drawHandle.SetTransform(worldMatrix);
@@ -160,7 +162,7 @@ namespace Content.Client.Atmos.Overlays
// by chunk, even though its currently slower. // by chunk, even though its currently slower.
drawHandle.UseShader(null); drawHandle.UseShader(null);
foreach (var chunk in gridData.Values) foreach (var chunk in comp.Chunks.Values)
{ {
var enumerator = new GasChunkEnumerator(chunk); var enumerator = new GasChunkEnumerator(chunk);
@@ -184,7 +186,7 @@ namespace Content.Client.Atmos.Overlays
// And again for fire, with the unshaded shader // And again for fire, with the unshaded shader
drawHandle.UseShader(_shader); drawHandle.UseShader(_shader);
foreach (var chunk in gridData.Values) foreach (var chunk in comp.Chunks.Values)
{ {
var enumerator = new GasChunkEnumerator(chunk); var enumerator = new GasChunkEnumerator(chunk);

View File

@@ -2,6 +2,7 @@ using System.Linq;
using Content.Server.Atmos.Components; using Content.Server.Atmos.Components;
using Content.Server.Atmos.Reactions; using Content.Server.Atmos.Reactions;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -48,6 +49,8 @@ public sealed partial class AtmosphereSystem
if (!TryComp(uid, out MapGridComponent? mapGrid)) if (!TryComp(uid, out MapGridComponent? mapGrid))
return; return;
EnsureComp<GasTileOverlayComponent>(uid);
foreach (var (indices, tile) in gridAtmosphere.Tiles) foreach (var (indices, tile) in gridAtmosphere.Tiles)
{ {
gridAtmosphere.InvalidatedCoords.Add(indices); gridAtmosphere.InvalidatedCoords.Add(indices);
@@ -550,12 +553,14 @@ public sealed partial class AtmosphereSystem
var uid = gridAtmosphere.Owner; var uid = gridAtmosphere.Owner;
TryComp(gridAtmosphere.Owner, out GasTileOverlayComponent? overlay);
// Gotta do this afterwards so we can properly update adjacent tiles. // Gotta do this afterwards so we can properly update adjacent tiles.
foreach (var (position, _) in gridAtmosphere.Tiles.ToArray()) foreach (var (position, _) in gridAtmosphere.Tiles.ToArray())
{ {
var ev = new UpdateAdjacentMethodEvent(uid, position); var ev = new UpdateAdjacentMethodEvent(uid, position);
GridUpdateAdjacent(uid, gridAtmosphere, ref ev); GridUpdateAdjacent(uid, gridAtmosphere, ref ev);
InvalidateVisuals(mapGrid.Owner, position); InvalidateVisuals(mapGrid.Owner, position, overlay);
} }
} }
} }

View File

@@ -1,11 +1,12 @@
using Content.Server.Atmos.Components; using Content.Server.Atmos.Components;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
namespace Content.Server.Atmos.EntitySystems namespace Content.Server.Atmos.EntitySystems
{ {
public sealed partial class AtmosphereSystem public sealed partial class AtmosphereSystem
{ {
private void ProcessCell(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int fireCount) private void ProcessCell(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int fireCount, GasTileOverlayComponent? visuals)
{ {
// Can't process a tile without air // Can't process a tile without air
if (tile.Air == null) if (tile.Air == null)
@@ -100,7 +101,7 @@ namespace Content.Server.Atmos.EntitySystems
if(tile.Air != null) if(tile.Air != null)
React(tile.Air, tile); React(tile.Air, tile);
InvalidateVisuals(tile.GridIndex, tile.GridIndices); InvalidateVisuals(tile.GridIndex, tile.GridIndices, visuals);
var remove = true; var remove = true;

View File

@@ -2,13 +2,14 @@ using Content.Server.Atmos.Components;
using Content.Server.Doors.Components; using Content.Server.Doors.Components;
using Content.Server.Doors.Systems; using Content.Server.Doors.Systems;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Database; using Content.Shared.Database;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using System.Linq; using System.Linq;
using Robust.Shared.Map.Components;
namespace Content.Server.Atmos.EntitySystems namespace Content.Server.Atmos.EntitySystems
{ {
@@ -26,7 +27,7 @@ namespace Content.Server.Atmos.EntitySystems
private readonly TileAtmosphere[] _depressurizeSpaceTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit]; private readonly TileAtmosphere[] _depressurizeSpaceTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit];
private readonly TileAtmosphere[] _depressurizeProgressionOrder = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit * 2]; private readonly TileAtmosphere[] _depressurizeProgressionOrder = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit * 2];
private void EqualizePressureInZone(MapGridComponent mapGrid, GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int cycleNum) private void EqualizePressureInZone(MapGridComponent mapGrid, GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int cycleNum, GasTileOverlayComponent? visuals)
{ {
if (tile.Air == null || (tile.MonstermosInfo.LastCycle >= cycleNum)) if (tile.Air == null || (tile.MonstermosInfo.LastCycle >= cycleNum))
return; // Already done. return; // Already done.
@@ -89,7 +90,7 @@ namespace Content.Server.Atmos.EntitySystems
{ {
// Looks like someone opened an airlock to space! // Looks like someone opened an airlock to space!
ExplosivelyDepressurize(mapGrid, gridAtmosphere, tile, cycleNum); ExplosivelyDepressurize(mapGrid, gridAtmosphere, tile, cycleNum, visuals);
return; return;
} }
} }
@@ -330,7 +331,7 @@ namespace Content.Server.Atmos.EntitySystems
for (var i = 0; i < tileCount; i++) for (var i = 0; i < tileCount; i++)
{ {
var otherTile = _equalizeTiles[i]!; var otherTile = _equalizeTiles[i]!;
FinalizeEq(gridAtmosphere, otherTile); FinalizeEq(gridAtmosphere, otherTile, visuals);
} }
for (var i = 0; i < tileCount; i++) for (var i = 0; i < tileCount; i++)
@@ -355,7 +356,7 @@ namespace Content.Server.Atmos.EntitySystems
Array.Clear(_equalizeQueue, 0, Atmospherics.MonstermosTileLimit); Array.Clear(_equalizeQueue, 0, Atmospherics.MonstermosTileLimit);
} }
private void ExplosivelyDepressurize(MapGridComponent mapGrid, GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int cycleNum) private void ExplosivelyDepressurize(MapGridComponent mapGrid, GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int cycleNum, GasTileOverlayComponent? visuals)
{ {
// Check if explosive depressurization is enabled and if the tile is valid. // Check if explosive depressurization is enabled and if the tile is valid.
if (!MonstermosDepressurization || tile.Air == null) if (!MonstermosDepressurization || tile.Air == null)
@@ -390,7 +391,7 @@ namespace Content.Server.Atmos.EntitySystems
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite())); DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle) continue; if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle) continue;
ConsiderFirelocks(gridAtmosphere, otherTile, otherTile2); ConsiderFirelocks(gridAtmosphere, otherTile, otherTile2, visuals, mapGrid);
// The firelocks might have closed on us. // The firelocks might have closed on us.
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue; if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
@@ -472,7 +473,7 @@ namespace Content.Server.Atmos.EntitySystems
// therefore there is no more gas in the tile, therefore the tile should be as cold as space! // therefore there is no more gas in the tile, therefore the tile should be as cold as space!
otherTile.Air.Temperature = Atmospherics.TCMB; otherTile.Air.Temperature = Atmospherics.TCMB;
InvalidateVisuals(otherTile.GridIndex, otherTile.GridIndices); InvalidateVisuals(otherTile.GridIndex, otherTile.GridIndices, visuals);
HandleDecompressionFloorRip(mapGrid, otherTile, sum); HandleDecompressionFloorRip(mapGrid, otherTile, sum);
} }
@@ -496,11 +497,8 @@ namespace Content.Server.Atmos.EntitySystems
Array.Clear(_depressurizeProgressionOrder, 0, Atmospherics.MonstermosHardTileLimit * 2); Array.Clear(_depressurizeProgressionOrder, 0, Atmospherics.MonstermosHardTileLimit * 2);
} }
private void ConsiderFirelocks(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, TileAtmosphere other) private void ConsiderFirelocks(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, TileAtmosphere other, GasTileOverlayComponent? visuals, MapGridComponent mapGrid)
{ {
if (!_mapManager.TryGetGrid(tile.GridIndex, out var mapGrid))
return;
var reconsiderAdjacent = false; var reconsiderAdjacent = false;
foreach (var entity in mapGrid.GetAnchoredEntities(tile.GridIndices)) foreach (var entity in mapGrid.GetAnchoredEntities(tile.GridIndices))
@@ -526,11 +524,11 @@ namespace Content.Server.Atmos.EntitySystems
var otherEv = new UpdateAdjacentMethodEvent(mapGrid.Owner, other.GridIndices); var otherEv = new UpdateAdjacentMethodEvent(mapGrid.Owner, other.GridIndices);
GridUpdateAdjacent(mapGrid.Owner, gridAtmosphere, ref tileEv); GridUpdateAdjacent(mapGrid.Owner, gridAtmosphere, ref tileEv);
GridUpdateAdjacent(mapGrid.Owner, gridAtmosphere, ref otherEv); GridUpdateAdjacent(mapGrid.Owner, gridAtmosphere, ref otherEv);
InvalidateVisuals(tile.GridIndex, tile.GridIndices); InvalidateVisuals(tile.GridIndex, tile.GridIndices, visuals);
InvalidateVisuals(other.GridIndex, other.GridIndices); InvalidateVisuals(other.GridIndex, other.GridIndices, visuals);
} }
private void FinalizeEq(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile) private void FinalizeEq(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, GasTileOverlayComponent? visuals)
{ {
Span<float> transferDirections = stackalloc float[Atmospherics.Directions]; Span<float> transferDirections = stackalloc float[Atmospherics.Directions];
var hasTransferDirs = false; var hasTransferDirs = false;
@@ -557,17 +555,17 @@ namespace Content.Server.Atmos.EntitySystems
// Everything that calls this method already ensures that Air will not be null. // Everything that calls this method already ensures that Air will not be null.
if (tile.Air!.TotalMoles < amount) if (tile.Air!.TotalMoles < amount)
FinalizeEqNeighbors(gridAtmosphere, tile, transferDirections); FinalizeEqNeighbors(gridAtmosphere, tile, transferDirections, visuals);
otherTile.MonstermosInfo[direction.GetOpposite()] = 0; otherTile.MonstermosInfo[direction.GetOpposite()] = 0;
Merge(otherTile.Air, tile.Air.Remove(amount)); Merge(otherTile.Air, tile.Air.Remove(amount));
InvalidateVisuals(tile.GridIndex, tile.GridIndices); InvalidateVisuals(tile.GridIndex, tile.GridIndices, visuals);
InvalidateVisuals(otherTile.GridIndex, otherTile.GridIndices); InvalidateVisuals(otherTile.GridIndex, otherTile.GridIndices, visuals);
ConsiderPressureDifference(gridAtmosphere, tile, direction, amount); ConsiderPressureDifference(gridAtmosphere, tile, direction, amount);
} }
} }
private void FinalizeEqNeighbors(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, ReadOnlySpan<float> transferDirs) private void FinalizeEqNeighbors(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, ReadOnlySpan<float> transferDirs, GasTileOverlayComponent? visuals)
{ {
for (var i = 0; i < Atmospherics.Directions; i++) for (var i = 0; i < Atmospherics.Directions; i++)
{ {
@@ -575,7 +573,7 @@ namespace Content.Server.Atmos.EntitySystems
var amount = transferDirs[i]; var amount = transferDirs[i];
// Since AdjacentBits is set, AdjacentTiles[i] wouldn't be null, and neither would its air. // Since AdjacentBits is set, AdjacentTiles[i] wouldn't be null, and neither would its air.
if(amount < 0 && tile.AdjacentBits.IsFlagSet(direction)) if(amount < 0 && tile.AdjacentBits.IsFlagSet(direction))
FinalizeEq(gridAtmosphere, tile.AdjacentTiles[i]!); // A bit of recursion if needed. FinalizeEq(gridAtmosphere, tile.AdjacentTiles[i]!, visuals); // A bit of recursion if needed.
} }
} }

View File

@@ -2,10 +2,12 @@ using Content.Server.Atmos.Components;
using Content.Server.Atmos.Piping.Components; using Content.Server.Atmos.Piping.Components;
using Content.Server.NodeContainer.NodeGroups; using Content.Server.NodeContainer.NodeGroups;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Maps; using Content.Shared.Maps;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using static Content.Shared.Disposal.Components.SharedDisposalUnitComponent;
namespace Content.Server.Atmos.EntitySystems namespace Content.Server.Atmos.EntitySystems
{ {
@@ -36,7 +38,7 @@ namespace Content.Server.Atmos.EntitySystems
/// </summary> /// </summary>
/// <param name="atmosphere">The grid atmosphere in question.</param> /// <param name="atmosphere">The grid atmosphere in question.</param>
/// <returns>Whether the process succeeded or got paused due to time constrains.</returns> /// <returns>Whether the process succeeded or got paused due to time constrains.</returns>
private bool ProcessRevalidate(GridAtmosphereComponent atmosphere) private bool ProcessRevalidate(GridAtmosphereComponent atmosphere, GasTileOverlayComponent? visuals)
{ {
if (!atmosphere.ProcessingPaused) if (!atmosphere.ProcessingPaused)
{ {
@@ -126,7 +128,7 @@ namespace Content.Server.Atmos.EntitySystems
tile.ThermalConductivity = tileDef?.ThermalConductivity ?? 0.5f; tile.ThermalConductivity = tileDef?.ThermalConductivity ?? 0.5f;
tile.HeatCapacity = tileDef?.HeatCapacity ?? float.PositiveInfinity; tile.HeatCapacity = tileDef?.HeatCapacity ?? float.PositiveInfinity;
InvalidateVisuals(mapGridComp.Owner, indices); InvalidateVisuals(mapGridComp.Owner, indices, visuals);
for (var i = 0; i < Atmospherics.Directions; i++) for (var i = 0; i < Atmospherics.Directions; i++)
{ {
@@ -149,7 +151,7 @@ namespace Content.Server.Atmos.EntitySystems
return true; return true;
} }
private bool ProcessTileEqualize(GridAtmosphereComponent atmosphere) private bool ProcessTileEqualize(GridAtmosphereComponent atmosphere, GasTileOverlayComponent? visuals)
{ {
if(!atmosphere.ProcessingPaused) if(!atmosphere.ProcessingPaused)
atmosphere.CurrentRunTiles = new Queue<TileAtmosphere>(atmosphere.ActiveTiles); atmosphere.CurrentRunTiles = new Queue<TileAtmosphere>(atmosphere.ActiveTiles);
@@ -162,7 +164,7 @@ namespace Content.Server.Atmos.EntitySystems
var number = 0; var number = 0;
while (atmosphere.CurrentRunTiles.TryDequeue(out var tile)) while (atmosphere.CurrentRunTiles.TryDequeue(out var tile))
{ {
EqualizePressureInZone(mapGridComp, atmosphere, tile, atmosphere.UpdateCounter); EqualizePressureInZone(mapGridComp, atmosphere, tile, atmosphere.UpdateCounter, visuals);
if (number++ < LagCheckIterations) continue; if (number++ < LagCheckIterations) continue;
number = 0; number = 0;
@@ -176,7 +178,7 @@ namespace Content.Server.Atmos.EntitySystems
return true; return true;
} }
private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere) private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere, GasTileOverlayComponent? visuals)
{ {
if(!atmosphere.ProcessingPaused) if(!atmosphere.ProcessingPaused)
atmosphere.CurrentRunTiles = new Queue<TileAtmosphere>(atmosphere.ActiveTiles); atmosphere.CurrentRunTiles = new Queue<TileAtmosphere>(atmosphere.ActiveTiles);
@@ -184,7 +186,7 @@ namespace Content.Server.Atmos.EntitySystems
var number = 0; var number = 0;
while (atmosphere.CurrentRunTiles.TryDequeue(out var tile)) while (atmosphere.CurrentRunTiles.TryDequeue(out var tile))
{ {
ProcessCell(atmosphere, tile, atmosphere.UpdateCounter); ProcessCell(atmosphere, tile, atmosphere.UpdateCounter, visuals);
if (number++ < LagCheckIterations) continue; if (number++ < LagCheckIterations) continue;
number = 0; number = 0;
@@ -368,6 +370,7 @@ namespace Content.Server.Atmos.EntitySystems
for (; _currentRunAtmosphereIndex < _currentRunAtmosphere.Count; _currentRunAtmosphereIndex++) for (; _currentRunAtmosphereIndex < _currentRunAtmosphere.Count; _currentRunAtmosphereIndex++)
{ {
var atmosphere = _currentRunAtmosphere[_currentRunAtmosphereIndex]; var atmosphere = _currentRunAtmosphere[_currentRunAtmosphereIndex];
TryComp(atmosphere.Owner, out GasTileOverlayComponent? visuals);
if (atmosphere.LifeStage >= ComponentLifeStage.Stopping || Paused(atmosphere.Owner) || !atmosphere.Simulated) if (atmosphere.LifeStage >= ComponentLifeStage.Stopping || Paused(atmosphere.Owner) || !atmosphere.Simulated)
continue; continue;
@@ -383,7 +386,7 @@ namespace Content.Server.Atmos.EntitySystems
switch (atmosphere.State) switch (atmosphere.State)
{ {
case AtmosphereProcessingState.Revalidate: case AtmosphereProcessingState.Revalidate:
if (!ProcessRevalidate(atmosphere)) if (!ProcessRevalidate(atmosphere, visuals))
{ {
atmosphere.ProcessingPaused = true; atmosphere.ProcessingPaused = true;
return; return;
@@ -398,7 +401,7 @@ namespace Content.Server.Atmos.EntitySystems
: AtmosphereProcessingState.ActiveTiles; : AtmosphereProcessingState.ActiveTiles;
continue; continue;
case AtmosphereProcessingState.TileEqualize: case AtmosphereProcessingState.TileEqualize:
if (!ProcessTileEqualize(atmosphere)) if (!ProcessTileEqualize(atmosphere, visuals))
{ {
atmosphere.ProcessingPaused = true; atmosphere.ProcessingPaused = true;
return; return;
@@ -408,7 +411,7 @@ namespace Content.Server.Atmos.EntitySystems
atmosphere.State = AtmosphereProcessingState.ActiveTiles; atmosphere.State = AtmosphereProcessingState.ActiveTiles;
continue; continue;
case AtmosphereProcessingState.ActiveTiles: case AtmosphereProcessingState.ActiveTiles:
if (!ProcessActiveTiles(atmosphere)) if (!ProcessActiveTiles(atmosphere, visuals))
{ {
atmosphere.ProcessingPaused = true; atmosphere.ProcessingPaused = true;
return; return;

View File

@@ -1,6 +1,7 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Content.Server.Atmos.Components; using Content.Server.Atmos.Components;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Maps; using Content.Shared.Maps;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
@@ -34,9 +35,9 @@ public partial class AtmosphereSystem
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void InvalidateVisuals(EntityUid gridUid, Vector2i tile) public void InvalidateVisuals(EntityUid gridUid, Vector2i tile, GasTileOverlayComponent? comp = null)
{ {
_gasTileOverlaySystem.Invalidate(gridUid, tile); _gasTileOverlaySystem.Invalidate(gridUid, tile, comp);
} }
public bool NeedsVacuumFixing(MapGridComponent mapGrid, Vector2i indices) public bool NeedsVacuumFixing(MapGridComponent mapGrid, Vector2i indices)

View File

@@ -4,6 +4,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Atmos.Components; using Content.Server.Atmos.Components;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems; using Content.Shared.Atmos.EntitySystems;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Chunking; using Content.Shared.Chunking;
@@ -12,9 +13,10 @@ using Content.Shared.Rounding;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.ObjectPool;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Enums; using Robust.Shared.Enums;
using Robust.Shared.IoC; using Robust.Shared.GameStates;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Threading; using Robust.Shared.Threading;
using Robust.Shared.Timing; using Robust.Shared.Timing;
@@ -37,17 +39,6 @@ namespace Content.Server.Atmos.EntitySystems
private readonly Dictionary<IPlayerSession, Dictionary<EntityUid, HashSet<Vector2i>>> _lastSentChunks = new(); private readonly Dictionary<IPlayerSession, Dictionary<EntityUid, HashSet<Vector2i>>> _lastSentChunks = new();
/// <summary>
/// The tiles that have had their atmos data updated since last tick
/// </summary>
private readonly Dictionary<EntityUid, HashSet<Vector2i>> _invalidTiles = new();
/// <summary>
/// Gas data stored in chunks to make PVS / bubbling easier.
/// </summary>
private readonly Dictionary<EntityUid, Dictionary<Vector2i, GasOverlayChunk>> _overlay =
new();
// Oh look its more duplicated decal system code! // Oh look its more duplicated decal system code!
private ObjectPool<HashSet<Vector2i>> _chunkIndexPool = private ObjectPool<HashSet<Vector2i>> _chunkIndexPool =
new DefaultObjectPool<HashSet<Vector2i>>( new DefaultObjectPool<HashSet<Vector2i>>(
@@ -63,15 +54,18 @@ namespace Content.Server.Atmos.EntitySystems
private int _thresholds; private int _thresholds;
private bool _pvsEnabled;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoved);
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged; _playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
_confMan.OnValueChanged(CCVars.NetGasOverlayTickRate, UpdateTickRate, true); _confMan.OnValueChanged(CCVars.NetGasOverlayTickRate, UpdateTickRate, true);
_confMan.OnValueChanged(CCVars.GasOverlayThresholds, UpdateThresholds, true); _confMan.OnValueChanged(CCVars.GasOverlayThresholds, UpdateThresholds, true);
_confMan.OnValueChanged(CVars.NetPVS, OnPvsToggle, true);
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
SubscribeLocalEvent<GasTileOverlayComponent, ComponentGetState>(OnGetState);
} }
public override void Shutdown() public override void Shutdown()
@@ -80,20 +74,45 @@ namespace Content.Server.Atmos.EntitySystems
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged; _playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
_confMan.UnsubValueChanged(CCVars.NetGasOverlayTickRate, UpdateTickRate); _confMan.UnsubValueChanged(CCVars.NetGasOverlayTickRate, UpdateTickRate);
_confMan.UnsubValueChanged(CCVars.GasOverlayThresholds, UpdateThresholds); _confMan.UnsubValueChanged(CCVars.GasOverlayThresholds, UpdateThresholds);
_confMan.UnsubValueChanged(CVars.NetPVS, OnPvsToggle);
}
private void OnPvsToggle(bool value)
{
if (value == _pvsEnabled)
return;
_pvsEnabled = value;
if (value)
return;
foreach (var lastSent in _lastSentChunks.Values)
{
foreach (var set in lastSent.Values)
{
set.Clear();
_chunkIndexPool.Return(set);
}
lastSent.Clear();
}
// PVS was turned off, ensure data gets sent to all clients.
foreach (var (grid, meta) in EntityQuery<GasTileOverlayComponent, MetaDataComponent>(true))
{
grid.ForceTick = _gameTiming.CurTick;
Dirty(grid, meta);
}
} }
private void UpdateTickRate(float value) => _updateInterval = value > 0.0f ? 1 / value : float.MaxValue; private void UpdateTickRate(float value) => _updateInterval = value > 0.0f ? 1 / value : float.MaxValue;
private void UpdateThresholds(int value) => _thresholds = value; private void UpdateThresholds(int value) => _thresholds = value;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invalidate(EntityUid grid, Vector2i index) public void Invalidate(EntityUid grid, Vector2i index, GasTileOverlayComponent? comp = null)
{ {
_invalidTiles.GetOrNew(grid).Add(index); if (Resolve(grid, ref comp))
} comp.InvalidTiles.Add(index);
private void OnGridRemoved(GridRemovalEvent ev)
{
_overlay.Remove(ev.EntityUid);
} }
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
@@ -117,19 +136,19 @@ namespace Content.Server.Atmos.EntitySystems
} }
/// <summary> /// <summary>
/// Updates the visuals for a tile on some grid chunk. /// Updates the visuals for a tile on some grid chunk. Returns true if the visuals have changed.
/// </summary> /// </summary>
private void UpdateChunkTile(GridAtmosphereComponent gridAtmosphere, GasOverlayChunk chunk, Vector2i index, GameTick curTick) private bool UpdateChunkTile(GridAtmosphereComponent gridAtmosphere, GasOverlayChunk chunk, Vector2i index, GameTick curTick)
{ {
ref var oldData = ref chunk.GetData(index); ref var oldData = ref chunk.GetData(index);
if (!gridAtmosphere.Tiles.TryGetValue(index, out var tile)) if (!gridAtmosphere.Tiles.TryGetValue(index, out var tile))
{ {
if (oldData.Equals(default)) if (oldData.Equals(default))
return; return false;
chunk.LastUpdate = curTick; chunk.LastUpdate = curTick;
oldData = default; oldData = default;
return; return true;
} }
var changed = false; var changed = false;
@@ -186,35 +205,33 @@ namespace Content.Server.Atmos.EntitySystems
} }
if (!changed) if (!changed)
return; return false;
chunk.LastUpdate = curTick; chunk.LastUpdate = curTick;
return true;
} }
private void UpdateOverlayData(GameTick curTick) private void UpdateOverlayData(GameTick curTick)
{ {
// TODO parallelize? // TODO parallelize?
foreach (var (gridId, invalidIndices) in _invalidTiles) foreach (var (overlay, gam, meta) in EntityQuery<GasTileOverlayComponent, GridAtmosphereComponent, MetaDataComponent>(true))
{ {
if (!TryComp(gridId, out GridAtmosphereComponent? gam)) bool changed = false;
{ foreach (var index in overlay.InvalidTiles)
_overlay.Remove(gridId);
continue;
}
var chunks = _overlay.GetOrNew(gridId);
foreach (var index in invalidIndices)
{ {
var chunkIndex = GetGasChunkIndices(index); var chunkIndex = GetGasChunkIndices(index);
if (!chunks.TryGetValue(chunkIndex, out var chunk)) if (!overlay.Chunks.TryGetValue(chunkIndex, out var chunk))
chunks[chunkIndex] = chunk = new GasOverlayChunk(chunkIndex); overlay.Chunks[chunkIndex] = chunk = new GasOverlayChunk(chunkIndex);
UpdateChunkTile(gam, chunk, index, curTick); changed |= UpdateChunkTile(gam, chunk, index, curTick);
} }
if (changed)
Dirty(overlay, meta);
overlay.InvalidTiles.Clear();
} }
_invalidTiles.Clear();
} }
public override void Update(float frameTime) public override void Update(float frameTime)
@@ -230,6 +247,9 @@ namespace Content.Server.Atmos.EntitySystems
// First, update per-chunk visual data for any invalidated tiles. // First, update per-chunk visual data for any invalidated tiles.
UpdateOverlayData(curTick); UpdateOverlayData(curTick);
if (!_pvsEnabled)
return;
// Now we'll go through each player, then through each chunk in range of that player checking if the player is still in range // Now we'll go through each player, then through each chunk in range of that player checking if the player is still in range
// If they are, check if they need the new data to send (i.e. if there's an overlay for the gas). // If they are, check if they need the new data to send (i.e. if there's an overlay for the gas).
// Afterwards we reset all the chunk data for the next time we tick. // Afterwards we reset all the chunk data for the next time we tick.
@@ -288,8 +308,8 @@ namespace Content.Server.Atmos.EntitySystems
foreach (var (grid, gridChunks) in chunksInRange) foreach (var (grid, gridChunks) in chunksInRange)
{ {
// Not all grids have atmospheres. // Not all grids have atmospheres.
if (!_overlay.TryGetValue(grid, out var gridData)) if (!TryComp(grid, out GasTileOverlayComponent? overlay))
continue; return;
List<GasOverlayChunk> dataToSend = new(); List<GasOverlayChunk> dataToSend = new();
ev.UpdatedChunks[grid] = dataToSend; ev.UpdatedChunks[grid] = dataToSend;
@@ -298,7 +318,7 @@ namespace Content.Server.Atmos.EntitySystems
foreach (var index in gridChunks) foreach (var index in gridChunks)
{ {
if (!gridData.TryGetValue(index, out var value)) if (!overlay.Chunks.TryGetValue(index, out var value))
continue; continue;
if (previousChunks != null && if (previousChunks != null &&
@@ -321,11 +341,8 @@ namespace Content.Server.Atmos.EntitySystems
RaiseNetworkEvent(ev, playerSession.ConnectedClient); RaiseNetworkEvent(ev, playerSession.ConnectedClient);
} }
public override void Reset(RoundRestartCleanupEvent ev) public void Reset(RoundRestartCleanupEvent ev)
{ {
_invalidTiles.Clear();
_overlay.Clear();
foreach (var data in _lastSentChunks.Values) foreach (var data in _lastSentChunks.Values)
{ {
foreach (var previous in data.Values) foreach (var previous in data.Values)
@@ -337,5 +354,27 @@ namespace Content.Server.Atmos.EntitySystems
data.Clear(); data.Clear();
} }
} }
private void OnGetState(EntityUid uid, GasTileOverlayComponent component, ref ComponentGetState args)
{
if (_pvsEnabled && !args.ReplayState)
return;
// Should this be a full component state or a delta-state?
if (args.FromTick <= component.CreationTick && args.FromTick <= component.ForceTick)
{
args.State = new GasTileOverlayState(component.Chunks);
return;
}
var data = new Dictionary<Vector2i, GasOverlayChunk>();
foreach (var (index, chunk) in component.Chunks)
{
if (chunk.LastUpdate >= args.FromTick)
data[index] = chunk;
}
args.State = new GasTileOverlayState(data) { AllChunks = new(component.Chunks.Keys) };
}
} }
} }

View File

@@ -0,0 +1,81 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Shared.Atmos.Components;
[RegisterComponent, NetworkedComponent]
public sealed class GasTileOverlayComponent : Component
{
/// <summary>
/// The tiles that have had their atmos data updated since last tick
/// </summary>
public readonly HashSet<Vector2i> InvalidTiles = new();
/// <summary>
/// Gas data stored in chunks to make PVS / bubbling easier.
/// </summary>
public readonly Dictionary<Vector2i, GasOverlayChunk> Chunks = new();
/// <summary>
/// Tick at which PVS was last toggled. Ensures that all players receive a full update when toggling PVS.
/// </summary>
public GameTick ForceTick { get; set; }
}
[Serializable, NetSerializable]
public sealed class GasTileOverlayState : ComponentState, IComponentDeltaState
{
public readonly Dictionary<Vector2i, GasOverlayChunk> Chunks;
public bool FullState => AllChunks == null;
// required to infer deleted/missing chunks for delta states
public HashSet<Vector2i>? AllChunks;
public GasTileOverlayState(Dictionary<Vector2i, GasOverlayChunk> chunks)
{
Chunks = chunks;
}
public void ApplyToFullState(ComponentState fullState)
{
DebugTools.Assert(!FullState);
var state = (GasTileOverlayState) fullState;
DebugTools.Assert(state.FullState);
foreach (var key in state.Chunks.Keys)
{
if (!AllChunks!.Contains(key))
state.Chunks.Remove(key);
}
foreach (var (chunk, data) in Chunks)
{
state.Chunks[chunk] = new(data);
}
}
public ComponentState CreateNewFullState(ComponentState fullState)
{
DebugTools.Assert(!FullState);
var state = (GasTileOverlayState) fullState;
DebugTools.Assert(state.FullState);
var chunks = new Dictionary<Vector2i, GasOverlayChunk>(state.Chunks.Count);
foreach (var (chunk, data) in Chunks)
{
chunks[chunk] = new(data);
}
foreach (var (chunk, data) in state.Chunks)
{
if (AllChunks!.Contains(chunk))
chunks.TryAdd(chunk, new(data));
}
return new GasTileOverlayState(chunks);
}
}

View File

@@ -1,5 +1,4 @@
using Content.Shared.Atmos.Prototypes; using Content.Shared.Atmos.Prototypes;
using Content.Shared.GameTicking;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
@@ -21,8 +20,6 @@ namespace Content.Shared.Atmos.EntitySystems
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
List<int> visibleGases = new(); List<int> visibleGases = new();
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++) for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
@@ -35,8 +32,6 @@ namespace Content.Shared.Atmos.EntitySystems
VisibleGasId = visibleGases.ToArray(); VisibleGasId = visibleGases.ToArray();
} }
public abstract void Reset(RoundRestartCleanupEvent ev);
public static Vector2i GetGasChunkIndices(Vector2i indices) public static Vector2i GetGasChunkIndices(Vector2i indices)
{ {
return new((int) MathF.Floor((float) indices.X / ChunkSize), (int) MathF.Floor((float) indices.Y / ChunkSize)); return new((int) MathF.Floor((float) indices.X / ChunkSize), (int) MathF.Floor((float) indices.Y / ChunkSize));

View File

@@ -33,6 +33,19 @@ namespace Content.Shared.Atmos
} }
} }
public GasOverlayChunk(GasOverlayChunk data)
{
Index = data.Index;
Origin = data.Origin;
for (int i = 0; i < ChunkSize; i++)
{
// This does not clone the opacity array. However, this chunk cloning is only used by the client,
// which never modifies that directly. So this should be fine.
var array = TileData[i] = new GasOverlayData[ChunkSize];
Array.Copy(data.TileData[i], array, ChunkSize);
}
}
public ref GasOverlayData GetData(Vector2i gridIndices) public ref GasOverlayData GetData(Vector2i gridIndices)
{ {
DebugTools.Assert(InBounds(gridIndices)); DebugTools.Assert(InBounds(gridIndices));