Files
OldThink/Content.Server/Atmos/EntitySystems/GasTileOverlaySystem.cs

310 lines
11 KiB
C#
Raw Normal View History

using System.Runtime.CompilerServices;
Atmos pipe rework (#3833) * Initial * Cleanup a bunch of things * some changes dunno * RequireAnchored * a * stuff * more work * Lots of progress * delete pipe visualizer * a * b * pipenet and pipenode cleanup * Fixes * Adds GasValve * Adds GasMiner * Fix stuff, maybe? * More fixes * Ignored components on the client * Adds thermomachine behavior, change a bunch of stuff * Remove Anchored * some work, but it's shitcode * significantly more ECS * ECS AtmosDevices * Cleanup * fix appearance * when the pipe direction is sus * Gas tanks and canisters * pipe anchoring and stuff * coding is my passion * Unsafe pipes take longer to unanchor * turns out we're no longer using eris canisters * Gas canister inserted tank appearance, improvements * Work on a bunch of appearances * Scrubber appearance * Reorganize AtmosphereSystem.Piping into a bunch of different systems * Appearance for vent/scrubber/pump turns off when leaving atmosphere * ThermoMachine appearance * Cleanup gas tanks * Remove passive gate unused imports * remove old canister UI functionality * PipeNode environment air, make everything use AssumeAir instead of merging manually * a * Reorganize atmos to follow new structure * ????? * Canister UI, restructure client * Restructure shared * Fix build tho * listen, at least the canister UI works entirely... * fix build : ) * Atmos device prototypes have names and descriptions * gas canister ui slider doesn't jitter * trinary prototypes * sprite for miners * ignore components * fix YAML * Fix port system doing useless thing * Fix build * fix thinking moment * fix build again because * canister direction * pipenode is a word * GasTank Air will throw on invalid states * fix build.... * Unhardcode volume pump thresholds * Volume pump and filter take time into account * Rename Join/Leave atmosphere events to AtmosDeviceEnabled/Disabled Event * Gas tank node volume is set by initial mixtuer * I love node container
2021-06-19 13:25:05 +02:00
using Content.Server.Atmos.Components;
using Content.Shared.Atmos;
Atmos pipe rework (#3833) * Initial * Cleanup a bunch of things * some changes dunno * RequireAnchored * a * stuff * more work * Lots of progress * delete pipe visualizer * a * b * pipenet and pipenode cleanup * Fixes * Adds GasValve * Adds GasMiner * Fix stuff, maybe? * More fixes * Ignored components on the client * Adds thermomachine behavior, change a bunch of stuff * Remove Anchored * some work, but it's shitcode * significantly more ECS * ECS AtmosDevices * Cleanup * fix appearance * when the pipe direction is sus * Gas tanks and canisters * pipe anchoring and stuff * coding is my passion * Unsafe pipes take longer to unanchor * turns out we're no longer using eris canisters * Gas canister inserted tank appearance, improvements * Work on a bunch of appearances * Scrubber appearance * Reorganize AtmosphereSystem.Piping into a bunch of different systems * Appearance for vent/scrubber/pump turns off when leaving atmosphere * ThermoMachine appearance * Cleanup gas tanks * Remove passive gate unused imports * remove old canister UI functionality * PipeNode environment air, make everything use AssumeAir instead of merging manually * a * Reorganize atmos to follow new structure * ????? * Canister UI, restructure client * Restructure shared * Fix build tho * listen, at least the canister UI works entirely... * fix build : ) * Atmos device prototypes have names and descriptions * gas canister ui slider doesn't jitter * trinary prototypes * sprite for miners * ignore components * fix YAML * Fix port system doing useless thing * Fix build * fix thinking moment * fix build again because * canister direction * pipenode is a word * GasTank Air will throw on invalid states * fix build.... * Unhardcode volume pump thresholds * Volume pump and filter take time into account * Rename Join/Leave atmosphere events to AtmosDeviceEnabled/Disabled Event * Gas tank node volume is set by initial mixtuer * I love node container
2021-06-19 13:25:05 +02:00
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.CCVar;
2022-07-25 14:10:18 +12:00
using Content.Shared.Chunking;
using Content.Shared.GameTicking;
using Content.Shared.Rounding;
using JetBrains.Annotations;
2022-07-25 14:10:18 +12:00
using Microsoft.Extensions.ObjectPool;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Map;
2022-07-25 14:10:18 +12:00
using Robust.Shared.Player;
using Robust.Shared.Timing;
2022-07-25 14:10:18 +12:00
using Robust.Shared.Utility;
using DependencyAttribute = Robust.Shared.IoC.DependencyAttribute;
Atmos pipe rework (#3833) * Initial * Cleanup a bunch of things * some changes dunno * RequireAnchored * a * stuff * more work * Lots of progress * delete pipe visualizer * a * b * pipenet and pipenode cleanup * Fixes * Adds GasValve * Adds GasMiner * Fix stuff, maybe? * More fixes * Ignored components on the client * Adds thermomachine behavior, change a bunch of stuff * Remove Anchored * some work, but it's shitcode * significantly more ECS * ECS AtmosDevices * Cleanup * fix appearance * when the pipe direction is sus * Gas tanks and canisters * pipe anchoring and stuff * coding is my passion * Unsafe pipes take longer to unanchor * turns out we're no longer using eris canisters * Gas canister inserted tank appearance, improvements * Work on a bunch of appearances * Scrubber appearance * Reorganize AtmosphereSystem.Piping into a bunch of different systems * Appearance for vent/scrubber/pump turns off when leaving atmosphere * ThermoMachine appearance * Cleanup gas tanks * Remove passive gate unused imports * remove old canister UI functionality * PipeNode environment air, make everything use AssumeAir instead of merging manually * a * Reorganize atmos to follow new structure * ????? * Canister UI, restructure client * Restructure shared * Fix build tho * listen, at least the canister UI works entirely... * fix build : ) * Atmos device prototypes have names and descriptions * gas canister ui slider doesn't jitter * trinary prototypes * sprite for miners * ignore components * fix YAML * Fix port system doing useless thing * Fix build * fix thinking moment * fix build again because * canister direction * pipenode is a word * GasTank Air will throw on invalid states * fix build.... * Unhardcode volume pump thresholds * Volume pump and filter take time into account * Rename Join/Leave atmosphere events to AtmosDeviceEnabled/Disabled Event * Gas tank node volume is set by initial mixtuer * I love node container
2021-06-19 13:25:05 +02:00
// ReSharper disable once RedundantUsingDirective
Atmos pipe rework (#3833) * Initial * Cleanup a bunch of things * some changes dunno * RequireAnchored * a * stuff * more work * Lots of progress * delete pipe visualizer * a * b * pipenet and pipenode cleanup * Fixes * Adds GasValve * Adds GasMiner * Fix stuff, maybe? * More fixes * Ignored components on the client * Adds thermomachine behavior, change a bunch of stuff * Remove Anchored * some work, but it's shitcode * significantly more ECS * ECS AtmosDevices * Cleanup * fix appearance * when the pipe direction is sus * Gas tanks and canisters * pipe anchoring and stuff * coding is my passion * Unsafe pipes take longer to unanchor * turns out we're no longer using eris canisters * Gas canister inserted tank appearance, improvements * Work on a bunch of appearances * Scrubber appearance * Reorganize AtmosphereSystem.Piping into a bunch of different systems * Appearance for vent/scrubber/pump turns off when leaving atmosphere * ThermoMachine appearance * Cleanup gas tanks * Remove passive gate unused imports * remove old canister UI functionality * PipeNode environment air, make everything use AssumeAir instead of merging manually * a * Reorganize atmos to follow new structure * ????? * Canister UI, restructure client * Restructure shared * Fix build tho * listen, at least the canister UI works entirely... * fix build : ) * Atmos device prototypes have names and descriptions * gas canister ui slider doesn't jitter * trinary prototypes * sprite for miners * ignore components * fix YAML * Fix port system doing useless thing * Fix build * fix thinking moment * fix build again because * canister direction * pipenode is a word * GasTank Air will throw on invalid states * fix build.... * Unhardcode volume pump thresholds * Volume pump and filter take time into account * Rename Join/Leave atmosphere events to AtmosDeviceEnabled/Disabled Event * Gas tank node volume is set by initial mixtuer * I love node container
2021-06-19 13:25:05 +02:00
namespace Content.Server.Atmos.EntitySystems
{
[UsedImplicitly]
internal sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
{
2022-07-25 14:10:18 +12:00
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IConfigurationManager _confMan = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly ChunkingSystem _chunkingSys = default!;
private readonly Dictionary<IPlayerSession, Dictionary<EntityUid, HashSet<Vector2i>>> _lastSentChunks = new();
2020-08-20 16:48:00 +02:00
/// <summary>
/// The tiles that have had their atmos data updated since last tick
/// </summary>
private readonly Dictionary<EntityUid, HashSet<Vector2i>> _invalidTiles = new();
2020-08-20 16:48:00 +02:00
/// <summary>
/// Gas data stored in chunks to make PVS / bubbling easier.
/// </summary>
private readonly Dictionary<EntityUid, Dictionary<Vector2i, GasOverlayChunk>> _overlay =
new();
2022-07-25 14:10:18 +12:00
// Oh look its more duplicated decal system code!
private ObjectPool<HashSet<Vector2i>> _chunkIndexPool =
new DefaultObjectPool<HashSet<Vector2i>>(
new DefaultPooledObjectPolicy<HashSet<Vector2i>>(), 64);
private ObjectPool<Dictionary<EntityUid, HashSet<Vector2i>>> _chunkViewerPool =
new DefaultObjectPool<Dictionary<EntityUid, HashSet<Vector2i>>>(
new DefaultPooledObjectPolicy<Dictionary<EntityUid, HashSet<Vector2i>>>(), 64);
2020-08-20 16:48:00 +02:00
/// <summary>
2022-07-25 14:10:18 +12:00
/// Overlay update interval, in seconds.
/// </summary>
2022-07-25 14:10:18 +12:00
private float _updateInterval;
private int _thresholds;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoved);
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
2022-07-25 14:10:18 +12:00
_confMan.OnValueChanged(CCVars.NetGasOverlayTickRate, UpdateTickRate, true);
_confMan.OnValueChanged(CCVars.GasOverlayThresholds, UpdateThresholds, true);
}
public override void Shutdown()
{
base.Shutdown();
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
2022-07-25 14:10:18 +12:00
_confMan.UnsubValueChanged(CCVars.NetGasOverlayTickRate, UpdateTickRate);
_confMan.UnsubValueChanged(CCVars.GasOverlayThresholds, UpdateThresholds);
}
2022-07-25 14:10:18 +12:00
private void UpdateTickRate(float value) => _updateInterval = value > 0.0f ? 1 / value : float.MaxValue;
private void UpdateThresholds(int value) => _thresholds = value;
2022-07-25 14:10:18 +12:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invalidate(EntityUid grid, Vector2i index)
{
2022-07-25 14:10:18 +12:00
_invalidTiles.GetOrNew(grid).Add(index);
}
private void OnGridRemoved(GridRemovalEvent ev)
{
_overlay.Remove(ev.EntityUid);
}
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.NewStatus != SessionStatus.InGame)
{
2022-07-25 14:10:18 +12:00
if (_lastSentChunks.Remove(e.Session, out var set))
ReturnToPool(set);
return;
}
2022-07-25 14:10:18 +12:00
if (!_lastSentChunks.ContainsKey(e.Session))
{
2022-07-25 14:10:18 +12:00
_lastSentChunks[e.Session] = _chunkViewerPool.Get();
DebugTools.Assert(_lastSentChunks[e.Session].Count == 0);
}
}
2022-07-25 14:10:18 +12:00
private void ReturnToPool(Dictionary<EntityUid, HashSet<Vector2i>> chunks)
{
2022-07-25 14:10:18 +12:00
foreach (var (_, previous) in chunks)
{
2022-07-25 14:10:18 +12:00
previous.Clear();
_chunkIndexPool.Return(previous);
}
2020-08-20 16:48:00 +02:00
2022-07-25 14:10:18 +12:00
chunks.Clear();
_chunkViewerPool.Return(chunks);
}
/// <summary>
2022-07-25 14:10:18 +12:00
/// Updates the visuals for a tile on some grid chunk.
/// </summary>
2022-07-25 14:10:18 +12:00
private void UpdateChunkTile(GridAtmosphereComponent gridAtmosphere, GasOverlayChunk chunk, Vector2i index, GameTick curTick)
{
2022-07-25 14:10:18 +12:00
var oldData = chunk.GetData(index);
if (!gridAtmosphere.Tiles.TryGetValue(index, out var tile))
{
if (oldData == null)
return;
2022-07-25 14:10:18 +12:00
chunk.LastUpdate = curTick;
chunk.SetData(index, null);
return;
}
2022-07-25 14:10:18 +12:00
var opacity = new byte[VisibleGasId.Length];
GasOverlayData newData = new(tile!.Hotspot.State, opacity);
if (tile.Air != null)
{
2022-07-25 14:10:18 +12:00
var i = 0;
foreach (var id in VisibleGasId)
{
2022-07-25 14:10:18 +12:00
var gas = _atmosphereSystem.GetGas(id);
var moles = tile.Air.Moles[id];
2022-07-25 14:10:18 +12:00
if (moles >= gas.GasMolesVisible)
{
2022-07-25 14:10:18 +12:00
opacity[i] = (byte) (ContentHelpers.RoundToLevels(
MathHelper.Clamp01((moles - gas.GasMolesVisible) / (gas.GasMolesVisibleMax - gas.GasMolesVisible)) * 255, byte.MaxValue, _thresholds) * 255 / (_thresholds - 1));
}
2022-07-25 14:10:18 +12:00
i++;
}
}
2022-07-25 14:10:18 +12:00
if (oldData != null && oldData.Value.Equals(newData))
return;
chunk.SetData(index, newData);
chunk.LastUpdate = curTick;
}
2022-07-25 14:10:18 +12:00
private void UpdateOverlayData(GameTick curTick)
{
2022-07-25 14:10:18 +12:00
foreach (var (gridId, invalidIndices) in _invalidTiles)
{
2022-07-25 14:10:18 +12:00
if (!TryComp(gridId, out GridAtmosphereComponent? gam))
{
2022-07-25 14:10:18 +12:00
_overlay.Remove(gridId);
continue;
}
2022-07-25 14:10:18 +12:00
var chunks = _overlay.GetOrNew(gridId);
2022-07-25 14:10:18 +12:00
foreach (var index in invalidIndices)
{
2022-07-25 14:10:18 +12:00
var chunkIndex = GetGasChunkIndices(index);
2022-07-25 14:10:18 +12:00
if (!chunks.TryGetValue(chunkIndex, out var chunk))
chunks[chunkIndex] = chunk = new GasOverlayChunk(chunkIndex);
2020-08-20 16:48:00 +02:00
2022-07-25 14:10:18 +12:00
UpdateChunkTile(gam, chunk, index, curTick);
}
}
2022-07-25 14:10:18 +12:00
_invalidTiles.Clear();
}
2022-07-25 14:10:18 +12:00
public override void Update(float frameTime)
{
base.Update(frameTime);
AccumulatedFrameTime += frameTime;
2022-07-25 14:10:18 +12:00
if (AccumulatedFrameTime < _updateInterval) return;
AccumulatedFrameTime -= _updateInterval;
var curTick = _gameTiming.CurTick;
// First, update per-chunk visual data for any invalidated tiles.
UpdateOverlayData(curTick);
// 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.
2022-07-25 14:10:18 +12:00
var xformQuery = GetEntityQuery<TransformComponent>();
2020-08-20 16:48:00 +02:00
2022-07-25 14:10:18 +12:00
foreach (var session in Filter.GetAllPlayers(_playerManager))
{
if (session is IPlayerSession { Status: SessionStatus.InGame } playerSession)
UpdatePlayer(playerSession, xformQuery, curTick);
}
}
2020-08-20 16:48:00 +02:00
2022-07-25 14:10:18 +12:00
private void UpdatePlayer(IPlayerSession playerSession, EntityQuery<TransformComponent> xformQuery, GameTick curTick)
{
var chunksInRange = _chunkingSys.GetChunksForSession(playerSession, ChunkSize, xformQuery, _chunkIndexPool, _chunkViewerPool);
2022-07-25 14:10:18 +12:00
if (!_lastSentChunks.TryGetValue(playerSession, out var previouslySent))
{
2022-07-25 14:10:18 +12:00
_lastSentChunks[playerSession] = previouslySent = _chunkViewerPool.Get();
DebugTools.Assert(previouslySent.Count == 0);
}
2022-07-25 14:10:18 +12:00
var ev = new GasOverlayUpdateEvent();
2020-08-20 16:48:00 +02:00
2022-07-25 14:10:18 +12:00
foreach (var (grid, oldIndices) in previouslySent)
{
2022-07-25 14:10:18 +12:00
// Mark the whole grid as stale and flag for removal.
if (!chunksInRange.TryGetValue(grid, out var chunks))
{
2022-07-25 14:10:18 +12:00
previouslySent.Remove(grid);
// If grid was deleted then don't worry about sending it to the client.
if (_mapManager.IsGrid(grid))
ev.RemovedChunks[grid] = oldIndices;
else
{
2022-07-25 14:10:18 +12:00
oldIndices.Clear();
_chunkIndexPool.Return(oldIndices);
}
2022-07-25 14:10:18 +12:00
continue;
}
2022-07-25 14:10:18 +12:00
var old = _chunkIndexPool.Get();
DebugTools.Assert(old.Count == 0);
foreach (var chunk in oldIndices)
{
2022-07-25 14:10:18 +12:00
if (!chunks.Contains(chunk))
old.Add(chunk);
}
2020-08-20 16:48:00 +02:00
2022-07-25 14:10:18 +12:00
if (old.Count == 0)
_chunkIndexPool.Return(old);
else
ev.RemovedChunks.Add(grid, old);
}
2022-07-25 14:10:18 +12:00
foreach (var (grid, gridChunks) in chunksInRange)
{
2022-07-25 14:10:18 +12:00
// Not all grids have atmospheres.
if (!_overlay.TryGetValue(grid, out var gridData))
continue;
2022-07-25 14:10:18 +12:00
List<GasOverlayChunk> dataToSend = new();
ev.UpdatedChunks[grid] = dataToSend;
2022-07-25 14:10:18 +12:00
previouslySent.TryGetValue(grid, out var previousChunks);
2022-07-25 14:10:18 +12:00
foreach (var index in gridChunks)
{
if (!gridData.TryGetValue(index, out var value))
continue;
2020-08-20 16:48:00 +02:00
2022-07-25 14:10:18 +12:00
if (previousChunks != null &&
previousChunks.Contains(index) &&
value.LastUpdate != curTick)
continue;
2022-07-25 14:10:18 +12:00
dataToSend.Add(value);
}
2022-07-25 14:10:18 +12:00
previouslySent[grid] = gridChunks;
if (previousChunks != null)
{
2022-07-25 14:10:18 +12:00
previousChunks.Clear();
_chunkIndexPool.Return(previousChunks);
}
}
2022-07-25 14:10:18 +12:00
RaiseNetworkEvent(ev, playerSession.ConnectedClient);
ReturnToPool(ev.RemovedChunks);
}
2022-07-25 14:10:18 +12:00
public override void Reset(RoundRestartCleanupEvent ev)
{
_invalidTiles.Clear();
_overlay.Clear();
2022-07-25 14:10:18 +12:00
foreach (var data in _lastSentChunks.Values)
{
2022-07-25 14:10:18 +12:00
ReturnToPool(data);
}
2022-07-25 14:10:18 +12:00
_lastSentChunks.Clear();
}
}
}