Gas tile overlay state handling changes (#12691)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
81
Content.Shared/Atmos/Components/GasTileOverlayComponent.cs
Normal file
81
Content.Shared/Atmos/Components/GasTileOverlayComponent.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user