diff --git a/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs b/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs index 2d12d50ef3..a7a2f8d904 100644 --- a/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs +++ b/Content.Client/Atmos/EntitySystems/GasTileOverlaySystem.cs @@ -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(HandleGasOverlayUpdate); - SubscribeLocalEvent(OnGridRemoved); + SubscribeLocalEvent(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); - } } } diff --git a/Content.Client/Atmos/Overlays/GasTileOverlay.cs b/Content.Client/Atmos/Overlays/GasTileOverlay.cs index c7b06d4be2..a446156f81 100644 --- a/Content.Client/Atmos/Overlays/GasTileOverlay.cs +++ b/Content.Client/Atmos/Overlays/GasTileOverlay.cs @@ -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> 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(); + var overlayQuery = _entManager.GetEntityQuery(); 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); diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs index 6cb1c59697..ff8adaf702 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs @@ -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(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); } } } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs index 165fb06c2d..795c6e0547 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.LINDA.cs @@ -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; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs index fa419e98d3..6ec036f4e0 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Monstermos.cs @@ -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 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 transferDirs) + private void FinalizeEqNeighbors(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, ReadOnlySpan 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. } } diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs index 80d637a867..7379b08472 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Processing.cs @@ -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 /// /// The grid atmosphere in question. /// Whether the process succeeded or got paused due to time constrains. - 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(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(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; diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs index be710390c1..b0b06fd07a 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Utils.cs @@ -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) diff --git a/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs b/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs index e8da8a2357..3b3acf8a38 100644 --- a/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs +++ b/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs @@ -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>> _lastSentChunks = new(); - /// - /// The tiles that have had their atmos data updated since last tick - /// - private readonly Dictionary> _invalidTiles = new(); - - /// - /// Gas data stored in chunks to make PVS / bubbling easier. - /// - private readonly Dictionary> _overlay = - new(); - // Oh look its more duplicated decal system code! private ObjectPool> _chunkIndexPool = new DefaultObjectPool>( @@ -63,15 +54,18 @@ namespace Content.Server.Atmos.EntitySystems private int _thresholds; + private bool _pvsEnabled; + public override void Initialize() { base.Initialize(); - - SubscribeLocalEvent(OnGridRemoved); - _playerManager.PlayerStatusChanged += OnPlayerStatusChanged; _confMan.OnValueChanged(CCVars.NetGasOverlayTickRate, UpdateTickRate, true); _confMan.OnValueChanged(CCVars.GasOverlayThresholds, UpdateThresholds, true); + _confMan.OnValueChanged(CVars.NetPVS, OnPvsToggle, true); + + SubscribeLocalEvent(Reset); + SubscribeLocalEvent(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(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 } /// - /// 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. /// - 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(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 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(); + 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) }; + } } } diff --git a/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs b/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs new file mode 100644 index 0000000000..3f75c81c42 --- /dev/null +++ b/Content.Shared/Atmos/Components/GasTileOverlayComponent.cs @@ -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 +{ + /// + /// The tiles that have had their atmos data updated since last tick + /// + public readonly HashSet InvalidTiles = new(); + + /// + /// Gas data stored in chunks to make PVS / bubbling easier. + /// + public readonly Dictionary Chunks = new(); + + /// + /// Tick at which PVS was last toggled. Ensures that all players receive a full update when toggling PVS. + /// + public GameTick ForceTick { get; set; } +} + + +[Serializable, NetSerializable] +public sealed class GasTileOverlayState : ComponentState, IComponentDeltaState +{ + public readonly Dictionary Chunks; + public bool FullState => AllChunks == null; + + // required to infer deleted/missing chunks for delta states + public HashSet? AllChunks; + + public GasTileOverlayState(Dictionary 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(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); + } +} diff --git a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs index 88cd0e8565..b4674f1059 100644 --- a/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs +++ b/Content.Shared/Atmos/EntitySystems/SharedGasTileOverlaySystem.cs @@ -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(Reset); - List 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)); diff --git a/Content.Shared/Atmos/GasOverlayChunk.cs b/Content.Shared/Atmos/GasOverlayChunk.cs index 18a53a95ba..e83ca7c6ca 100644 --- a/Content.Shared/Atmos/GasOverlayChunk.cs +++ b/Content.Shared/Atmos/GasOverlayChunk.cs @@ -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));