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.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.GameTicking;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Utility;
using Robust.Shared.GameStates;
namespace Content.Client.Atmos.EntitySystems
{
@@ -22,49 +22,71 @@ namespace Content.Client.Atmos.EntitySystems
{
base.Initialize();
SubscribeNetworkEvent<GasOverlayUpdateEvent>(HandleGasOverlayUpdate);
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoved);
SubscribeLocalEvent<GasTileOverlayComponent, ComponentHandleState>(OnHandleState);
_overlay = new GasTileOverlay(this, EntityManager, _resourceCache, ProtoMan, _spriteSys);
_overlayMan.AddOverlay(_overlay);
}
public override void Reset(RoundRestartCleanupEvent ev)
{
_overlay.TileData.Clear();
}
public override void Shutdown()
{
base.Shutdown();
_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)
{
foreach (var (grid, removedIndicies) in ev.RemovedChunks)
{
if (!_overlay.TileData.TryGetValue(grid, out var chunks))
if (!TryComp(grid, out GasTileOverlayComponent? comp))
continue;
foreach (var index in removedIndicies)
{
chunks.Remove(index);
comp.Chunks.Remove(index);
}
}
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)
{
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.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.Prototypes;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@@ -20,8 +21,6 @@ namespace Content.Client.Atmos.Overlays
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities;
private readonly ShaderInstance _shader;
public readonly Dictionary<EntityUid, Dictionary<Vector2i, GasOverlayChunk>> TileData = new();
// Gas overlays
private readonly float[] _timer;
private readonly float[][] _frameDelays;
@@ -139,12 +138,15 @@ namespace Content.Client.Atmos.Overlays
{
var drawHandle = args.WorldHandle;
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
var overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
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))
{
continue;
}
var (_, _, worldMatrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv();
drawHandle.SetTransform(worldMatrix);
@@ -160,7 +162,7 @@ namespace Content.Client.Atmos.Overlays
// by chunk, even though its currently slower.
drawHandle.UseShader(null);
foreach (var chunk in gridData.Values)
foreach (var chunk in comp.Chunks.Values)
{
var enumerator = new GasChunkEnumerator(chunk);
@@ -184,7 +186,7 @@ namespace Content.Client.Atmos.Overlays
// And again for fire, with the unshaded shader
drawHandle.UseShader(_shader);
foreach (var chunk in gridData.Values)
foreach (var chunk in comp.Chunks.Values)
{
var enumerator = new GasChunkEnumerator(chunk);

View File

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

View File

@@ -2,13 +2,14 @@ using Content.Server.Atmos.Components;
using Content.Server.Doors.Components;
using Content.Server.Doors.Systems;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Database;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using System.Linq;
using Robust.Shared.Map.Components;
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[] _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))
return; // Already done.
@@ -89,7 +90,7 @@ namespace Content.Server.Atmos.EntitySystems
{
// Looks like someone opened an airlock to space!
ExplosivelyDepressurize(mapGrid, gridAtmosphere, tile, cycleNum);
ExplosivelyDepressurize(mapGrid, gridAtmosphere, tile, cycleNum, visuals);
return;
}
}
@@ -330,7 +331,7 @@ namespace Content.Server.Atmos.EntitySystems
for (var i = 0; i < tileCount; i++)
{
var otherTile = _equalizeTiles[i]!;
FinalizeEq(gridAtmosphere, otherTile);
FinalizeEq(gridAtmosphere, otherTile, visuals);
}
for (var i = 0; i < tileCount; i++)
@@ -355,7 +356,7 @@ namespace Content.Server.Atmos.EntitySystems
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.
if (!MonstermosDepressurization || tile.Air == null)
@@ -390,7 +391,7 @@ namespace Content.Server.Atmos.EntitySystems
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle) continue;
ConsiderFirelocks(gridAtmosphere, otherTile, otherTile2);
ConsiderFirelocks(gridAtmosphere, otherTile, otherTile2, visuals, mapGrid);
// The firelocks might have closed on us.
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!
otherTile.Air.Temperature = Atmospherics.TCMB;
InvalidateVisuals(otherTile.GridIndex, otherTile.GridIndices);
InvalidateVisuals(otherTile.GridIndex, otherTile.GridIndices, visuals);
HandleDecompressionFloorRip(mapGrid, otherTile, sum);
}
@@ -496,11 +497,8 @@ namespace Content.Server.Atmos.EntitySystems
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;
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);
GridUpdateAdjacent(mapGrid.Owner, gridAtmosphere, ref tileEv);
GridUpdateAdjacent(mapGrid.Owner, gridAtmosphere, ref otherEv);
InvalidateVisuals(tile.GridIndex, tile.GridIndices);
InvalidateVisuals(other.GridIndex, other.GridIndices);
InvalidateVisuals(tile.GridIndex, tile.GridIndices, visuals);
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];
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.
if (tile.Air!.TotalMoles < amount)
FinalizeEqNeighbors(gridAtmosphere, tile, transferDirections);
FinalizeEqNeighbors(gridAtmosphere, tile, transferDirections, visuals);
otherTile.MonstermosInfo[direction.GetOpposite()] = 0;
Merge(otherTile.Air, tile.Air.Remove(amount));
InvalidateVisuals(tile.GridIndex, tile.GridIndices);
InvalidateVisuals(otherTile.GridIndex, otherTile.GridIndices);
InvalidateVisuals(tile.GridIndex, tile.GridIndices, visuals);
InvalidateVisuals(otherTile.GridIndex, otherTile.GridIndices, visuals);
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++)
{
@@ -575,7 +573,7 @@ namespace Content.Server.Atmos.EntitySystems
var amount = transferDirs[i];
// Since AdjacentBits is set, AdjacentTiles[i] wouldn't be null, and neither would its air.
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.NodeContainer.NodeGroups;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Maps;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
using static Content.Shared.Disposal.Components.SharedDisposalUnitComponent;
namespace Content.Server.Atmos.EntitySystems
{
@@ -36,7 +38,7 @@ namespace Content.Server.Atmos.EntitySystems
/// </summary>
/// <param name="atmosphere">The grid atmosphere in question.</param>
/// <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)
{
@@ -126,7 +128,7 @@ namespace Content.Server.Atmos.EntitySystems
tile.ThermalConductivity = tileDef?.ThermalConductivity ?? 0.5f;
tile.HeatCapacity = tileDef?.HeatCapacity ?? float.PositiveInfinity;
InvalidateVisuals(mapGridComp.Owner, indices);
InvalidateVisuals(mapGridComp.Owner, indices, visuals);
for (var i = 0; i < Atmospherics.Directions; i++)
{
@@ -149,7 +151,7 @@ namespace Content.Server.Atmos.EntitySystems
return true;
}
private bool ProcessTileEqualize(GridAtmosphereComponent atmosphere)
private bool ProcessTileEqualize(GridAtmosphereComponent atmosphere, GasTileOverlayComponent? visuals)
{
if(!atmosphere.ProcessingPaused)
atmosphere.CurrentRunTiles = new Queue<TileAtmosphere>(atmosphere.ActiveTiles);
@@ -162,7 +164,7 @@ namespace Content.Server.Atmos.EntitySystems
var number = 0;
while (atmosphere.CurrentRunTiles.TryDequeue(out var tile))
{
EqualizePressureInZone(mapGridComp, atmosphere, tile, atmosphere.UpdateCounter);
EqualizePressureInZone(mapGridComp, atmosphere, tile, atmosphere.UpdateCounter, visuals);
if (number++ < LagCheckIterations) continue;
number = 0;
@@ -176,7 +178,7 @@ namespace Content.Server.Atmos.EntitySystems
return true;
}
private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere)
private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere, GasTileOverlayComponent? visuals)
{
if(!atmosphere.ProcessingPaused)
atmosphere.CurrentRunTiles = new Queue<TileAtmosphere>(atmosphere.ActiveTiles);
@@ -184,7 +186,7 @@ namespace Content.Server.Atmos.EntitySystems
var number = 0;
while (atmosphere.CurrentRunTiles.TryDequeue(out var tile))
{
ProcessCell(atmosphere, tile, atmosphere.UpdateCounter);
ProcessCell(atmosphere, tile, atmosphere.UpdateCounter, visuals);
if (number++ < LagCheckIterations) continue;
number = 0;
@@ -368,6 +370,7 @@ namespace Content.Server.Atmos.EntitySystems
for (; _currentRunAtmosphereIndex < _currentRunAtmosphere.Count; _currentRunAtmosphereIndex++)
{
var atmosphere = _currentRunAtmosphere[_currentRunAtmosphereIndex];
TryComp(atmosphere.Owner, out GasTileOverlayComponent? visuals);
if (atmosphere.LifeStage >= ComponentLifeStage.Stopping || Paused(atmosphere.Owner) || !atmosphere.Simulated)
continue;
@@ -383,7 +386,7 @@ namespace Content.Server.Atmos.EntitySystems
switch (atmosphere.State)
{
case AtmosphereProcessingState.Revalidate:
if (!ProcessRevalidate(atmosphere))
if (!ProcessRevalidate(atmosphere, visuals))
{
atmosphere.ProcessingPaused = true;
return;
@@ -398,7 +401,7 @@ namespace Content.Server.Atmos.EntitySystems
: AtmosphereProcessingState.ActiveTiles;
continue;
case AtmosphereProcessingState.TileEqualize:
if (!ProcessTileEqualize(atmosphere))
if (!ProcessTileEqualize(atmosphere, visuals))
{
atmosphere.ProcessingPaused = true;
return;
@@ -408,7 +411,7 @@ namespace Content.Server.Atmos.EntitySystems
atmosphere.State = AtmosphereProcessingState.ActiveTiles;
continue;
case AtmosphereProcessingState.ActiveTiles:
if (!ProcessActiveTiles(atmosphere))
if (!ProcessActiveTiles(atmosphere, visuals))
{
atmosphere.ProcessingPaused = true;
return;

View File

@@ -1,6 +1,7 @@
using System.Runtime.CompilerServices;
using Content.Server.Atmos.Components;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Maps;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
@@ -34,9 +35,9 @@ public partial class AtmosphereSystem
}
[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)

View File

@@ -4,6 +4,7 @@ using System.Threading;
using System.Threading.Tasks;
using Content.Server.Atmos.Components;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.CCVar;
using Content.Shared.Chunking;
@@ -12,9 +13,10 @@ using Content.Shared.Rounding;
using JetBrains.Annotations;
using Microsoft.Extensions.ObjectPool;
using Robust.Server.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.IoC;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Threading;
using Robust.Shared.Timing;
@@ -37,17 +39,6 @@ namespace Content.Server.Atmos.EntitySystems
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!
private ObjectPool<HashSet<Vector2i>> _chunkIndexPool =
new DefaultObjectPool<HashSet<Vector2i>>(
@@ -63,15 +54,18 @@ namespace Content.Server.Atmos.EntitySystems
private int _thresholds;
private bool _pvsEnabled;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoved);
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
_confMan.OnValueChanged(CCVars.NetGasOverlayTickRate, UpdateTickRate, true);
_confMan.OnValueChanged(CCVars.GasOverlayThresholds, UpdateThresholds, true);
_confMan.OnValueChanged(CVars.NetPVS, OnPvsToggle, true);
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
SubscribeLocalEvent<GasTileOverlayComponent, ComponentGetState>(OnGetState);
}
public override void Shutdown()
@@ -80,20 +74,45 @@ namespace Content.Server.Atmos.EntitySystems
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
_confMan.UnsubValueChanged(CCVars.NetGasOverlayTickRate, UpdateTickRate);
_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 UpdateThresholds(int value) => _thresholds = value;
[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);
}
private void OnGridRemoved(GridRemovalEvent ev)
{
_overlay.Remove(ev.EntityUid);
if (Resolve(grid, ref comp))
comp.InvalidTiles.Add(index);
}
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
@@ -117,19 +136,19 @@ namespace Content.Server.Atmos.EntitySystems
}
/// <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>
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);
if (!gridAtmosphere.Tiles.TryGetValue(index, out var tile))
{
if (oldData.Equals(default))
return;
return false;
chunk.LastUpdate = curTick;
oldData = default;
return;
return true;
}
var changed = false;
@@ -186,35 +205,33 @@ namespace Content.Server.Atmos.EntitySystems
}
if (!changed)
return;
return false;
chunk.LastUpdate = curTick;
return true;
}
private void UpdateOverlayData(GameTick curTick)
{
// 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))
{
_overlay.Remove(gridId);
continue;
}
var chunks = _overlay.GetOrNew(gridId);
foreach (var index in invalidIndices)
bool changed = false;
foreach (var index in overlay.InvalidTiles)
{
var chunkIndex = GetGasChunkIndices(index);
if (!chunks.TryGetValue(chunkIndex, out var chunk))
chunks[chunkIndex] = chunk = new GasOverlayChunk(chunkIndex);
if (!overlay.Chunks.TryGetValue(chunkIndex, out var chunk))
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)
@@ -230,6 +247,9 @@ namespace Content.Server.Atmos.EntitySystems
// First, update per-chunk visual data for any invalidated tiles.
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
// 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.
@@ -288,8 +308,8 @@ namespace Content.Server.Atmos.EntitySystems
foreach (var (grid, gridChunks) in chunksInRange)
{
// Not all grids have atmospheres.
if (!_overlay.TryGetValue(grid, out var gridData))
continue;
if (!TryComp(grid, out GasTileOverlayComponent? overlay))
return;
List<GasOverlayChunk> dataToSend = new();
ev.UpdatedChunks[grid] = dataToSend;
@@ -298,7 +318,7 @@ namespace Content.Server.Atmos.EntitySystems
foreach (var index in gridChunks)
{
if (!gridData.TryGetValue(index, out var value))
if (!overlay.Chunks.TryGetValue(index, out var value))
continue;
if (previousChunks != null &&
@@ -321,11 +341,8 @@ namespace Content.Server.Atmos.EntitySystems
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 previous in data.Values)
@@ -337,5 +354,27 @@ namespace Content.Server.Atmos.EntitySystems
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.GameTicking;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
@@ -21,8 +20,6 @@ namespace Content.Shared.Atmos.EntitySystems
{
base.Initialize();
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
List<int> visibleGases = new();
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
@@ -35,8 +32,6 @@ namespace Content.Shared.Atmos.EntitySystems
VisibleGasId = visibleGases.ToArray();
}
public abstract void Reset(RoundRestartCleanupEvent ev);
public static Vector2i GetGasChunkIndices(Vector2i indices)
{
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)
{
DebugTools.Assert(InBounds(gridIndices));